Merge branch 'master' into release-9
diff --git a/.travis.yml b/.travis.yml
index 80b6f4b..af6acdf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,8 @@
+branches:
+  only:
+    - /^release-.*$/
 language: java
+script: mvn install
 jdk:
   - openjdk7
   - oraclejdk7
diff --git a/examples/embedded/src/main/resources/jetty-logging.properties b/examples/embedded/src/main/resources/jetty-logging.properties
index 73456ad..5838fba 100644
--- a/examples/embedded/src/main/resources/jetty-logging.properties
+++ b/examples/embedded/src/main/resources/jetty-logging.properties
@@ -5,6 +5,6 @@
 #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.io.LEVEL=DEBUG
+#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
 #org.eclipse.jetty.spdy.server.LEVEL=DEBUG
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
index 7b66d6f..7adcb80 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -40,6 +40,7 @@
     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;
@@ -90,6 +91,15 @@
                 return;
             }
 
+            HttpConversation conversation = client.getConversation(request.getConversationID(), false);
+            if (conversation.getAttribute(AUTHENTICATION_ATTRIBUTE) != null)
+            {
+                // We have already tried to authenticate, but we failed again
+                LOG.debug("Bad credentials for {}", request);
+                forwardSuccessComplete(request, response);
+                return;
+            }
+
             HttpHeader header = getAuthenticateHeader();
             List<Authentication.HeaderInfo> headerInfos = parseAuthenticateHeader(response, header);
             if (headerInfos.isEmpty())
@@ -118,7 +128,6 @@
                 return;
             }
 
-            HttpConversation conversation = client.getConversation(request.getConversationID(), false);
             final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
             LOG.debug("Authentication result {}", authnResult);
             if (authnResult == null)
@@ -127,6 +136,8 @@
                 return;
             }
 
+            conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true);
+
             Request newRequest = client.copyRequest(request, request.getURI());
             authnResult.apply(newRequest);
             newRequest.onResponseSuccess(new Response.SuccessListener()
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 dd2c7bc..643d117 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
@@ -63,6 +63,7 @@
 import org.eclipse.jetty.util.Jetty;
 import org.eclipse.jetty.util.Promise;
 import org.eclipse.jetty.util.SocketAddressResolver;
+import org.eclipse.jetty.util.URIUtil;
 import org.eclipse.jetty.util.component.ContainerLifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -362,7 +363,7 @@
      */
     public Request newRequest(String host, int port)
     {
-        return newRequest(URI.create(address("http", host, port)));
+        return newRequest(address("http", host, port));
     }
 
     /**
@@ -417,9 +418,11 @@
         return newRequest;
     }
 
-    private String address(String scheme, String host, int port)
+    protected String address(String scheme, String host, int port)
     {
-        return scheme + "://" + host + ":" + port;
+        StringBuilder result = new StringBuilder();
+        URIUtil.appendSchemeHostPort(result, scheme, host, port);
+        return result.toString();
     }
 
     /**
@@ -900,6 +903,13 @@
         return encodingField;
     }
 
+    protected String normalizeHost(String host)
+    {
+        if (host != null && host.matches("\\[.*\\]"))
+            return host.substring(1, host.length() - 1);
+        return host;
+    }
+
     protected int normalizePort(String scheme, int port)
     {
         return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
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
index ca61d22..1769ff3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
@@ -18,14 +18,10 @@
 
 package org.eclipse.jetty.client;
 
-import java.io.UnsupportedEncodingException;
 import java.net.HttpCookie;
 import java.net.URI;
-import java.net.URLEncoder;
-import java.nio.charset.UnsupportedCharsetException;
 import java.util.ArrayList;
 import java.util.Enumeration;
-import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -35,17 +31,14 @@
 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.StringContentProvider;
 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;
-import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.io.AbstractConnection;
 import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.util.Fields;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 
@@ -166,45 +159,12 @@
             path = "/";
             request.path(path);
         }
-        if (destination.isProxied() && HttpMethod.CONNECT != request.getMethod())
+        if (destination.isProxied() && HttpMethod.CONNECT != method)
         {
             path = request.getURI().toString();
             request.path(path);
         }
 
-        Fields fields = request.getParams();
-        if (!fields.isEmpty())
-        {
-            StringBuilder params = new StringBuilder();
-            for (Iterator<Fields.Field> fieldIterator = fields.iterator(); fieldIterator.hasNext();)
-            {
-                Fields.Field field = fieldIterator.next();
-                String[] values = field.values();
-                for (int i = 0; i < values.length; ++i)
-                {
-                    if (i > 0)
-                        params.append("&");
-                    params.append(field.name()).append("=");
-                    params.append(urlEncode(values[i]));
-                }
-                if (fieldIterator.hasNext())
-                    params.append("&");
-            }
-
-            // POST with no content, send parameters as body
-            if (method == HttpMethod.POST && request.getContent() == null)
-            {
-                request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString());
-                request.content(new StringContentProvider(params.toString()));
-            }
-            else
-            {
-                path += "?";
-                path += params.toString();
-                request.path(path);
-            }
-        }
-
         // If we are HTTP 1.1, add the Host header
         if (version.getVersion() > 10)
         {
@@ -257,19 +217,6 @@
         }
     }
 
-    private String urlEncode(String value)
-    {
-        String encoding = "UTF-8";
-        try
-        {
-            return URLEncoder.encode(value, encoding);
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new UnsupportedCharsetException(encoding);
-        }
-    }
-
     public HttpExchange getExchange()
     {
         return exchange.get();
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
index 081e4b6..2f33731 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -374,10 +374,9 @@
     }
 
     @Override
-    public boolean earlyEOF()
+    public void earlyEOF()
     {
         failAndClose(new EOFException());
-        return false;
     }
 
     private void failAndClose(Throwable 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
index 6841f94..458dca3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -22,10 +22,12 @@
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URLDecoder;
+import java.net.URLEncoder;
 import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -62,6 +64,7 @@
     private URI uri;
     private String scheme;
     private String path;
+    private String query;
     private HttpMethod method;
     private HttpVersion version;
     private long idleTimeout;
@@ -80,35 +83,15 @@
         this.client = client;
         this.conversation = conversation;
         scheme = uri.getScheme();
-        host = uri.getHost();
+        host = client.normalizeHost(uri.getHost());
         port = client.normalizePort(scheme, uri.getPort());
         path = uri.getRawPath();
-        String query = uri.getRawQuery();
-        if (query != null)
-        {
-            for (String nameValue : query.split("&"))
-            {
-                String[] parts = nameValue.split("=");
-                param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
-            }
-        }
+        query = uri.getRawQuery();
+        extractParams(query);
         this.uri = buildURI();
         followRedirects(client.isFollowRedirects());
     }
 
-    private String urlDecode(String value)
-    {
-        String charset = "UTF-8";
-        try
-        {
-            return URLDecoder.decode(value, charset);
-        }
-        catch (UnsupportedEncodingException x)
-        {
-            throw new UnsupportedCharsetException(charset);
-        }
-    }
-
     @Override
     public long getConversationID()
     {
@@ -163,12 +146,26 @@
     @Override
     public Request path(String path)
     {
-        this.path = path;
+        URI uri = URI.create(path);
+        this.path = uri.getRawPath();
+        String query = uri.getRawQuery();
+        if (query != null)
+        {
+            this.query = query;
+            params.clear();
+            extractParams(query);
+        }
         this.uri = buildURI();
         return this;
     }
 
     @Override
+    public String getQuery()
+    {
+        return query;
+    }
+
+    @Override
     public URI getURI()
     {
         return uri;
@@ -191,13 +188,14 @@
     public Request param(String name, String value)
     {
         params.add(name, value);
+        this.query = buildQuery();
         return this;
     }
 
     @Override
     public Fields getParams()
     {
-        return params;
+        return new Fields(params, true);
     }
 
     @Override
@@ -496,12 +494,73 @@
         return aborted;
     }
 
+    private String buildQuery()
+    {
+        StringBuilder result = new StringBuilder();
+        for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
+        {
+            Fields.Field field = iterator.next();
+            String[] values = field.values();
+            for (int i = 0; i < values.length; ++i)
+            {
+                if (i > 0)
+                    result.append("&");
+                result.append(field.name()).append("=");
+                result.append(urlEncode(values[i]));
+            }
+            if (iterator.hasNext())
+                result.append("&");
+        }
+        return result.toString();
+    }
+
+    private String urlEncode(String value)
+    {
+        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("=");
+                param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
+            }
+        }
+    }
+
+    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()
     {
         String path = getPath();
+        String query = getQuery();
+        if (query != null)
+            path += "?" + query;
         URI result = URI.create(path);
         if (!result.isAbsolute())
-            result = URI.create(getScheme() + "://" + getHost() + ":" + getPort() + path);
+            result = URI.create(client.address(getScheme(), getHost(), getPort()) + path);
         return result;
     }
 
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
index 17835ba..f6737cc 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -172,7 +172,11 @@
                     {
                         ContentProvider requestContent = request.getContent();
                         long contentLength = requestContent == null ? -1 : requestContent.getLength();
-                        requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), request.getPath());
+                        String path = request.getPath();
+                        String query = request.getQuery();
+                        if (query != null)
+                            path += "?" + query;
+                        requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), path);
                         break;
                     }
                     case NEED_HEADER:
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
index f78447c..252aff1 100644
--- 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
@@ -20,6 +20,7 @@
 
 import java.io.IOException;
 import java.net.URI;
+import java.net.URLEncoder;
 import java.nio.ByteBuffer;
 import java.nio.file.Path;
 import java.util.EventListener;
@@ -86,18 +87,33 @@
     Request method(HttpMethod method);
 
     /**
-     * @return the path of this request, such as "/"
+     * @return the path of this request, such as "/" or "/path" - without the query
+     * @see #getQuery()
      */
     String getPath();
 
     /**
-     * @param path the path of this request, such as "/"
+     * 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 parameter value is the euro symbol &euro; then the
+     * query string must be "param=%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 full URI of this request such as "http://host:port/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();
 
@@ -118,6 +134,9 @@
     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
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 a50bf5b..503ad74 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
@@ -20,7 +20,6 @@
 
 import java.io.IOException;
 import java.net.Socket;
-import java.net.URI;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -94,8 +93,6 @@
             {
                 Assert.assertTrue(result.isSucceeded());
                 Assert.assertEquals(200, result.getResponse().getStatus());
-                URI uri = result.getRequest().getURI();
-                Assert.assertTrue(uri.getPort() > 0);
                 latch2.countDown();
             }
         });
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
index 80c3d0b..6f6d705 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
@@ -304,4 +304,20 @@
         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(555, TimeUnit.SECONDS).send();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(401, response.getStatus());
+    }
 }
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..0727e3a
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java
@@ -0,0 +1,241 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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.size());
+        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());
+            }
+        });
+
+        Request request = client.newRequest("localhost", connector.getLocalPort())
+                .scheme(scheme)
+                .timeout(5, TimeUnit.SECONDS)
+                .path(path + "?" + query);
+
+        Assert.assertEquals(path, request.getPath());
+        Assert.assertEquals(query, request.getQuery());
+        Fields params = request.getParams();
+        Assert.assertEquals(1, params.size());
+        Assert.assertEquals(value, params.get(name).value());
+
+        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";
+        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());
+        Fields params = request.getParams();
+        Assert.assertEquals(1, params.size());
+        Assert.assertEquals(value, params.get(name).value());
+
+        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";
+        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());
+        Fields params = request.getParams();
+        Assert.assertEquals(2, params.size());
+        Assert.assertEquals(value1, params.get(name1).value());
+        Assert.assertEquals(value2, params.get(name2).value());
+
+        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";
+        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());
+        Fields params = request.getParams();
+        Assert.assertEquals(2, params.size());
+        Assert.assertEquals(value1, params.get(name1).value());
+        Assert.assertEquals(value2, params.get(name2).value());
+
+        ContentResponse response = request.send();
+
+        Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+    }
+}
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
index 4d2aa2f..b9be5c1 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -24,6 +24,8 @@
 import java.util.concurrent.BlockingQueue;
 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;
@@ -66,7 +68,10 @@
         Assert.assertEquals(0, activeConnections.size());
 
         final CountDownLatch headersLatch = new CountDownLatch(1);
+        final CountDownLatch testLatch = new CountDownLatch(1);
         final CountDownLatch successLatch = new CountDownLatch(3);
+        final AtomicBoolean failed = new AtomicBoolean(false);
+        
         client.newRequest(host, port)
                 .scheme(scheme)
                 .onRequestSuccess(new Request.SuccessListener()
@@ -82,9 +87,15 @@
                     @Override
                     public void onHeaders(Response response)
                     {
-                        Assert.assertEquals(0, idleConnections.size());
-                        Assert.assertEquals(1, activeConnections.size());
                         headersLatch.countDown();
+                        try
+                        {
+                            testLatch.await();
+                        }
+                        catch (InterruptedException e)
+                        {
+                            e.printStackTrace();
+                        }
                     }
                 })
                 .send(new Response.Listener.Empty()
@@ -98,16 +109,21 @@
                     @Override
                     public void onComplete(Result result)
                     {
-                        Assert.assertFalse(result.isFailed());
+                        failed.set(result.isFailed());
                         successLatch.countDown();
                     }
                 });
 
         Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
+        Assert.assertEquals(0, idleConnections.size());
+        Assert.assertEquals(1, activeConnections.size());
+        testLatch.countDown();
+        
         Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
 
         Assert.assertEquals(1, idleConnections.size());
         Assert.assertEquals(0, activeConnections.size());
+        Assert.assertFalse(failed.get());
     }
 
     @Test
@@ -127,6 +143,8 @@
 
         final CountDownLatch beginLatch = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(2);
+        final AtomicBoolean failed = new AtomicBoolean(false);
+        
         client.newRequest(host, port).scheme(scheme).listener(new Request.Listener.Empty()
         {
             @Override
@@ -146,9 +164,7 @@
             @Override
             public void onComplete(Result result)
             {
-                Assert.assertTrue(result.isFailed());
-                Assert.assertEquals(0, idleConnections.size());
-                Assert.assertEquals(0, activeConnections.size());
+                failed.set(result.isFailed());
                 failureLatch.countDown();
             }
         });
@@ -158,6 +174,7 @@
 
         Assert.assertEquals(0, idleConnections.size());
         Assert.assertEquals(0, activeConnections.size());
+        Assert.assertTrue(failed.get());
     }
 
     @Test
@@ -176,6 +193,9 @@
         Assert.assertEquals(0, activeConnections.size());
 
         final CountDownLatch successLatch = new CountDownLatch(3);
+        final AtomicBoolean failed = new AtomicBoolean(false);
+        final AtomicBoolean four_hundred = new AtomicBoolean(false);
+        
         client.newRequest(host, port)
                 .scheme(scheme)
                 .listener(new Request.Listener.Empty()
@@ -198,16 +218,16 @@
                     @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
+                        four_hundred.set(response.getStatus()==400);
                         successLatch.countDown();
                     }
 
                     @Override
                     public void onComplete(Result result)
                     {
-                        Assert.assertFalse(result.isFailed());
+                        failed.set(result.isFailed());
                         successLatch.countDown();
                     }
                 });
@@ -216,6 +236,8 @@
 
         Assert.assertEquals(0, idleConnections.size());
         Assert.assertEquals(0, activeConnections.size());
+        Assert.assertFalse(failed.get());
+        Assert.assertTrue(four_hundred.get());
     }
 
     @Slow
@@ -236,6 +258,8 @@
 
         final long delay = 1000;
         final CountDownLatch successLatch = new CountDownLatch(3);
+        final AtomicBoolean failed = new AtomicBoolean(false);
+        final AtomicBoolean four_hundred = new AtomicBoolean(false);
         client.newRequest(host, port)
                 .scheme(scheme)
                 .listener(new Request.Listener.Empty()
@@ -271,16 +295,16 @@
                     @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
+                        four_hundred.set(response.getStatus()==400);
                         successLatch.countDown();
                     }
 
                     @Override
                     public void onComplete(Result result)
                     {
-                        Assert.assertFalse(result.isFailed());
+                        failed.set(result.isFailed());
                         successLatch.countDown();
                     }
                 });
@@ -289,6 +313,8 @@
 
         Assert.assertEquals(0, idleConnections.size());
         Assert.assertEquals(0, activeConnections.size());
+        Assert.assertFalse(failed.get());
+        Assert.assertTrue(four_hundred.get());
     }
 
     @Test
@@ -309,6 +335,7 @@
         server.stop();
 
         final CountDownLatch failureLatch = new CountDownLatch(2);
+        final AtomicBoolean failed = new AtomicBoolean(false);
         client.newRequest(host, port)
                 .scheme(scheme)
                 .onRequestFailure(new Request.FailureListener()
@@ -324,7 +351,7 @@
                     @Override
                     public void onComplete(Result result)
                     {
-                        Assert.assertTrue(result.isFailed());
+                        failed.set(result.isFailed());
                         failureLatch.countDown();
                     }
                 });
@@ -333,6 +360,7 @@
 
         Assert.assertEquals(0, idleConnections.size());
         Assert.assertEquals(0, activeConnections.size());
+        Assert.assertTrue(failed.get());
     }
 
     @Test
@@ -359,6 +387,7 @@
         Assert.assertEquals(0, activeConnections.size());
 
         final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicBoolean failed = new AtomicBoolean(false);
         client.newRequest(host, port)
                 .scheme(scheme)
                 .send(new Response.Listener.Empty()
@@ -366,9 +395,7 @@
                     @Override
                     public void onComplete(Result result)
                     {
-                        Assert.assertFalse(result.isFailed());
-                        Assert.assertEquals(0, idleConnections.size());
-                        Assert.assertEquals(0, activeConnections.size());
+                        failed.set(result.isFailed());
                         latch.countDown();
                     }
                 });
@@ -377,6 +404,7 @@
 
         Assert.assertEquals(0, idleConnections.size());
         Assert.assertEquals(0, activeConnections.size());
+        Assert.assertFalse(failed.get());
     }
 
     @Test
@@ -410,6 +438,7 @@
             Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
 
             final CountDownLatch latch = new CountDownLatch(1);
+            final AtomicBoolean failed = new AtomicBoolean(false);
             ByteBuffer buffer = ByteBuffer.allocate(16 * 1024 * 1024);
             Arrays.fill(buffer.array(),(byte)'x');
             client.newRequest(host, port)
@@ -420,8 +449,7 @@
                         @Override
                         public void onComplete(Result result)
                         {
-                            Assert.assertEquals(0, idleConnections.size());
-                            Assert.assertEquals(0, activeConnections.size());
+                            failed.set(result.isFailed());
                             latch.countDown();
                         }
                     });
@@ -430,6 +458,7 @@
 
             Assert.assertEquals(0, idleConnections.size());
             Assert.assertEquals(0, activeConnections.size());
+            Assert.assertTrue(failed.get());
             server.stop();
         }
         finally
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 9e94676..fdc8092 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
@@ -44,6 +44,7 @@
 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;
@@ -290,9 +291,7 @@
     public URI getServerURI() throws UnknownHostException
     {
         StringBuilder uri = new StringBuilder();
-        uri.append(getScheme()).append("://");
-        uri.append(InetAddress.getLocalHost().getHostAddress());
-        uri.append(":").append(getServerPort());
+        URIUtil.appendSchemeHostPort(uri, getScheme(), InetAddress.getLocalHost().getHostAddress(), getServerPort());
         return URI.create(uri.toString());
     }
 
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 2189d3b..e06e1fb 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -100,17 +100,47 @@
                   <type>war</type>
                   <overWrite>true</overWrite>
                   <includes>**</includes>
-                  <outputDirectory>${assembly-directory}/webapps</outputDirectory>
+                  <outputDirectory>${assembly-directory}/webapps.demo</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}/webapps.demo</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}/webapps.demo</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}/webapps.demo</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}/webapps</outputDirectory>
+                  <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
                   <destFileName>xref-proxy.war</destFileName>
                 </artifactItem>
                 <artifactItem>
@@ -120,7 +150,7 @@
                   <type>war</type>
                   <overWrite>true</overWrite>
                   <includes>**</includes>
-                  <outputDirectory>${assembly-directory}/webapps</outputDirectory>
+                  <outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
                   <destFileName>async-rest.war</destFileName>
                 </artifactItem>
                 <artifactItem>
@@ -173,6 +203,7 @@
               </artifactItems>
             </configuration>
           </execution>
+
           <execution>
             <id>unpack-setuid-config</id>
             <phase>process-resources</phase>
@@ -193,6 +224,70 @@
               </artifactItems>
             </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>
+            </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>
+            </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>
+            </configuration>
+          </execution>
+
           <execution>
             <id>copy-lib-deps</id>
             <phase>generate-resources</phase>
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index cc3f771..bfcb1fd 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -118,9 +118,9 @@
   for T in 1 2 3 4 5 6 7 9 10 11 12 13 14 15 
   do
     sleep 4
-    [ -z "$(grep STARTED $1)" ] || return 0
-    [ -z "$(grep STOPPED $1)" ] || return 1
-    [ -z "$(grep FAILED $1)" ] || return 1
+    [ -z "$(grep STARTED $1 2>/dev/null)" ] || return 0
+    [ -z "$(grep STOPPED $1 2>/dev/null)" ] || return 1
+    [ -z "$(grep FAILED $1 2>/dev/null)" ] || return 1
     local PID=$(cat "$2" 2>/dev/null) || return 1
     kill -0 "$PID" 2>/dev/null || return 1
     echo -n ". "
@@ -338,13 +338,17 @@
 fi
 
 #####################################################
-# Find a PID for the pid file
+# Find a pid and state file
 #####################################################
 if [ -z "$JETTY_PID" ] 
 then
   JETTY_PID="$JETTY_RUN/jetty.pid"
 fi
-JETTY_STATE=$(dirname $JETTY_PID)/jetty.state
+
+if [ -z "$JETTY_STATE" ] 
+then
+  JETTY_STATE=$JETTY_HOME/jetty.state
+fi
 JAVA_OPTIONS+=("-Djetty.state=$JETTY_STATE")
 rm -f $JETTY_STATE
 
@@ -415,8 +419,8 @@
 then
   echo "JETTY_HOME     =  $JETTY_HOME"
   echo "JETTY_CONF     =  $JETTY_CONF"
-  echo "JETTY_RUN      =  $JETTY_RUN"
   echo "JETTY_PID      =  $JETTY_PID"
+  echo "JETTY_START    =  $JETTY_START"
   echo "JETTY_ARGS     =  $JETTY_ARGS"
   echo "CONFIGS        =  ${CONFIGS[*]}"
   echo "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
@@ -566,22 +570,21 @@
     fi
 
     exec "${RUN_CMD[@]}"
-
     ;;
 
   check|status)
     echo "Checking arguments to Jetty: "
+    echo "START_INI      =  $START_INI"
     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_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 "CLASSPATH      =  $CLASSPATH"
+    echo "JAVA           =  $JAVA"
+    echo "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
+    echo "JETTY_ARGS     =  $JETTY_ARGS"
     echo "RUN_CMD        =  ${RUN_CMD[*]}"
     echo
     
diff --git a/jetty-distribution/src/main/resources/etc/jetty-demo.xml b/jetty-distribution/src/main/resources/etc/jetty-demo.xml
new file mode 100644
index 0000000..4dc0aae
--- /dev/null
+++ b/jetty-distribution/src/main/resources/etc/jetty-demo.xml
@@ -0,0 +1,124 @@
+<?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 webapps.demo to deployment manager scans                  -->
+  <!-- ============================================================= -->
+  <Ref refid="DeploymentManager">
+    <Call id="webappprovider" name="addAppProvider">
+      <Arg>
+        <New class="org.eclipse.jetty.deploy.providers.WebAppProvider">
+          <Set name="monitoredDirName"><Property name="jetty.home" default="." />/webapps.demo</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.PropertiesConfigurationManager"/>
+          </Set>
+        </New>
+      </Arg>
+    </Call>
+  </Ref>
+
+
+  <!-- ============================================================= -->
+  <!-- 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/jetty-distribution/src/main/resources/start.d/900-demo.ini b/jetty-distribution/src/main/resources/start.d/900-demo.ini
new file mode 100644
index 0000000..fc153ad
--- /dev/null
+++ b/jetty-distribution/src/main/resources/start.d/900-demo.ini
@@ -0,0 +1,53 @@
+
+# ===========================================================
+# Enable the demonstration web applications
+#
+# To disable the demos, either delete this file, move it out of 
+# the start.d directory or rename it to not end with ".ini"
+# ===========================================================
+
+# ===========================================================
+# Enable rewrite handler
+# -----------------------------------------------------------
+OPTIONS=rewrite
+etc/jetty-rewrite.xml
+
+# ===========================================================
+# Add a deploy app provider to scan the webapps.demo directory
+# -----------------------------------------------------------
+etc/jetty-demo.xml
+
+# ===========================================================
+# Enable the Jetty HTTP client APIs for use by demo webapps
+# -----------------------------------------------------------
+OPTIONS=client
+
+# ===========================================================
+# Enable the test-realm login service for use by authentication
+# demonstrations
+# -----------------------------------------------------------
+etc/test-realm.xml
+
+# ===========================================================
+# Enable JAAS test webapp
+# -----------------------------------------------------------
+OPTIONS=jaas
+jaas.login.conf=webapps.demo/test-jaas.d/login.conf
+etc/jetty-jaas.xml
+
+# ===========================================================
+# Enable JNDI test webapp
+# -----------------------------------------------------------
+OPTIONS=jndi,jndi.demo
+
+# ===========================================================
+# Enable additional webapp environment configurators 
+# -----------------------------------------------------------
+OPTIONS=plus
+etc/jetty-plus.xml
+
+# ===========================================================
+# Enable servlet 3.1 annotations
+# -----------------------------------------------------------
+OPTIONS=annotations
+etc/jetty-annotations.xml
diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini
index 382010d..fdd0102 100644
--- a/jetty-distribution/src/main/resources/start.ini
+++ b/jetty-distribution/src/main/resources/start.ini
@@ -1,5 +1,6 @@
 #===========================================================
 # Jetty start.jar arguments
+#
 # The contents of this file, together with the start.ini
 # fragments found in start.d directory are used to build
 # the classpath and command line on a call to
@@ -21,50 +22,34 @@
 #  + A JVM option like: -Xmx2000m 
 #  + A System Property like: -Dcom.sun.management.jmxremote
 #
+#-----------------------------------------------------------
+#
+# NOTE: The lines in this file may be uncommented to activate
+# features. Alternately, the lines may be copied to a ini file
+# in the start.d directory to enabled configuration without
+# editing this file.  See start.d/900-demo.ini for an example.
+#
+# Future releases will switch start.d style configuration for 
+# all features.
 #===========================================================
 
-#===========================================================
-# The --exec option should be used if any of the JVM options
-# in this file are uncommented (eg -D* or -X*).  Because a
-# JVM cannot change it's own options, the --exec flag causes
-# start.jar to fork a new JVM with the requested arguments.
-#
-# Alternately, a command line may be generated by running
-#
-#    java -jar start.jar --exec-print
-#
-# and the results executed to start the jetty server. 
-# For example --exec can be avoided if jetty is started on unix with
-#
-#     eval $(java -jar start.jar --exec-print)
-#
-#-----------------------------------------------------------
-# --exec
-#===========================================================
-
-#===========================================================
-# Configure Properties.
-# The properties defined here may be used by the
-# <Property name="myproperty"/> element in the XML files
-# passed to start.jar.   
-# Alternately a file ending with ".properties" can be 
-# added that will include multiple properties.
-# Properties, unlike SystemProperties, do not need --exec
-# to be specified.
-#-----------------------------------------------------------
-# jetty.home=.
-# jetty.logs=./logs
-# jetty.host=0.0.0.0
-#===========================================================
 
 
 #===========================================================
 # Configure JVM arguments.
-# Must be used with --exec or --exec-print
+# 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
 #-----------------------------------------------------------
-# -Dorg.apache.jasper.compiler.disablejsr199=true
+# --exec
 # -Xmx2000m
 # -Xmn512m
+# -XX:+UseConcMarkSweepGC
+# -XX:ParallelCMSThreads=2
+# -XX:+CMSClassUnloadingEnabled  
+# -XX:+UseCMSCompactAtFullCollection
+# -XX:CMSInitiatingOccupancyFraction=80
 # -verbose:gc
 # -XX:+PrintGCDateStamps
 # -XX:+PrintGCTimeStamps
@@ -72,12 +57,10 @@
 # -XX:+PrintTenuringDistribution
 # -XX:+PrintCommandLineFlags
 # -XX:+DisableExplicitGC
-# -XX:+UseConcMarkSweepGC
-# -XX:ParallelCMSThreads=2
-# -XX:+CMSClassUnloadingEnabled  
-# -XX:+UseCMSCompactAtFullCollection
-# -XX:CMSInitiatingOccupancyFraction=80
-#===========================================================
+
+# -Dorg.apache.jasper.compiler.disablejsr199=true
+
+
 
 #===========================================================
 # Default Server Options
@@ -87,8 +70,50 @@
 # Include the core jetty configuration file
 #-----------------------------------------------------------
 OPTIONS=Server,websocket,resources,ext
+threads.min=10
+threads.max=200
+threads.timeout=60000
+#jetty.host=myhost.com
+jetty.dump.start=false
+jetty.dump.stop=false
+
 etc/jetty.xml
+
 #===========================================================
+# JMX Management
+# To enable remote JMX access uncomment jmxremote and
+# enable --exec 
+#-----------------------------------------------------------
+OPTIONS=jmx
+# jetty.jmxrmihost=localhost
+# jetty.jmxrmiport=1099
+# -Dcom.sun.management.jmxremote
+etc/jetty-jmx.xml
+
+#===========================================================
+# Java Server Pages
+#-----------------------------------------------------------
+OPTIONS=jsp
+
+#===========================================================
+# Request logger
+# Will add a handler to log all HTTP requests to a standard
+# request log format file.
+#-----------------------------------------------------------
+# requestlog.retain=90
+# requestlog.append=true
+# requestlog.extended=true
+# etc/jetty-requestlog.xml
+
+
+#===========================================================
+# stderr/stdout logging.
+# The following configuration will redirect stderr and stdout
+# to file which is rolled over daily.
+#-----------------------------------------------------------
+# jetty.log.retain=90
+# etc/jetty-logging.xml
+
 
 #===========================================================
 # Enable SetUID
@@ -96,59 +121,25 @@
 # starting as root you must change the run privledged to true
 #-----------------------------------------------------------
 # OPTIONS=setuid
-# etc/jetty-setuid.xml
 # jetty.startServerAsPrivileged=false
 # jetty.username=jetty
 # jetty.groupname=jetty
 # jetty.umask=002
-#===========================================================
+# etc/jetty-setuid.xml
 
-#===========================================================
-# Server logging.
-# The following configuration will redirect stderr and stdout
-# to file which is rolled over daily.
-#-----------------------------------------------------------
-# etc/jetty-logging.xml
-#===========================================================
-
-#===========================================================
-# JMX Management
-# To enable remote JMX access uncomment jmxremote and
-# enable --exec or use --exec-print (see above)
-#-----------------------------------------------------------
-OPTIONS=jmx
-# jetty.jmxrmihost=localhost
-# jetty.jmxrmiport=1099
-# -Dcom.sun.management.jmxremote
-etc/jetty-jmx.xml
-#===========================================================
-
-#===========================================================
-# Java Server Pages
-#-----------------------------------------------------------
-OPTIONS=jsp
-#===========================================================
-
-
-#===========================================================
-# Annotations JNDI JAAS processing
-#-----------------------------------------------------------
-# OPTIONS=plus
-# etc/jetty-plus.xml
-# OPTIONS=annotations
-# etc/jetty-annotations.xml
-#===========================================================
 
 #===========================================================
 # HTTP Connector
 #-----------------------------------------------------------
-# jetty.port=8080
+jetty.port=8080
+http.timeout=30000
 etc/jetty-http.xml
-#===========================================================
+
 
 #===========================================================
 # SSL Context 
-# For use by HTTPS and SPDY
+# Create the keystore and trust store for use by
+# HTTPS and SPDY
 #-----------------------------------------------------------
 # jetty.keystore=etc/keystore
 # jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
@@ -157,65 +148,90 @@
 # jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
 # jetty.secure.port=8443
 # etc/jetty-ssl.xml
-#===========================================================
+
 
 #===========================================================
 # HTTPS Connector
+# Must be used with 200-ssl.ini
 #-----------------------------------------------------------
 # jetty.https.port=8443
 # etc/jetty-https.xml
+
+
 #===========================================================
+# NPN Next Protocol Negotiation 
+#
+# The SPDY and HTTP/2.0 connectors require NPN.  The jar for
+# NPN cannot be downloaded from eclipse. So the --download
+# option is used to install the NPN jar if it does not already
+# exist
+#
+#-----------------------------------------------------------
+# --exec
+# --download=http://repo1.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
+# -Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar
+
 
 #===========================================================
 # SPDY Connector
-#
-# SPDY requires the NPN jar which must be separately downloaded:
-#
-# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar
-#
-# Which should be saved in lib/npn-boot-1.1.5.v20130313.jar
-# 
-# To include the NPN jar on the boot path, you must either:
-#
-# a) enable --exec above and uncomment the -Xbootclass line
-#    below
-#
-# b) Add -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
-#    to the command line when running jetty.
-#
+# Requires SSL Context and NPN from above
 #-----------------------------------------------------------
 # OPTIONS=spdy
-# -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
 # jetty.spdy.port=8443
 # etc/jetty-spdy.xml
-#===========================================================
+
 
 #===========================================================
 # Webapplication Deployer
 #-----------------------------------------------------------
 etc/jetty-deploy.xml
-#===========================================================
+
+
+# ===========================================================
+# Enable JAAS
+# -----------------------------------------------------------
+# OPTIONS=jaas
+# jaas.login.conf=etc/login.conf
+# etc/jetty-jaas.xml
+
+# ===========================================================
+# Enable JNDI 
+# -----------------------------------------------------------
+# OPTIONS=jndi
+
+# ===========================================================
+# Enable additional webapp environment configurators 
+# -----------------------------------------------------------
+# OPTIONS=plus
+# etc/jetty-plus.xml
+
+# ===========================================================
+# Enable servlet 3.1 annotations
+# -----------------------------------------------------------
+# OPTIONS=annotations
+# etc/jetty-annotations.xml
 
 #===========================================================
-# Request logger
-# Will add a handler to log all HTTP requests to a standard
-# request log format file.
+# Other server features
 #-----------------------------------------------------------
-etc/jetty-requestlog.xml
-#===========================================================
-
-#===========================================================
-# Additional configurations
-# See headers of individual files for explanations
-#-----------------------------------------------------------
-# etc/jetty-stats.xml
 # etc/jetty-debug.xml
 # etc/jetty-ipaccess.xml
-# etc/jetty-lowresources.xml
-#===========================================================
+# etc/jetty-stats.xml
+
 
 #===========================================================
-# Lookup additional ini files in start.d
+# Low resource managment
 #-----------------------------------------------------------
-start.d/
+# lowresources.period=1050
+# lowresources.lowResourcesIdleTimeout=200
+# lowresources.monitorThreads=true
+# lowresources.maxConnections=0
+# lowresources.maxMemory=0
+# lowresources.maxLowResourcesTime=5000
+# etc/jetty-lowresources.xml
+
+
 #===========================================================
+# The start.d directory contains the active start.ini fragments 
+start.d/
+
diff --git a/jetty-distribution/src/main/resources/webapps.demo/README.TXT b/jetty-distribution/src/main/resources/webapps.demo/README.TXT
new file mode 100644
index 0000000..ec2bea2
--- /dev/null
+++ b/jetty-distribution/src/main/resources/webapps.demo/README.TXT
@@ -0,0 +1,7 @@
+
+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.
+
+For normal deployment, use the webapps directory.
+
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/images/jetty-header.jpg b/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/ROOT/images/jetty-header.jpg
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/images/jetty-header.jpg
Binary files differ
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/images/webtide_logo.jpg b/jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/ROOT/images/webtide_logo.jpg
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/images/webtide_logo.jpg
Binary files differ
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/index.html b/jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html
similarity index 87%
rename from jetty-distribution/src/main/resources/webapps/ROOT/index.html
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html
index 826974f..8f73374 100644
--- a/jetty-distribution/src/main/resources/webapps/ROOT/index.html
+++ b/jetty-distribution/src/main/resources/webapps.demo/ROOT/index.html
@@ -34,8 +34,9 @@
           <ul>
             <li><a href="/test/">Test Jetty Webapp</a></li>
             <li><a href="/async-rest/">Async Rest</a></li>
-            <li><a href="/proxy/apidocs/">Transparent Proxy to Javadoc</a></li>
-            <li><a href="/proxy/xref/">Transparent Proxy to Xref</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>
@@ -44,6 +45,8 @@
           <ul>
             <li><a href="http://www.eclipse.org/jetty/">Jetty @ Eclipse Home</a></li>
             <li><a href="http://wiki.eclipse.org/Jetty">Jetty @ Eclipse Doco</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://docs.codehaus.org/display/JETTY/Jetty+Powered">Jetty Powered</a></li>
           </ul>
diff --git a/jetty-distribution/src/main/resources/webapps/ROOT/jetty.css b/jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/ROOT/jetty.css
rename to jetty-distribution/src/main/resources/webapps.demo/ROOT/jetty.css
diff --git a/jetty-distribution/src/main/resources/webapps/example-moved.xml b/jetty-distribution/src/main/resources/webapps.demo/example-moved.xml
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/example-moved.xml
rename to jetty-distribution/src/main/resources/webapps.demo/example-moved.xml
diff --git a/jetty-distribution/src/main/resources/webapps/javadoc.xml b/jetty-distribution/src/main/resources/webapps.demo/javadoc.xml
similarity index 100%
rename from jetty-distribution/src/main/resources/webapps/javadoc.xml
rename to jetty-distribution/src/main/resources/webapps.demo/javadoc.xml
diff --git a/jetty-distribution/src/main/resources/webapps/.donotdelete b/jetty-distribution/src/main/resources/webapps/.donotdelete
deleted file mode 100644
index e69de29..0000000
--- a/jetty-distribution/src/main/resources/webapps/.donotdelete
+++ /dev/null
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
index c1a9ef2..c127e75 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java
@@ -33,7 +33,7 @@
  */
 public class HttpField
 {
-    public final static Trie<HttpField> CACHE = new ArrayTrie<>(1024);
+    public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
     public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
     
     static
@@ -77,10 +77,7 @@
         }
 
         // Add headers with null values so HttpParser can avoid looking up name again for unknown values
-        Set<HttpHeader> headers = new HashSet<>();
-        for (String key:CACHE.keySet())
-            headers.add(CACHE.get(key).getHeader());
-        for (HttpHeader h:headers)
+        for (HttpHeader h:HttpHeader.values())
             if (!CACHE.put(new HttpField(h,(String)null)))
                 throw new IllegalStateException("CACHE FULL");
         // Add some more common headers
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
index 60dc69d..6b27173 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
@@ -110,7 +110,10 @@
     {
         if (buffer.hasArray())
             return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
-        return CACHE.getBest(buffer,0,buffer.remaining());
+        
+        // TODO use cache and check for space
+        // return CACHE.getBest(buffer,0,buffer.remaining());
+        return null;
     }
     
     /* ------------------------------------------------------------ */
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 239b44f..9b89bfa 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,7 +18,6 @@
 
 package org.eclipse.jetty.http;
 
-import java.io.IOException;
 import java.nio.ByteBuffer;
 
 import org.eclipse.jetty.http.HttpTokens.EndOfContent;
@@ -26,6 +25,7 @@
 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;
 
@@ -80,13 +80,13 @@
     private String _methodString;
     private HttpVersion _version;
     private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
-    private byte _eol;
     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;
 
@@ -205,10 +205,80 @@
     }
 
     /* ------------------------------------------------------------------------------- */
+    private byte next(ByteBuffer buffer)
+    {
+        byte ch=buffer.get();
+
+        // If not a special character 
+        if (ch>=HttpTokens.SPACE || ch<0)
+        {
+            if (_cr)
+            {
+                badMessage(buffer,400,"Bad EOL");
+                return -1;
+            }
+            /*
+            if (ch>HttpTokens.SPACE)
+                System.err.println("Next "+(char)ch);
+            else
+                System.err.println("Next ["+ch+"]");*/
+            return ch;   
+        }
+            
+        
+        // Only a LF acceptable after CR
+        if (_cr)
+        {
+            _cr=false;
+            if (ch==HttpTokens.LINE_FEED)
+                return ch;
+
+            badMessage(buffer,400,"Bad EOL");
+            return -1;
+        }
+        
+        // If it is a CR
+        if (ch==HttpTokens.CARRIAGE_RETURN)
+        {
+            // Skip CR and look for a LF
+            if (buffer.hasRemaining())
+            {
+                if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+                    _headerBytes++;
+                ch=buffer.get();
+                if (ch==HttpTokens.LINE_FEED)
+                    return ch;
+
+                badMessage(buffer,HttpStatus.BAD_REQUEST_400,null);
+                return -1;
+            }
+
+            // Defer lookup of LF
+            _cr=true;
+            return 0;
+        }
+        
+        // Only LF or TAB acceptable special characters
+        if (ch!=HttpTokens.LINE_FEED && ch!=HttpTokens.TAB)
+        {
+            badMessage(buffer,HttpStatus.BAD_REQUEST_400,null);
+            return -1;
+        }
+        
+        /*
+        if (ch>HttpTokens.SPACE)
+            System.err.println("Next "+(char)ch);
+        else
+            System.err.println("Next ["+ch+"]");
+            */
+        return ch;
+    }
+    
+    /* ------------------------------------------------------------------------------- */
     /* Quick lookahead for the start state looking for a request method or a HTTP version,
      * otherwise skip white space until something else to parse.
      */
-    private void quickStart(ByteBuffer buffer)
+    private boolean quickStart(ByteBuffer buffer)
     {
         // Quick start look
         while (_state==State.START && buffer.hasRemaining())
@@ -221,7 +291,7 @@
                     _methodString = _method.asString();
                     buffer.position(buffer.position()+_methodString.length()+1);
                     setState(State.SPACE1);
-                    return;
+                    return false;
                 }
             }
             else if (_responseHandler!=null)
@@ -231,27 +301,23 @@
                 {
                     buffer.position(buffer.position()+_version.asString().length()+1);
                     setState(State.SPACE1);
-                    return;
+                    return false;
                 }
             }
 
-            byte ch=buffer.get();
-
-            if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
-            {
-                _eol=HttpTokens.LINE_FEED;
-                continue;
-            }
-            _eol=0;
-
-            if (ch > HttpTokens.SPACE || ch<0)
+            byte ch=next(buffer);
+            
+            if (ch > HttpTokens.SPACE)
             {
                 _string.setLength(0);
                 _string.append((char)ch);
                 setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
-                return;
+                return false;
             }
+            if (ch==-1)
+                return true;
         }
+        return false;
     }
 
     private String takeString()
@@ -281,7 +347,11 @@
         while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !return_from_parse)
         {
             // process each character
-            byte ch=buffer.get();
+            byte ch=next(buffer);
+            if (ch==-1)
+                return true;
+            if (ch==0)
+                continue;
 
             if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
             {
@@ -301,13 +371,6 @@
                 return true;
             }
 
-            if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
-            {
-                _eol=HttpTokens.LINE_FEED;
-                continue;
-            }
-            _eol=0;
-
             switch (_state)
             {
                 case METHOD:
@@ -413,7 +476,6 @@
                     else if (ch < HttpTokens.SPACE && ch>=0)
                     {
                         return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null);
-                        _eol=ch;
                         setState(State.HEADER);
                     }
                     else
@@ -464,28 +526,39 @@
                         {
                             setState(State.REQUEST_VERSION);
 
-                            // try quick look ahead
+                            // try quick look ahead for HTTP Version
                             if (buffer.position()>0 && buffer.hasArray())
                             {
-                                _version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
-                                if (_version!=null)
+                                HttpVersion version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+                                if (version!=null) 
                                 {
-                                    _string.setLength(0);
-                                    buffer.position(buffer.position()+_version.asString().length()-1);
-                                    _eol=buffer.get();
-                                    setState(State.HEADER);
-                                    _uri.flip();
-                                    return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version);
+                                    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);
+                                        }
+                                    }
                                 }
                             }
                         }
                     }
-                    else if (ch < HttpTokens.SPACE)
+                    else if (ch == HttpTokens.LINE_FEED)
                     {
                         if (_responseHandler!=null)
                         {
                             return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null);
-                            _eol=ch;
                             setState(State.HEADER);
                         }
                         else
@@ -502,10 +575,10 @@
                     break;
 
                 case REQUEST_VERSION:
-                    if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                    if (ch == HttpTokens.LINE_FEED)
                     {
-                        String version = takeString();
-                        _version=HttpVersion.CACHE.get(version);
+                        if (_version==null)
+                            _version=HttpVersion.CACHE.get(takeString());
                         if (_version==null)
                         {
                             badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Unknown Version");
@@ -513,14 +586,13 @@
                         }
                         
                         // Should we try to cache header fields?
-                        if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+                        if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
                         {
                             int header_cache = _handler.getHeaderCacheSize();
                             if (header_cache>0)
                                 _connectionFields=new ArrayTernaryTrie<>(header_cache);
                         }
 
-                        _eol=ch;
                         setState(State.HEADER);
                         _uri.flip();
                         return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version);
@@ -532,11 +604,10 @@
                     break;
 
                 case REASON:
-                    if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                    if (ch == HttpTokens.LINE_FEED)
                     {
                         String reason=takeLengthString();
 
-                        _eol=ch;
                         setState(State.HEADER);
                         return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, reason);
                         continue;
@@ -609,7 +680,8 @@
                     return true;
                 }
 
-                loop: for (int i = host.length(); i-- > 0;)
+                int len=host.length();
+                loop: for (int i = len; i-- > 0;)
                 {
                     char c2 = (char)(0xff & host.charAt(i));
                     switch (c2)
@@ -620,6 +692,7 @@
                         case ':':
                             try
                             {
+                                len=i;
                                 port = StringUtil.toInt(host.substring(i+1));
                             }
                             catch (NumberFormatException e)
@@ -628,10 +701,21 @@
                                 badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header");
                                 return true;
                             }
-                            host = host.substring(0,i);
                             break loop;
                     }
                 }
+                if (host.charAt(0)=='[')
+                {
+                    if (host.charAt(len-1)!=']') 
+                    {
+                        badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+                        return true;
+                    }
+                    host = host.substring(1,len-1);
+                }
+                else if (len!=host.length())
+                    host = host.substring(0,len);
+                
                 if (_requestHandler!=null)
                     _requestHandler.parsedHostHeader(host,port);
                 
@@ -652,6 +736,9 @@
             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)
@@ -676,7 +763,12 @@
         while (_state.ordinal()<State.END.ordinal() && buffer.hasRemaining() && !return_from_parse)
         {
             // process each character
-            byte ch=buffer.get();
+            byte ch=next(buffer);
+            if (ch==-1)
+                return true;
+            if (ch==0)
+                continue;
+            
             if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
             {
                 LOG.warn("Header is too large >"+_maxHeaderBytes);
@@ -684,13 +776,6 @@
                 return true;
             }
 
-            if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
-            {
-                _eol=HttpTokens.LINE_FEED;
-                continue;
-            }
-            _eol=0;
-
             switch (_state)
             {
                 case HEADER:
@@ -735,10 +820,8 @@
                             _field=null;
 
                             // now handle the ch
-                            if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                            if (ch == HttpTokens.LINE_FEED)
                             {
-                                consumeCRLF(ch,buffer);
-
                                 _contentPosition=0;
 
                                 // End of headers!
@@ -796,44 +879,53 @@
                             }
                             else
                             {
-                                if (buffer.remaining()>6)
+                                if (buffer.hasRemaining())
                                 {
                                     // Try a look ahead for the known header name and value.
-                                    _field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
-                                    if (_field==null)
-                                        _field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
+                                    HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+                                    if (field==null)
+                                        field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
                                         
-                                    if (_field!=null)
+                                    if (field!=null)
                                     {
-                                        _header=_field.getHeader();
-                                        _headerString=_field.getName();
-                                        _valueString=_field.getValue();
-                                        if (_valueString==null)
+                                        String n=field.getName();
+                                        String v=field.getValue();
+         
+                                        if (v==null)
                                         {
+                                            // Header only
+                                            _header=field.getHeader();
+                                            _headerString=n;
                                             setState(State.HEADER_VALUE);
-                                            buffer.position(buffer.position()+_headerString.length()+1);
                                             _string.setLength(0);
                                             _length=0;
-                                            _field=null;
+                                            buffer.position(buffer.position()+n.length()+1);
+                                            break;
                                         }
                                         else
                                         {
-                                            setState(State.HEADER_IN_VALUE);
-                                            buffer.position(buffer.position()+_headerString.length()+_valueString.length()+1);
-                                        }
-                                        break;
-                                    }
+                                            // Header and value
+                                            int pos=buffer.position()+n.length()+v.length()+1;
+                                            byte b=buffer.get(pos);
 
-                                    // Try a look ahead for the known header name.
-                                    _header=HttpHeader.CACHE.getBest(buffer,-1,buffer.remaining());
-                                    //_header=HttpHeader.CACHE.getBest(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.remaining()+1);
-                                    if (_header!=null)
-                                    {
-                                        _headerString=_header.asString();
-                                        _string.setLength(0);
-                                        setState(State.HEADER_IN_NAME);
-                                        buffer.position(buffer.position()+_headerString.length()-1);
-                                        break;
+                                            if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+                                            {                     
+                                                _field=field;
+                                                _header=_field.getHeader();
+                                                _headerString=n;
+                                                _valueString=v;
+                                                setState(State.HEADER_IN_VALUE);
+
+                                                if (b==HttpTokens.CARRIAGE_RETURN)
+                                                {
+                                                    _cr=true;
+                                                    buffer.position(pos+1);
+                                                }
+                                                else
+                                                    buffer.position(pos);
+                                                break;
+                                            }
+                                        }
                                     }
                                 }
 
@@ -851,9 +943,7 @@
                 case HEADER_NAME:
                     switch(ch)
                     {
-                        case HttpTokens.CARRIAGE_RETURN:
                         case HttpTokens.LINE_FEED:
-                            consumeCRLF(ch,buffer);
                             if (_headerString==null)
                             {
                                 _headerString=takeLengthString();
@@ -888,9 +978,7 @@
                 case HEADER_IN_NAME:
                     switch(ch)
                     {
-                        case HttpTokens.CARRIAGE_RETURN:
                         case HttpTokens.LINE_FEED:
-                            consumeCRLF(ch,buffer);
                             _headerString=takeString();
                             _length=-1;
                             _header=HttpHeader.CACHE.get(_headerString);
@@ -936,9 +1024,7 @@
                 case HEADER_VALUE:
                     switch(ch)
                     {
-                        case HttpTokens.CARRIAGE_RETURN:
                         case HttpTokens.LINE_FEED:
-                            consumeCRLF(ch,buffer);
                             if (_length > 0)
                             {
                                 if (_valueString!=null)
@@ -975,9 +1061,7 @@
                 case HEADER_IN_VALUE:
                     switch(ch)
                     {
-                        case HttpTokens.CARRIAGE_RETURN:
                         case HttpTokens.LINE_FEED:
-                            consumeCRLF(ch,buffer);
                             if (_length > 0)
                             {
                                 if (HttpHeaderValue.hasKnownValues(_header))
@@ -1031,17 +1115,6 @@
     }
 
     /* ------------------------------------------------------------------------------- */
-    private void consumeCRLF(byte ch, ByteBuffer buffer)
-    {
-        _eol=ch;
-        if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
-        {
-            buffer.get();
-            _eol=0;
-        }
-    }
-
-    /* ------------------------------------------------------------------------------- */
     /**
      * Parse until next Event.
      * @return True if an {@link RequestHandler} method was called and it returned true;
@@ -1059,7 +1132,8 @@
                     _methodString=null;
                     _endOfContent=EndOfContent.UNKNOWN_CONTENT;
                     _header=null;
-                    quickStart(buffer);
+                    if(quickStart(buffer))
+                        return true;
                     break;
 
                 case CONTENT:
@@ -1077,18 +1151,18 @@
                 case CLOSED:
                     if (BufferUtil.hasContent(buffer))
                     {
-                        int len=buffer.remaining();
-                        _headerBytes+=len;
+                        // Just ignore data when closed
+                        _headerBytes+=buffer.remaining();
+                        BufferUtil.clear(buffer);
                         if (_headerBytes>_maxHeaderBytes)
                         {
-                            Thread.sleep(100);
-                            String chars = BufferUtil.toDetailString(buffer);
-                            BufferUtil.clear(buffer);
-                            throw new IllegalStateException(String.format("%s %d/%d>%d data when CLOSED:%s",this,len,_headerBytes,_maxHeaderBytes,chars));
+                            // Don't want to waste time reading data of a closed request
+                            throw new IllegalStateException("too much data after closed");
                         }
-                        BufferUtil.clear(buffer);
                     }
                     return false;
+                default: break;
+    
             }
 
             // Request/response line
@@ -1113,13 +1187,6 @@
             byte ch;
             while (_state.ordinal() > State.END.ordinal() && buffer.hasRemaining())
             {
-                if (_eol == HttpTokens.CARRIAGE_RETURN && buffer.get(buffer.position()) == HttpTokens.LINE_FEED)
-                {
-                    _eol=buffer.get();
-                    continue;
-                }
-                _eol=0;
-
                 switch (_state)
                 {
                     case EOF_CONTENT:
@@ -1169,31 +1236,24 @@
 
                     case CHUNKED_CONTENT:
                     {
-                        ch=buffer.get(buffer.position());
-                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
-                            _eol=buffer.get();
-                        else if (ch <= HttpTokens.SPACE)
-                            buffer.get();
-                        else
+                        ch=next(buffer);
+                        if (ch>HttpTokens.SPACE)
                         {
-                            _chunkLength=0;
+                            _chunkLength=TypeUtil.convertHexDigit(ch);
                             _chunkPosition=0;
                             setState(State.CHUNK_SIZE);
                         }
+                        
                         break;
                     }
 
                     case CHUNK_SIZE:
                     {
-                        ch=buffer.get();
-                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                        ch=next(buffer);
+                        if (ch == HttpTokens.LINE_FEED)
                         {
-                            _eol=ch;
-
                             if (_chunkLength == 0)
                             {
-                                if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
-                                    _eol=buffer.get();
                                 setState(State.END);
                                 if (_handler.messageComplete())
                                     return true;
@@ -1203,27 +1263,18 @@
                         }
                         else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
                             setState(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);
+                        else 
+                            _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
                         break;
                     }
 
                     case CHUNK_PARAMS:
                     {
-                        ch=buffer.get();
-                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                        ch=next(buffer);
+                        if (ch == HttpTokens.LINE_FEED)
                         {
-                            _eol=ch;
                             if (_chunkLength == 0)
                             {
-                                if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
-                                    _eol=buffer.get();
                                 setState(State.END);
                                 if (_handler.messageComplete())
                                     return true;
@@ -1262,6 +1313,9 @@
                         BufferUtil.clear(buffer);
                         return false;
                     }
+                    
+                    default: 
+                        break;
                 }
             }
 
@@ -1340,8 +1394,19 @@
             case CLOSED:
             case END:
                 break;
+                
+            case EOF_CONTENT:
+                _handler.messageComplete();
+                break;
+                
             default:
-                LOG.warn("Closing {}",this);
+                if (_state.ordinal()>State.END.ordinal())
+                {
+                    _handler.earlyEOF();
+                    _handler.messageComplete();
+                }
+                else
+                    LOG.warn("Closing {}",this);
         }
         setState(State.CLOSED);
         _endOfContent=EndOfContent.UNKNOWN_CONTENT;
@@ -1369,6 +1434,7 @@
     /* ------------------------------------------------------------------------------- */
     private void setState(State state)
     {
+        // LOG.debug("{} --> {}",_state,state);
         _state=state;
     }
 
@@ -1405,8 +1471,18 @@
          */
         public boolean parsedHeader(HttpField field);
 
-        public boolean earlyEOF();
+        /* ------------------------------------------------------------ */
+        /** Called to signal that an EOF was received unexpectedly
+         * during the parsing of a HTTP message
+         * @return True if the parser should return to its caller
+         */
+        public void earlyEOF();
 
+        /* ------------------------------------------------------------ */
+        /** 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);
         
         /* ------------------------------------------------------------ */
@@ -1443,5 +1519,9 @@
         public abstract boolean startResponse(HttpVersion version, int status, String reason);
     }
 
+    public Trie<HttpField> getFieldCache()
+    {
+        return _connectionFields;
+    }
 
 }
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
index 24f2e7a..b14221d 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java
@@ -138,9 +138,8 @@
         }
 
         @Override
-        public boolean earlyEOF()
+        public void earlyEOF()
         {
-            return true;
         }
 
         @Override
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 e3d13d5..d54efff 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,13 +25,12 @@
 {
     // 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 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 f7297f2..dcd00cf 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
@@ -546,6 +546,8 @@
     {
         if (_host==_port)
             return null;
+        if (_raw[_host]=='[')
+            return new String(_raw,_host+1,_port-_host-2,_charset);
         return new String(_raw,_host,_port-_host,_charset);
     }
 
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
index 7ca440f..262c0ac 100644
--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java
@@ -51,9 +51,8 @@
         }
 
         @Override
-        public boolean earlyEOF()
+        public void earlyEOF()
         {
-            return true;
         }
 
         @Override
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 16651a9..b65e10a 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
@@ -23,6 +23,8 @@
 import static org.junit.Assert.assertTrue;
 
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.eclipse.jetty.http.HttpParser.State;
 import org.eclipse.jetty.util.BufferUtil;
@@ -50,11 +52,13 @@
             throw new IllegalStateException("!START");
 
         // continue parsing
-        while (!parser.isState(State.END) && buffer.hasRemaining())
+        int remaining=buffer.remaining();
+        while (!parser.isState(State.END) && remaining>0)
         {
-            int remaining=buffer.remaining();
+            int was_remaining=remaining;
             parser.parseNext(buffer);
-            if (remaining==buffer.remaining())
+            remaining=buffer.remaining();
+            if (remaining==was_remaining)
                 break;
         }
     }
@@ -64,8 +68,8 @@
     {
         ByteBuffer buffer= BufferUtil.toBuffer("POST /foo HTTP/1.0\015\012" + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
         assertEquals("POST", _methodOrVersion);
         assertEquals("/foo", _uriOrStatus);
@@ -79,8 +83,8 @@
         ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012");
 
         _versionOrReason= null;
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
         assertEquals("GET", _methodOrVersion);
         assertEquals("/999", _uriOrStatus);
@@ -94,8 +98,8 @@
         ByteBuffer buffer= BufferUtil.toBuffer("POST /222  \015\012");
 
         _versionOrReason= null;
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
         assertEquals("POST", _methodOrVersion);
         assertEquals("/222", _uriOrStatus);
@@ -108,8 +112,8 @@
     {
         ByteBuffer buffer= BufferUtil.toBuffer("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET);
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
         assertEquals("POST", _methodOrVersion);
         assertEquals("/fo\u0690", _uriOrStatus);
@@ -122,8 +126,8 @@
     {
         ByteBuffer buffer= BufferUtil.toBuffer("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET);
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
         assertEquals("POST", _methodOrVersion);
         assertEquals("/foo?param=\u0690", _uriOrStatus);
@@ -136,8 +140,8 @@
     {
         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");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        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);
@@ -149,10 +153,9 @@
     public void testConnect() throws Exception
     {
         ByteBuffer buffer= BufferUtil.toBuffer("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012");
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
-        assertTrue(handler.request);
         assertEquals("CONNECT", _methodOrVersion);
         assertEquals("192.168.1.2:80", _uriOrStatus);
         assertEquals("HTTP/1.1", _versionOrReason);
@@ -182,8 +185,8 @@
         BufferUtil.put(b0,buffer);
         BufferUtil.flipToFlush(buffer,pos);
         
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
 
         assertEquals("GET", _methodOrVersion);
@@ -230,8 +233,8 @@
                         "Accept-Encoding: gzip, deflated\015\012" +
                         "Accept: unknown\015\012" +
                 "\015\012");
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
 
         assertEquals("GET", _methodOrVersion);
@@ -260,6 +263,8 @@
         assertEquals(9, _h);
     }
 
+    
+    
     @Test
     public void testHeaderParseLF() throws Exception
     {
@@ -278,8 +283,8 @@
                         "Accept-Encoding: gzip, deflated\n" +
                         "Accept: unknown\n" +
                 "\n");
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
 
         assertEquals("GET", _methodOrVersion);
@@ -328,9 +333,10 @@
 
         for (int i=0;i<buffer.capacity()-4;i++)
         {
-            Handler handler = new Handler();
-            HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+            HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+            HttpParser parser= new HttpParser(handler);
 
+            // System.err.println(BufferUtil.toDetailString(buffer));
             buffer.position(2);
             buffer.limit(2+i);
 
@@ -377,8 +383,8 @@
                         + "1a\015\012"
                         + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
                         + "0\015\012");
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parseAll(parser,buffer);
 
         assertEquals("GET", _methodOrVersion);
@@ -421,8 +427,8 @@
                         + "0123456789\015\012");
 
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("GET", _methodOrVersion);
         assertEquals("/mp", _uriOrStatus);
@@ -466,8 +472,8 @@
                         + "\015\012"
                         + "0123456789\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
         assertEquals("200", _uriOrStatus);
@@ -485,8 +491,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
         assertEquals("304", _uriOrStatus);
@@ -509,8 +515,8 @@
                         + "\015\012"
                         + "0123456789\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
         assertEquals("204", _uriOrStatus);
@@ -542,8 +548,8 @@
                         + "\015\012"
                         + "0123456789\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
         assertEquals("200", _uriOrStatus);
@@ -563,8 +569,8 @@
                         + "\015\012"
                         + "0123456789\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
         assertEquals("200", _uriOrStatus);
@@ -582,8 +588,8 @@
                         + "Content-Length: 10\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
         assertEquals("304", _uriOrStatus);
@@ -601,8 +607,8 @@
                         + "Transfer-Encoding: chunked\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
         assertEquals("101", _uriOrStatus);
@@ -624,8 +630,8 @@
                         + "HTTP/1.1 400 OK\015\012");  // extra data causes close
 
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals("HTTP/1.1", _methodOrVersion);
@@ -647,8 +653,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals(null,_methodOrVersion);
@@ -667,8 +673,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals(null,_methodOrVersion);
@@ -686,8 +692,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals(null,_methodOrVersion);
@@ -705,8 +711,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        HttpParser.ResponseHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals(null,_methodOrVersion);
@@ -724,9 +730,9 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
-
+        HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
+        HttpParser parser= new HttpParser(handler);
+        
         parser.parseNext(buffer);
         assertEquals(null,_methodOrVersion);
         assertEquals("No Status",_bad);
@@ -743,8 +749,23 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
+        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()); 
+        
+        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);
@@ -752,6 +773,42 @@
         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());
+    }
+    
+    
+    
 
     @Test
     public void testBadContentLength0() throws Exception
@@ -762,8 +819,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals("GET",_methodOrVersion);
@@ -781,8 +838,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals("GET",_methodOrVersion);
@@ -800,8 +857,8 @@
                         + "Connection: close\015\012"
                         + "\015\012");
 
-        Handler handler = new Handler();
-        HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
+        HttpParser.RequestHandler<ByteBuffer> handler  = new Handler();
+        HttpParser parser= new HttpParser(handler);
 
         parser.parseNext(buffer);
         assertEquals("GET",_methodOrVersion);
@@ -809,7 +866,154 @@
         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 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");
+        
+        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));
+        
+    }
 
     @Before
     public void init()
@@ -826,11 +1030,14 @@
         _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 _h;
@@ -858,6 +1065,7 @@
         @Override
         public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
         {
+            _fields.clear();
             request=true;
             _h= -1;
             _hdr= new String[10];
@@ -875,6 +1083,7 @@
         @Override
         public boolean parsedHeader(HttpField field)
         {
+            _fields.add(field);
             //System.err.println("header "+name+": "+value);
             _hdr[++_h]= field.getName();
             _val[_h]= field.getValue();
@@ -884,7 +1093,8 @@
         @Override
         public boolean parsedHostHeader(String host,int port)
         {
-            // TODO test this
+            _host=host;
+            _port=port;
             return false;
         }
 
@@ -921,6 +1131,7 @@
         @Override
         public boolean startResponse(HttpVersion version, int status, String reason)
         {
+            _fields.clear();
             request=false;
             _methodOrVersion = version.asString();
             _uriOrStatus = Integer.toString(status);
@@ -936,9 +1147,8 @@
         }
 
         @Override
-        public boolean earlyEOF()
+        public void earlyEOF()
         {
-            return true;
         }
 
         @Override
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 6262efd..52eb707 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 @@
 /* ------------------------------------------------------------ */
 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);
+        for (String[] test:tests)
+        {
+            HttpURI uri = new HttpURI(test[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());
+            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));
+        for (String[] test:tests)
+        {
+            HttpURI uri = new HttpURI(new URI(test[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());
+            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-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
index a0b090c..3fc9bef 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
@@ -24,6 +24,7 @@
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.jetty.util.BlockingCallback;
 import org.eclipse.jetty.util.Callback;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -112,13 +113,130 @@
                     if (_state.compareAndSet(State.FILLING,State.FILLING_INTERESTED))
                         break loop;
                     break;
+                    
+                case FILLING_BLOCKED:
+                    if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING_BLOCKED_INTERESTED))
+                        break loop;
+                    break;
+                    
+                case BLOCKED:
+                    if (_state.compareAndSet(State.BLOCKED,State.BLOCKED_INTERESTED))
+                        break loop;
+                    break;
 
+                case FILLING_BLOCKED_INTERESTED:
+                case FILLING_INTERESTED:
+                case BLOCKED_INTERESTED:
+                case INTERESTED:
+                    break loop;
+            }
+        }
+    }
+    
+
+    private void unblock()
+    {
+        LOG.debug("unblock {}",this);
+
+        loop:while(true)
+        {
+            switch(_state.get())
+            {
+                case FILLING_BLOCKED:
+                    if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING))
+                        break loop;
+                    break;
+                    
+                case FILLING_BLOCKED_INTERESTED:
+                    if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.FILLING_INTERESTED))
+                        break loop;
+                    break;
+                    
+                case BLOCKED_INTERESTED:
+                    if (_state.compareAndSet(State.BLOCKED_INTERESTED,State.INTERESTED))
+                    {
+                        getEndPoint().fillInterested(_readCallback);
+                        break loop;
+                    }
+                    break;
+                    
+                case BLOCKED:
+                    if (_state.compareAndSet(State.BLOCKED,State.IDLE))
+                        break loop;
+                    break;
+
+                case FILLING:
+                case IDLE:
                 case FILLING_INTERESTED:
                 case INTERESTED:
                     break loop;
             }
         }
     }
+    
+    
+    /**
+     */
+    protected void block(final BlockingCallback callback)
+    {
+        LOG.debug("block {}",this);
+        
+        final Callback blocked=new Callback()
+        {
+            @Override
+            public void succeeded()
+            {
+                unblock();
+                callback.succeeded();
+            }
+
+            @Override
+            public void failed(Throwable x)
+            {
+                unblock();
+                callback.failed(x);                
+            }
+        };
+
+        loop:while(true)
+        {
+            switch(_state.get())
+            {
+                case IDLE:
+                    if (_state.compareAndSet(State.IDLE,State.BLOCKED))
+                    {
+                        getEndPoint().fillInterested(blocked);
+                        break loop;
+                    }
+                    break;
+
+                case FILLING:
+                    if (_state.compareAndSet(State.FILLING,State.FILLING_BLOCKED))
+                    {
+                        getEndPoint().fillInterested(blocked);
+                        break loop;
+                    }
+                    break;
+                    
+                case FILLING_INTERESTED:
+                    if (_state.compareAndSet(State.FILLING_INTERESTED,State.FILLING_BLOCKED_INTERESTED))
+                    {
+                        getEndPoint().fillInterested(blocked);
+                        break loop;
+                    }
+                    break;
+
+                case BLOCKED:
+                case BLOCKED_INTERESTED:
+                case FILLING_BLOCKED:
+                case FILLING_BLOCKED_INTERESTED:
+                    throw new IllegalStateException("Already Blocked");
+                    
+                case INTERESTED:
+                    throw new IllegalStateException();
+            }
+        }
+    }
 
     /**
      * <p>Callback method invoked when the endpoint is ready to be read.</p>
@@ -225,7 +343,7 @@
 
     private enum State
     {
-        IDLE, INTERESTED, FILLING, FILLING_INTERESTED
+        IDLE, INTERESTED, FILLING, FILLING_INTERESTED, FILLING_BLOCKED, BLOCKED, FILLING_BLOCKED_INTERESTED, BLOCKED_INTERESTED
     }
     
     private class ReadCallback implements Callback, Runnable
@@ -247,12 +365,25 @@
                         {
                             case IDLE:
                             case INTERESTED:
-                                throw new IllegalStateException();
+                            case BLOCKED:
+                            case BLOCKED_INTERESTED:
+                                LOG.warn(new IllegalStateException());
+                                return;
 
                             case FILLING:
                                 if (_state.compareAndSet(State.FILLING,State.IDLE))
                                     break loop;
                                 break;
+                                
+                            case FILLING_BLOCKED:
+                                if (_state.compareAndSet(State.FILLING_BLOCKED,State.BLOCKED))
+                                    break loop;
+                                break;
+                                
+                            case FILLING_BLOCKED_INTERESTED:
+                                if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.BLOCKED_INTERESTED))
+                                    break loop;
+                                break;
 
                             case FILLING_INTERESTED:
                                 if (_state.compareAndSet(State.FILLING_INTERESTED,State.INTERESTED))
@@ -266,7 +397,7 @@
                 }
             }
             else
-                LOG.warn(new Throwable());
+                LOG.warn(new IllegalStateException());
         }
         
         @Override
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
index 317c55e..f8b1327 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java
@@ -35,7 +35,6 @@
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -167,20 +166,6 @@
         selector.submit(selector.new Accept(channel));
     }
 
-    /**
-     * <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)}.</p>
-     *
-     * @param channel the channel to register
-     * @param attachment An attachment to be passed via the selection key to the {@link SelectorManager#newConnection(SocketChannel, EndPoint, Object)} method.
-     */
-    public void accept(final SocketChannel channel, Object attachment)
-    {
-        final ManagedSelector selector = chooseSelector();
-        selector.submit(selector.new Accept(channel, attachment));
-    }
-    
     @Override
     protected void doStart() throws Exception
     {
@@ -333,7 +318,7 @@
     public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
     {
         private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
-        
+
         private final int _id;
         private Selector _selector;
         private volatile Thread _thread;
@@ -699,18 +684,10 @@
         private class Accept implements Runnable
         {
             private final SocketChannel _channel;
-            private final Object _attachment;
 
             public Accept(SocketChannel channel)
             {
                 this._channel = channel;
-                this._attachment = null;
-            }
-            
-            public Accept(SocketChannel channel, Object attachment)
-            {
-                this._channel = channel;
-                this._attachment = attachment;
             }
 
             @Override
@@ -718,7 +695,7 @@
             {
                 try
                 {
-                    SelectionKey key = _channel.register(_selector, 0, _attachment);
+                    SelectionKey key = _channel.register(_selector, 0, null);
                     EndPoint endpoint = createEndPoint(_channel, key);
                     key.attach(endpoint);
                 }
diff --git a/jetty-jaas/src/main/config/etc/jetty-jaas.xml b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
index e399478..7494898 100644
--- a/jetty-jaas/src/main/config/etc/jetty-jaas.xml
+++ b/jetty-jaas/src/main/config/etc/jetty-jaas.xml
@@ -11,7 +11,7 @@
     <!-- ======================================================== -->
     <Call class="java.lang.System" name="setProperty">
       <Arg>java.security.auth.login.config</Arg>
-      <Arg><SystemProperty name="jetty.home" default="." />/etc/login.conf</Arg>
+      <Arg><Property name="jetty.home" default="." />/<Property name="jaas.login.conf" default="etc/login.conf"/></Arg>
     </Call>
 
 </Configure>
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
index 9429512..558899c 100644
--- 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
@@ -284,7 +284,7 @@
     /**
      * A wrapper for the Server object
      */
-    protected JettyServer server = JettyServer.getInstance();
+    protected JettyServer server = new JettyServer();
     
     
     /**
@@ -494,6 +494,8 @@
                     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);
             }
 
@@ -504,12 +506,13 @@
                 //if <httpConnector> not configured in the pom, create one
                 if (httpConnector == null)
                 {
-                    httpConnector = new MavenServerConnector();
+                    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});
             }
 
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
index 5be4aaf..8018052 100644
--- 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
@@ -38,16 +38,6 @@
 {
     public static final JettyServer __instance = new JettyServer();
     
-    /**
-     * Singleton instance
-     * @return
-     */
-    public static JettyServer getInstance()
-    {
-        return __instance;
-    }
-
-    
     private RequestLog requestLog;
     private ContextHandlerCollection contexts;
     
@@ -56,7 +46,7 @@
     /**
      * 
      */
-    private JettyServer()
+    public JettyServer()
     {
         super();
         setStopAtShutdown(true);
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
index f7c20c5..54aedb1 100644
--- 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
@@ -19,21 +19,259 @@
 
 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 ServerConnector
+public class MavenServerConnector extends AbstractLifeCycle implements Connector
 {
     public static int DEFAULT_PORT = 8080;
     public static String DEFAULT_PORT_STR = String.valueOf(DEFAULT_PORT);   
     public static 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()
     {
-        super(JettyServer.getInstance());
+    }
+    
+    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 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/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
index 649b847..4d56d0f 100644
--- 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
@@ -55,7 +55,7 @@
     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;
+    private JettyServer server = new JettyServer();
     private JettyWebAppContext webApp;
 
     
@@ -120,8 +120,6 @@
     {
         LOG.debug("Starting Jetty Server ...");
 
-        this.server = JettyServer.getInstance();
-
         //apply any configs from jetty.xml files first 
         applyJettyXml ();
 
@@ -132,6 +130,7 @@
         {
             //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};
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 ac00de7..b078f08 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
@@ -149,7 +149,7 @@
             }
             else
             {
-                version = new Long(((Long)version).intValue() + 1);
+                version = new Long(((Number)version).longValue() + 1);
                 update.put("$inc",__version_1); 
             }
 
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 a89ff5b..f20838b 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
@@ -236,7 +236,7 @@
                 // can define their own configuration.
                 if ((enUrls == null || !enUrls.hasMoreElements()))
                 {
-                    String tmp = DEFAULT_JETTYHOME+etcFile;
+                    String tmp = DEFAULT_JETTYHOME+(DEFAULT_JETTYHOME.endsWith("/")?"":"/")+etcFile;
                     enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, tmp);                    
                     LOG.info("Configuring jetty from bundle: "
                                        + configurationBundle.getSymbolicName()
diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml
index 66af9e4..58184f8 100644
--- a/jetty-osgi/test-jetty-osgi/pom.xml
+++ b/jetty-osgi/test-jetty-osgi/pom.xml
@@ -95,7 +95,7 @@
         <version>${exam.version}</version>
         <scope>test</scope>
     </dependency>
-     
+
     <dependency>
         <groupId>org.ops4j.pax.runner</groupId>
         <artifactId>pax-runner-no-jcl</artifactId>
@@ -141,12 +141,14 @@
         <scope>test</scope>
     </dependency>
     <!-- For sane logging -->
+<!--
     <dependency>
       <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-simple</artifactId>
+      <artifactId>slf4j-log4j12</artifactId>
       <version>1.6.1</version>
       <scope>test</scope>
     </dependency>
+-->
     <!-- Orbit Servlet Deps -->
     <dependency>
       <groupId>org.eclipse.jetty.orbit</groupId>
@@ -258,7 +260,7 @@
       <version>${project.version}</version>
       <scope>runtime</scope>
     </dependency>
-    
+
     <dependency>
       <groupId>org.eclipse.jetty.spdy</groupId>
       <artifactId>spdy-core</artifactId>
@@ -296,7 +298,7 @@
       <scope>test</scope>
     </dependency>
 
-    
+
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-plus</artifactId>
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
index dc850f2..b15e002 100644
--- 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
@@ -78,17 +78,21 @@
         // to pick up and deploy
         options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-context").versionAsInProject().start());
 
+        String logLevel = "WARN";
         // 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"))));
-        }
+            logLevel = "INFO";
+        
+
+        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(logLevel),
+                                             systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
+
 
         return options.toArray(new Option[options.size()]);
     }
@@ -142,12 +146,14 @@
         ServiceReference[] refs = bundleContext.getServiceReferences(ContextHandler.class.getName(), null);
         Assert.assertNotNull(refs);
         Assert.assertEquals(1, refs.length);
-        String[] keys = refs[0].getPropertyKeys();
+        //uncomment for debugging
+       /*  
+       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());
 
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
index 8ffde85..01eb167 100644
--- 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
@@ -53,7 +53,6 @@
     public Option[] config()
     {
         VersionResolver resolver = MavenUtils.asInProject();
-        System.err.println(resolver.getVersion("org.eclipse.jetty", "jetty-server"));
         ArrayList<Option> options = new ArrayList<Option>();
         TestOSGiUtil.addMoreOSGiContainers(options);
         options.addAll(provisionCoreJetty());
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
index 3d6b211..ee104b7 100644
--- 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
@@ -19,9 +19,13 @@
 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;
@@ -41,7 +45,8 @@
 @RunWith(JUnit4TestRunner.class)
 public class TestJettyOSGiBootSpdy
 {
-
+    private static final boolean LOGGING_ENABLED = false;
+    
     private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
 
     private static final int DEFAULT_JETTY_SPDY_PORT = 9877;
@@ -61,6 +66,22 @@
         options.add(CoreOptions.junitBundles());
         options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
         options.addAll(spdyJettyDependencies());
+        
+        String logLevel = "WARN";
+        
+        // Enable Logging
+        if (LOGGING_ENABLED)
+            logLevel = "INFO";
+        
+
+        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(logLevel),
+                                             systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
         return options.toArray(new Option[options.size()]);
     }
 
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
index 4ed58cb..f6e04f5 100644
--- 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
@@ -77,18 +77,20 @@
         options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
         options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
         options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
-
-        // Enable Logging
+        
+        String logLevel = "WARN";
         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"))));
-        }
+            logLevel = "INFO";
+        
+
+        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(logLevel),
+                                             systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
 
         options.addAll(jspDependencies());
         return options.toArray(new Option[options.size()]);
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
index f8521b9..cd4b5b2 100644
--- 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
@@ -52,7 +52,7 @@
 @RunWith(JUnit4TestRunner.class)
 public class TestJettyOSGiBootWithJsp
 {
-    private static final boolean LOGGING_ENABLED = true;
+    private static final boolean LOGGING_ENABLED = false;
 
     private static final boolean REMOTE_DEBUGGING = false;
 
@@ -72,18 +72,21 @@
         options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
         options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
 
+        String logLevel = "WARN";
+        
         // Enable Logging
         if (LOGGING_ENABLED)
-        {
+            logLevel = "INFO";
+ 
             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"))));
-        }
-
+                                                 systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value(logLevel),
+                                                 systemProperty("org.eclipse.jetty.LEVEL").value(logLevel))));
+     
         options.addAll(jspDependencies());
 
         // Remote JDWP Debugging, this won't work with the forked container.
@@ -119,7 +122,6 @@
                 + etc
                 + "/jetty-testrealm.xml";
         options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS).value(xmlConfigs));
-        System.err.println(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS+"="+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;
@@ -163,10 +165,6 @@
     @Test
     public void testJspDump() throws Exception
     {
-
-        // System.err.println("http://127.0.0.1:9876/jsp/dump.jsp  sleeping....");
-        // Thread.currentThread().sleep(5000000);
-        // now test the jsp/dump.jsp
         HttpClient client = new HttpClient();
         try
         {
@@ -175,7 +173,6 @@
             Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
 
             String content = new String(response.getContent());
-            System.err.println("content: " + content);
             Assert.assertTrue(content.contains("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>"));
         }
         finally
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-rewrite/src/main/config/etc/jetty-rewrite.xml b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
index fd0a891..e13a162 100644
--- a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
+++ b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
@@ -16,101 +16,9 @@
     <Set name="handler">
      <New id="Rewrite" class="org.eclipse.jetty.rewrite.handler.RewriteHandler">
       <Set name="handler"><Ref refid="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">/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>
-
+      <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>
 
diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml
index 0f6dff6..3ddb4ca 100644
--- a/jetty-runner/pom.xml
+++ b/jetty-runner/pom.xml
@@ -27,9 +27,7 @@
             </goals>
             <configuration>
               <includes>**</includes>
-              <excludes>**/MANIFEST.MF</excludes>
-              <excludes>**/ECLIPSEF.RSA</excludes>
-              <excludes>**/ECLIPSEF.SF</excludes>
+              <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>
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 7878479..fd9a415 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
@@ -551,23 +551,20 @@
     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);
     }
-    
+
     
     /* ------------------------------------------------------------ */
     /**
@@ -630,7 +627,7 @@
      * <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>Mappings with keys of the form "&lt;method&gt;.&lt;method&gt;.&lt;method&gt;.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)
@@ -659,7 +656,7 @@
                 //(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());
                 }
                 
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 2dcb0d0..0fe3747 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
@@ -21,11 +21,11 @@
 import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.SecureRandom;
+import java.util.BitSet;
 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;
@@ -53,24 +53,40 @@
  * @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"
+ * 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
 {
     private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
     SecureRandom _random = new SecureRandom();
     private long _maxNonceAgeMs = 60*1000;
-    private ConcurrentMap<String, Nonce> _nonceCount = new ConcurrentHashMap<String, Nonce>();
+    private int _maxNC=1024;
+    private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
     private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
     private static class Nonce
     {
         final String _nonce;
         final long _ts;
-        AtomicInteger _nc=new AtomicInteger();
-        public Nonce(String nonce, long ts)
+        final BitSet _seen; 
+
+        public Nonce(String nonce, long ts, int size)
         {
             _nonce=nonce;
             _ts=ts;
+            _seen = new BitSet(size);
+        }
+
+        public boolean seen(int count)
+        {
+            synchronized (this)
+            {
+                if (count>=_seen.size())
+                    return true;
+                boolean s=_seen.get(count);
+                _seen.set(count);
+                return s;
+            }
         }
     }
 
@@ -92,11 +108,31 @@
         String mna=configuration.getInitParameter("maxNonceAge");
         if (mna!=null)
         {
-            synchronized (this)
-            {
-                _maxNonceAgeMs=Long.valueOf(mna);
-            }
+            _maxNonceAgeMs=Long.valueOf(mna);
         }
+        String mnc=configuration.getInitParameter("maxNonceCount");
+        if (mnc!=null)
+        {
+            _maxNC=Integer.valueOf(mnc);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxNonceCount()
+    {
+        return _maxNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxNonceCount(int maxNC)
+    {
+        _maxNC = maxNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getMaxNonceAge()
+    {
+        return _maxNonceAgeMs;
     }
 
     /* ------------------------------------------------------------ */
@@ -238,9 +274,9 @@
             byte[] nounce = new byte[24];
             _random.nextBytes(nounce);
 
-            nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp());
+            nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
         }
-        while (_nonceCount.putIfAbsent(nonce._nonce,nonce)!=null);
+        while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
         _nonceQueue.add(nonce);
 
         return nonce._nonce;
@@ -255,34 +291,27 @@
     private int checkNonce(Digest digest, Request request)
     {
         // firstly let's expire old nonces
-        long expired;
-        synchronized (this)
-        {
-            expired = request.getTimeStamp()-_maxNonceAgeMs;
-        }
-
+        long expired = request.getTimeStamp()-_maxNonceAgeMs;
         Nonce nonce=_nonceQueue.peek();
         while (nonce!=null && nonce._ts<expired)
         {
             _nonceQueue.remove(nonce);
-            _nonceCount.remove(nonce._nonce);
+            _nonceMap.remove(nonce._nonce);
             nonce=_nonceQueue.peek();
         }
 
-
+        // Now check the requested nonce
         try
         {
-            nonce = _nonceCount.get(digest.nonce);
+            nonce = _nonceMap.get(digest.nonce);
             if (nonce==null)
                 return 0;
 
             long count = Long.parseLong(digest.nc,16);
-            if (count>Integer.MAX_VALUE)
+            if (count>=_maxNC)
                 return 0;
-            int old=nonce._nc.get();
-            while (!nonce._nc.compareAndSet(old,(int)count))
-                old=nonce._nc.get();
-            if (count<=old)
+            
+            if (nonce.seen((int)count))
                 return -1;
 
             return 1;
@@ -383,6 +412,7 @@
             return false;
         }
 
+        @Override
         public String toString()
         {
             return username + "," + response;
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 abbfe73..a1480b4 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
@@ -25,6 +25,7 @@
 import static org.junit.matchers.JUnitMatchers.containsString;
 
 import java.io.IOException;
+import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -33,12 +34,15 @@
 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.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 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;
@@ -52,7 +56,10 @@
 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.junit.After;
 import org.junit.Before;
@@ -205,7 +212,6 @@
     @Test
     public void testBasic() throws Exception
     {
-        
         List<ConstraintMapping> list = new ArrayList<ConstraintMapping>(_security.getConstraintMappings());
         
         Constraint constraint6 = new Constraint();
@@ -250,14 +256,11 @@
         _server.start();
 
         String response;
-        /*
         response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
         assertThat(response,startsWith("HTTP/1.1 200 OK"));
-*/
    
         response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
         assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
-        /*
         response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
         assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
         assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
@@ -272,8 +275,7 @@
                 "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
                 "\r\n");
         assertThat(response,startsWith("HTTP/1.1 200 OK"));
-*/
-/*
+        
         // test admin
         response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
         assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
@@ -304,28 +306,159 @@
         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"));
+        assertThat(response,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"));
+        assertThat(response,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"));
+        assertThat(response,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"));*/
+        assertThat(response,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
+    {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] ha1;
+        // calc A1 digest
+        md.update(username.getBytes(StringUtil.__ISO_8859_1));
+        md.update((byte) ':');
+        md.update("TestRealm".getBytes(StringUtil.__ISO_8859_1));
+        md.update((byte) ':');
+        md.update(password.getBytes(StringUtil.__ISO_8859_1));
+        ha1 = md.digest();
+        // calc A2 digest
+        md.reset();
+        md.update("GET".getBytes(StringUtil.__ISO_8859_1));
+        md.update((byte) ':');
+        md.update(uri.getBytes(StringUtil.__ISO_8859_1));
+        byte[] ha2 = md.digest();
+
+        // calc digest
+        // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
+        // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
+        // <">
+        // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
+        // ) > <">
+
+        md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
+        md.update((byte) ':');
+        md.update(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("auth".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 TypeUtil.toString(digest, 16);
     }
     
-    
+    @Test
+    public void testDigest() throws Exception
+    {
+        DigestAuthenticator authenticator = new DigestAuthenticator();
+        authenticator.setMaxNonceCount(5);
+        _security.setAuthenticator(authenticator);
+        _security.setStrict(false);
+        _server.start();
+
+        String response;
+        response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+   
+        response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
+        assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
+        response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
+        assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+        assertThat(response,containsString("WWW-Authenticate: Digest realm=\"TestRealm\""));
+
+        Pattern nonceP = Pattern.compile("nonce=\"([^\"]*)\",");
+        Matcher matcher = nonceP.matcher(response);
+        assertTrue(matcher.find());
+        String nonce=matcher.group(1);
+        
+        
+        //wrong password
+        String digest= digest(nonce,"user","WRONG","/ctx/auth/info","1");
+        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=1, "+
+            "nonce=\""+nonce+"\", "+
+            "response=\""+digest+"\"\r\n"+
+            "\r\n");
+        assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+        
+        // right password
+        digest= digest(nonce,"user","password","/ctx/auth/info","2");
+        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=2, "+
+            "nonce=\""+nonce+"\", "+
+            "response=\""+digest+"\"\r\n"+
+            "\r\n");
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+        
+
+        // once only
+        digest= digest(nonce,"user","password","/ctx/auth/info","2");
+        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=2, "+
+            "nonce=\""+nonce+"\", "+
+            "response=\""+digest+"\"\r\n"+
+            "\r\n");
+        assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+
+        // increasing
+        digest= digest(nonce,"user","password","/ctx/auth/info","4");
+        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=4, "+
+            "nonce=\""+nonce+"\", "+
+            "response=\""+digest+"\"\r\n"+
+            "\r\n");
+        assertThat(response,startsWith("HTTP/1.1 200 OK"));
+        
+        // out of order
+        digest= digest(nonce,"user","password","/ctx/auth/info","3");
+        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=3, "+
+            "nonce=\""+nonce+"\", "+
+            "response=\""+digest+"\"\r\n"+
+            "\r\n");
+        assertThat(response,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");
+        assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
+        assertThat(response,containsString("stale=true"));
+    }
+
 
     @Test
     public void testFormDispatch() throws Exception
@@ -863,32 +996,32 @@
         String response;
 
         response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
-        assertTrue(response.startsWith("HTTP/1.1 403"));
+        assertThat(response,startsWith("HTTP/1.1 403"));
         
         _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"));
+        assertThat(response,startsWith("HTTP/1.1 302 Found"));
         assertTrue(response.indexOf("Location") > 0);
         assertTrue(response.indexOf(":8443/ctx/data/info") > 0);
 
         _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"));
+        assertThat(response,startsWith("HTTP/1.1 302 Found"));
         assertTrue(response.indexOf("Location") > 0);
         assertTrue(response.indexOf(":443/ctx/data/info") < 0);
 
         _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"));
+        assertThat(response,startsWith("HTTP/1.1 302 Found"));
         assertTrue(response.indexOf("Location") > 0);
         assertTrue(response.indexOf("https://wobble.com:8443/ctx/data/info") > 0);
 
         _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"));
+        assertThat(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);
diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml
index 731304e..7ae3064 100644
--- a/jetty-server/src/main/config/etc/jetty-http.xml
+++ b/jetty-server/src/main/config/etc/jetty-http.xml
@@ -33,7 +33,7 @@
         </Arg>
         <Set name="host"><Property name="jetty.host" /></Set>
         <Set name="port"><Property name="jetty.port" default="8080" /></Set>
-        <Set name="idleTimeout">30000</Set>
+        <Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
       </New>
     </Arg>
   </Call>
diff --git a/jetty-server/src/main/config/etc/jetty-lowresources.xml b/jetty-server/src/main/config/etc/jetty-lowresources.xml
index 5b264f1..060919a 100644
--- a/jetty-server/src/main/config/etc/jetty-lowresources.xml
+++ b/jetty-server/src/main/config/etc/jetty-lowresources.xml
@@ -10,12 +10,12 @@
     <Arg>
       <New class="org.eclipse.jetty.server.LowResourceMonitor">
         <Arg name="server"><Ref refid='Server'/></Arg>
-        <Set name="period">1000</Set>
-        <Set name="lowResourcesIdleTimeout">200</Set>
-        <Set name="monitorThreads">true</Set>
-        <Set name="maxConnections">0</Set>
-        <Set name="maxMemory">0</Set>
-        <Set name="maxLowResourcesTime">5000</Set>
+        <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>
diff --git a/jetty-server/src/main/config/etc/jetty-requestlog.xml b/jetty-server/src/main/config/etc/jetty-requestlog.xml
index 6e6eb05..2131777 100644
--- a/jetty-server/src/main/config/etc/jetty-requestlog.xml
+++ b/jetty-server/src/main/config/etc/jetty-requestlog.xml
@@ -17,9 +17,9 @@
             <New id="RequestLogImpl" class="org.eclipse.jetty.server.AsyncNCSARequestLog">
               <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">true</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">false</Set>
               <Set name="LogTimeZone">GMT</Set>
             </New>
diff --git a/jetty-server/src/main/config/etc/jetty-stats.xml b/jetty-server/src/main/config/etc/jetty-stats.xml
index dbef0e2..2e7a57c 100644
--- a/jetty-server/src/main/config/etc/jetty-stats.xml
+++ b/jetty-server/src/main/config/etc/jetty-stats.xml
@@ -12,4 +12,7 @@
       <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.xml b/jetty-server/src/main/config/etc/jetty.xml
index 668980e..a551326 100644
--- a/jetty-server/src/main/config/etc/jetty.xml
+++ b/jetty-server/src/main/config/etc/jetty.xml
@@ -44,9 +44,9 @@
     <!-- =========================================================== -->
     <Arg name="threadpool">
       <New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
-        <Arg name="minThreads" type="int">10</Arg>
-        <Arg name="maxThreads" type="int">200</Arg>
-        <Arg name="idleTimeout" type="int">60000</Arg>
+        <Arg name="minThreads" type="int"><Property name="threads.min" default="10"/></Arg>
+        <Arg name="maxThreads" type="int"><Property name="threads.max" default="200"/></Arg>
+        <Arg name="idleTimeout" type="int"><Property name="threads.timeout" default="60000"/></Arg>
         <Set name="detailedDump">false</Set>
       </New>
     </Arg>
@@ -124,7 +124,7 @@
     <!-- =========================================================== -->
     <Set name="stopAtShutdown">true</Set>
     <Set name="stopTimeout">5000</Set>
-    <Set name="dumpAfterStart">false</Set>
-    <Set name="dumpBeforeStop">false</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/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..11d334d
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java
@@ -0,0 +1,153 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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
+{
+    final private Context _context;
+    final private AsyncContextState _asyncContext;
+    volatile HttpChannelState _state;
+    private ServletContext _dispatchContext;
+    private String _pathInContext;
+    private 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 _pathInContext;
+    }
+    
+    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 setDispatchTarget(ServletContext context, String path)
+    {
+        if (context!=null)
+            _dispatchContext=context;
+        if (path!=null)
+            _pathInContext=path;
+    }
+
+    
+    public void completed()
+    {
+        _asyncContext.reset();
+    }
+
+    public HttpChannelState getHttpChannelState()
+    {
+        return _state;
+    }
+
+}
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..1688ed6
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
@@ -0,0 +1,180 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+
+public class AsyncContextState implements AsyncContext
+{
+    volatile HttpChannelState _state;
+
+    public AsyncContextState(HttpChannelState state)
+    {
+        _state=state;
+    }
+    
+    private 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
+    {
+        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/ConnectorStatistics.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
index 5fb942e..56bb66d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectorStatistics.java
@@ -23,14 +23,22 @@
 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.
+ */
 @ManagedObject("Connector Statistics")
 public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
 {
@@ -52,78 +60,93 @@
         connectionClosed(System.currentTimeMillis()-connection.getCreatedTimeStamp(),connection.getMessagesIn(),connection.getMessagesOut());
     }
 
+    @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 duraton maximum in ms")
     public long getConnectionsDurationMax()
     {
         return _connectionDurationStats.getMax();
     }
 
+    @ManagedAttribute("Connection duraton mean in ms")
     public double getConnectionsDurationMean()
     {
         return _connectionDurationStats.getMean();
     }
 
+    @ManagedAttribute("Connection duraton standard deviation")
     public double getConnectionsDurationStdDev()
     {
         return _connectionDurationStats.getStdDev();
     }
 
+    @ManagedAttribute("Connection duraton total of all connections in ms")
     public long getConnectionsDurationTotal()
     {
         return _connectionDurationStats.getTotal();
     }
 
-    public int getConnectionsMessagesInMax()
-    {
-        return (int)_messagesIn.getMax();
-    }
-
-    public double getConnectionsMessagesInMean()
-    {
-        return _messagesIn.getMean();
-    }
-
-    public double getConnectionsMessagesInStdDev()
-    {
-        return _messagesIn.getStdDev();
-    }
-
-    public int getConnectionsOpen()
-    {
-        return (int)_connectionStats.getCurrent();
-    }
-
-    public int getConnectionsOpenMax()
-    {
-        return (int)_connectionStats.getMax();
-    }
-
+    @ManagedAttribute("Messages In for all connections")
     public int getMessagesIn()
     {
         return (int)_messagesIn.getTotal();
     }
 
+    @ManagedAttribute("Messages In per connection maximum")
+    public int getConnectionsMessagesInMax()
+    {
+        return (int)_messagesIn.getMax();
+    }
+
+    @ManagedAttribute("Messages In per connection mean")
+    public double getConnectionsMessagesInMean()
+    {
+        return _messagesIn.getMean();
+    }
+
+    @ManagedAttribute("Messages In per connection standard deviation")
+    public double getConnectionsMessagesInStdDev()
+    {
+        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("Connection statistics started ms since epoch")
     public long getStartedMillis()
     {
         long start = _startMillis.get();
@@ -141,6 +164,7 @@
     {
     }
 
+    @ManagedOperation("Reset the statistics")
     public void reset()
     {
         _startMillis.set(System.currentTimeMillis());
@@ -178,7 +202,6 @@
         }
     }
 
-
     @Override
     @ManagedOperation("dump thread state")
     public String dump()
@@ -192,4 +215,13 @@
         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());
+        }
+    }
 }
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
index 9fbbde7..5d82b78 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -40,8 +40,10 @@
 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.Next;
 import org.eclipse.jetty.server.handler.ErrorHandler;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.URIUtil;
@@ -216,6 +218,15 @@
     @Override
     public void run()
     {
+        handle();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the channel is ready to continue handling (ie it is not suspended)
+     */
+    public boolean handle()
+    {
         LOG.debug("{} handle enter", this);
 
         setCurrentHttpChannel(this);
@@ -227,104 +238,104 @@
             Thread.currentThread().setName(threadName + " - " + _uri);
         }
 
-        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.
+        HttpChannelState.Next next = _state.handling();
+        while (next==Next.CONTINUE && getServer().isRunning())
         {
-            // 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.
-            boolean handling = _state.handling();
-
-            while (handling && getServer().isRunning())
+            try
             {
-                try
-                {
-                    _request.setHandled(false);
-                    _response.getHttpOutput().reopen();
+                _request.setHandled(false);
+                _response.getHttpOutput().reopen();
 
-                    if (_state.isInitial())
+                if (_state.isInitial())
+                {
+                    _request.setTimeStamp(System.currentTimeMillis());
+                    _request.setDispatcherType(DispatcherType.REQUEST);
+
+                    for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
+                        customizer.customize(getConnector(),_configuration,_request);
+                    getServer().handle(this);
+                }
+                else
+                {
+                    if (_request.getHttpChannelState().isExpired())
                     {
-                        _request.setTimeStamp(System.currentTimeMillis());
-                        _request.setDispatcherType(DispatcherType.REQUEST);
-                        
-                        for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
-                            customizer.customize(getConnector(),_configuration,_request);
-                        getServer().handle(this);
+                        _request.setDispatcherType(DispatcherType.ERROR);
+                        _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+                        _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout");
+                        _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
+                        _response.setStatusWithReason(500,"Async Timeout");
                     }
                     else
-                    {
-                        if (_request.getHttpChannelState().isExpired())
-                        {
-                            _request.setDispatcherType(DispatcherType.ERROR);
-                            _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
-                            _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,"Async Timeout");
-                            _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
-                            _response.setStatusWithReason(500,"Async Timeout");
-                        }
-                        else
-                            _request.setDispatcherType(DispatcherType.ASYNC);
-                        getServer().handleAsync(this);
-                    }
-                }
-                catch (Error e)
-                {
-                    if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
-                        LOG.ignore(e);
-                    else 
-                        throw e;
-                }
-                catch (Exception e)
-                {
-                    if (e instanceof EofException)
-                        LOG.debug(e);
-                    else
-                        LOG.warn(String.valueOf(_uri), e);
-                    _state.error(e);
-                    _request.setHandled(true);
-                    handleException(e);
-                }
-                finally
-                {
-                    handling = !_state.unhandle();
+                        _request.setDispatcherType(DispatcherType.ASYNC);
+                    getServer().handleAsync(this);
                 }
             }
-        }
-        finally
-        {
-            if (threadName != null && LOG.isDebugEnabled())
-                Thread.currentThread().setName(threadName);
-            setCurrentHttpChannel(null);
-
-            if (_state.isCompleting())
+            catch (Error e)
             {
-                try
-                {
-                    _state.completed();
-
-                    if (!_response.isCommitted() && !_request.isHandled())
-                        _response.sendError(404);
-
-                    // Complete generating the response
-                    _response.complete();
-
-                }
-                catch(EofException e)
-                {
+                if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+                    LOG.ignore(e);
+                else 
+                    throw e;
+            }
+            catch (Exception e)
+            {
+                if (e instanceof EofException)
                     LOG.debug(e);
-                }
-                catch(Exception e)
-                {
-                    LOG.warn(e);
-                }
-                finally
-                {
-                    _request.setHandled(true);
-                    _transport.completed();
-                }
+                else
+                    LOG.warn(String.valueOf(_uri), e);
+                _state.error(e);
+                _request.setHandled(true);
+                handleException(e);
             }
-
-            LOG.debug("{} handle exit", this);
+            finally
+            {
+                next = _state.unhandle();
+            }
         }
+
+        if (threadName != null && LOG.isDebugEnabled())
+            Thread.currentThread().setName(threadName);
+        setCurrentHttpChannel(null);
+
+        if (next==Next.COMPLETE)
+        {
+            try
+            {
+                _state.completed();
+
+                if (!_response.isCommitted() && !_request.isHandled())
+                    _response.sendError(404);
+
+                // Complete generating the response
+                _response.complete();
+            }
+            catch(EofException e)
+            {
+                LOG.debug(e);
+            }
+            catch(Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                next=Next.RECYCLE;
+            }
+        }
+
+        if (next==Next.RECYCLE)
+        {
+            _request.setHandled(true);
+            _transport.completed();
+        }
+
+        LOG.debug("{} handle exit", this);
+
+        return next!=Next.WAIT;
     }
 
     /**
@@ -380,11 +391,13 @@
     @Override
     public String toString()
     {
-        return String.format("%s@%x{r=%s,a=%s}",
+        return String.format("%s@%x{r=%s,a=%s,uri=%s}",
                 getClass().getSimpleName(),
                 hashCode(),
                 _requests,
-                _state.getState());
+                _state.getState(),
+                _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
+            );
     }
 
     @Override
@@ -555,10 +568,9 @@
     }
 
     @Override
-    public boolean earlyEOF()
+    public void earlyEOF()
     {
         _request.getHttpInput().earlyEOF();
-        return false;
     }
 
     @Override
@@ -569,10 +581,9 @@
 
         try
         {
-            if (_state.handling())
+            if (_state.handling()==Next.CONTINUE)
             {
                 commitResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
-                _state.unhandle();
             }
         }
         catch (IOException e)
@@ -580,8 +591,9 @@
             LOG.warn(e);
         }
         finally
-        {
-            _state.completed();
+        { 
+            if (_state.unhandle()==Next.COMPLETE)
+                _state.completed();
         }
     }
 
@@ -664,4 +676,13 @@
     {
         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;
+    }
 }
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
index e50f77a..b61f1cb 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
@@ -21,19 +21,15 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
-import javax.servlet.AsyncContext;
+
 import javax.servlet.AsyncEvent;
 import javax.servlet.AsyncListener;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
 
 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.Scheduler;
@@ -56,7 +52,7 @@
  * <tr><th align=right>COMPLETED:</th>     <td></td>            <td></td>            <td></td>            <td></td>             <td></td>                <td></td></tr>
  * </table>
  */
-public class HttpChannelState implements AsyncContext
+public class HttpChannelState
 {
     private static final Logger LOG = Log.getLogger(HttpChannelState.class);
 
@@ -75,6 +71,14 @@
         COMPLETING,    // Request is completable
         COMPLETED      // Request is complete
     }
+    
+    public enum Next
+    {
+        CONTINUE,       // Continue handling the channel        
+        WAIT,           // Wait for further events 
+        COMPLETE,       // Complete the channel
+        RECYCLE,        // Channel is completed
+    }
 
     private final HttpChannel<?> _channel;
     private List<AsyncListener> _lastAsyncListeners;
@@ -86,7 +90,7 @@
     private boolean _expired;
     private volatile boolean _responseWrapped;
     private long _timeoutMs=DEFAULT_TIMEOUT;
-    private AsyncEventState _event;
+    private AsyncContextEvent _event;
 
     protected HttpChannelState(HttpChannel<?> channel)
     {
@@ -103,7 +107,6 @@
         }
     }
 
-    @Override
     public void addListener(AsyncListener listener)
     {
         synchronized(this)
@@ -114,19 +117,6 @@
         }
     }
 
-    @Override
-    public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response)
-    {
-        synchronized(this)
-        {
-            if (_asyncListeners==null)
-                _asyncListeners=new ArrayList<>();
-            _asyncListeners.add(listener);
-        }
-    }
-
-
-    @Override
     public void setTimeout(long ms)
     {
         synchronized(this)
@@ -135,7 +125,6 @@
         }
     }
 
-    @Override
     public long getTimeout()
     {
         synchronized(this)
@@ -144,7 +133,7 @@
         }
     }
 
-    public AsyncEventState getAsyncEventState()
+    public AsyncContextEvent getAsyncContextEvent()
     {
         synchronized(this)
         {
@@ -173,9 +162,9 @@
     }
 
     /**
-     * @return true if the handling of the request should proceed
+     * @return Next handling of the request should proceed
      */
-    protected boolean handling()
+    protected Next handling()
     {
         synchronized (this)
         {
@@ -197,12 +186,16 @@
 
                 case COMPLETECALLED:
                     _state=State.COMPLETING;
-                    return false;
+                    return Next.COMPLETE;
 
-                case ASYNCWAIT:
                 case COMPLETING:
+                    return Next.COMPLETE;
+                    
+                case ASYNCWAIT:
+                    return Next.WAIT;
+                    
                 case COMPLETED:
-                    return false;
+                    return Next.RECYCLE;
 
                 case REDISPATCH:
                     _state=State.REDISPATCHED;
@@ -213,12 +206,13 @@
             }
 
             _responseWrapped=false;
-            return true;
+            return Next.CONTINUE;
 
         }
     }
 
-    public void startAsync()
+
+    public void startAsync(AsyncContextEvent event)
     {
         synchronized (this)
         {
@@ -228,56 +222,15 @@
                 case REDISPATCHED:
                     _dispatched=false;
                     _expired=false;
+                    _responseWrapped=event.getSuppliedResponse()!=_channel.getResponse();
                     _responseWrapped=false;
-                    _event=new AsyncEventState(_channel.getRequest().getServletContext(),_channel.getRequest(),_channel.getResponse());
+                    _event=event;
                     _state=State.ASYNCSTARTED;
                     List<AsyncListener> listeners=_lastAsyncListeners;
                     _lastAsyncListeners=_asyncListeners;
+                    if (listeners!=null)
+                        listeners.clear();
                     _asyncListeners=listeners;
-                    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);
-                }
-            }
-        }
-    }
-
-    public void startAsync(final ServletContext context,final ServletRequest request,final ServletResponse response)
-    {
-        synchronized (this)
-        {
-            switch(_state)
-            {
-                case DISPATCHED:
-                case REDISPATCHED:
-                    _dispatched=false;
-                    _expired=false;
-                    _responseWrapped=response!=_channel.getResponse();
-                    _event=new AsyncEventState(context,request,response);
-                    _event._pathInContext = (request instanceof HttpServletRequest)?URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo()):null;
-                    _state=State.ASYNCSTARTED;
-                    List<AsyncListener> listeners=_lastAsyncListeners;
-                    _lastAsyncListeners=_asyncListeners;
-                    _asyncListeners=listeners;
-                    if (_asyncListeners!=null)
-                        _asyncListeners.clear();
                     break;
 
                 default:
@@ -306,7 +259,7 @@
         synchronized (this)
         {
             if (_event!=null)
-                _event._cause=th;
+                _event.setThrowable(th);
         }
     }
 
@@ -314,10 +267,10 @@
      * 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
+     * @return next actions
      * be handled again (eg because of a resume that happened before unhandle was called)
      */
-    protected boolean unhandle()
+    protected Next unhandle()
     {
         synchronized (this)
         {
@@ -326,7 +279,7 @@
                 case REDISPATCHED:
                 case DISPATCHED:
                     _state=State.COMPLETING;
-                    return true;
+                    return Next.COMPLETE;
 
                 case IDLE:
                     throw new IllegalStateException(this.getStatusString());
@@ -335,26 +288,17 @@
                     _initial=false;
                     _state=State.ASYNCWAIT;
                     scheduleTimeout();
-                    if (_state==State.ASYNCWAIT)
-                        return true;
-                    else if (_state==State.COMPLETECALLED)
-                    {
-                        _state=State.COMPLETING;
-                        return true;
-                    }
-                    _initial=false;
-                    _state=State.REDISPATCHED;
-                    return false;
+                    return Next.WAIT;
 
                 case REDISPATCHING:
                     _initial=false;
                     _state=State.REDISPATCHED;
-                    return false;
+                    return Next.CONTINUE;
 
                 case COMPLETECALLED:
                     _initial=false;
                     _state=State.COMPLETING;
-                    return true;
+                    return Next.COMPLETE;
 
                 default:
                     throw new IllegalStateException(this.getStatusString());
@@ -362,26 +306,29 @@
         }
     }
 
-    @Override
-    public void dispatch()
+    public void dispatch(ServletContext context, String path)
     {
         boolean dispatch;
         synchronized (this)
         {
+            
             switch(_state)
             {
                 case ASYNCSTARTED:
                     _state=State.REDISPATCHING;
+                    _event.setDispatchTarget(context,path);
                     _dispatched=true;
                     return;
 
                 case ASYNCWAIT:
                     dispatch=!_expired;
                     _state=State.REDISPATCH;
+                    _event.setDispatchTarget(context,path);
                     _dispatched=true;
                     break;
 
                 case REDISPATCH:
+                    _event.setDispatchTarget(context,path);
                     return;
 
                 default:
@@ -407,6 +354,7 @@
     protected void expired()
     {
         final List<AsyncListener> aListeners;
+        AsyncEvent event;
         synchronized (this)
         {
             switch(_state)
@@ -414,6 +362,7 @@
                 case ASYNCSTARTED:
                 case ASYNCWAIT:
                     _expired=true;
+                    event=_event;
                     aListeners=_asyncListeners;
                     break;
                 default:
@@ -427,7 +376,7 @@
             {
                 try
                 {
-                    listener.onTimeout(_event);
+                    listener.onTimeout(event);
                 }
                 catch(Exception e)
                 {
@@ -453,11 +402,10 @@
         scheduleDispatch();
     }
 
-    @Override
     public void complete()
     {
         // just like resume, except don't set _dispatched=true;
-        boolean dispatch;
+        boolean handle;
         synchronized (this)
         {
             switch(_state)
@@ -473,7 +421,7 @@
 
                 case ASYNCWAIT:
                     _state=State.COMPLETECALLED;
-                    dispatch=!_expired;
+                    handle=!_expired;
                     break;
 
                 default:
@@ -481,29 +429,21 @@
             }
         }
 
-        if (dispatch)
+        if (handle)
         {
             cancelTimeout();
-            scheduleDispatch();
-        }
-    }
-
-    @Override
-    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
-    {
-        try
-        {
-            return clazz.newInstance();
-        }
-        catch(Exception e)
-        {
-            throw new ServletException(e);
+            ContextHandler handler=getContextHandler();
+            if (handler!=null)
+                handler.handle(_channel);
+            else
+                _channel.handle();
         }
     }
 
     protected void completed()
     {
         final List<AsyncListener> aListeners;
+        final AsyncContextEvent event;
         synchronized (this)
         {
             switch(_state)
@@ -511,6 +451,8 @@
                 case COMPLETING:
                     _state=State.COMPLETED;
                     aListeners=_asyncListeners;
+                    event=_event;
+                    _event=null;
                     break;
 
                 default:
@@ -524,14 +466,14 @@
             {
                 try
                 {
-                    if (_event!=null && _event._cause!=null)
+                    if (event!=null && event.getThrowable()!=null)
                     {
-                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,_event._cause);
-                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,_event._cause.getMessage());
-                        listener.onError(_event);
+                        event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+                        event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
+                        listener.onError(event);
                     }
                     else
-                        listener.onComplete(_event);
+                        listener.onComplete(event);
                 }
                 catch(Exception e)
                 {
@@ -539,6 +481,9 @@
                 }
             }
         }
+
+        if (event!=null)
+            event.completed();
     }
 
     protected void recycle()
@@ -563,14 +508,6 @@
         }
     }
 
-    public void cancel()
-    {
-        synchronized (this)
-        {
-            cancelTimeout();
-        }
-    }
-
     protected void scheduleDispatch()
     {
         _channel.execute(_channel);
@@ -580,18 +517,14 @@
     {
         Scheduler scheduler = _channel.getScheduler();
         if (scheduler!=null && _timeoutMs>0)
-            _event._timeout=scheduler.schedule(new AsyncTimeout(),_timeoutMs,TimeUnit.MILLISECONDS);
+            _event.setTimeoutTask(scheduler.schedule(new AsyncTimeout(),_timeoutMs,TimeUnit.MILLISECONDS));
     }
 
     protected void cancelTimeout()
     {
-        AsyncEventState event=_event;
+        AsyncContextEvent event=_event;
         if (event!=null)
-        {
-            Scheduler.Task task=event._timeout;
-            if (task!=null)
-                task.cancel();
-        }
+            event.cancelTimeoutTask();
     }
 
     public boolean isExpired()
@@ -656,73 +589,25 @@
         }
     }
 
-    @Override
-    public void dispatch(ServletContext context, String path)
-    {
-        _event._dispatchContext=context;
-        _event._pathInContext=path;
-        dispatch();
-    }
-
-    @Override
-    public void dispatch(String path)
-    {
-        _event._pathInContext=path;
-        dispatch();
-    }
-
     public Request getBaseRequest()
     {
         return _channel.getRequest();
     }
 
-    @Override
-    public ServletRequest getRequest()
+    public HttpChannel<?> getHttpChannel()
     {
-        if (_event!=null)
-            return _event.getSuppliedRequest();
-        return _channel.getRequest();
-    }
-
-    @Override
-    public ServletResponse getResponse()
-    {
-        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
-            return _event.getSuppliedResponse();
-        return _channel.getResponse();
-    }
-
-    @Override
-    public void start(final Runnable run)
-    {
-        final AsyncEventState event=_event;
-        if (event!=null)
-        {
-            _channel.execute(new Runnable()
-            {
-                @Override
-                public void run()
-                {
-                    ((Context)event.getServletContext()).getContextHandler().handle(run);
-                }
-            });
-        }
-    }
-
-    @Override
-    public boolean hasOriginalRequestAndResponse()
-    {
-        synchronized (this)
-        {
-            return (_event!=null && _event.getSuppliedRequest()==_channel.getRequest() && _event.getSuppliedResponse()==_channel.getResponse());
-        }
+        return _channel;
     }
 
     public ContextHandler getContextHandler()
     {
-        final AsyncEventState event=_event;
+        final AsyncContextEvent event=_event;
         if (event!=null)
-            return ((Context)event.getServletContext()).getContextHandler();
+        {
+            Context context=((Context)event.getServletContext());
+            if (context!=null)
+                return context.getContextHandler();
+        }
         return null;
     }
 
@@ -757,70 +642,4 @@
         }
     }
 
-    public class AsyncEventState extends AsyncEvent
-    {
-        final private ServletContext _suspendedContext;
-        private String _pathInContext;
-        private Scheduler.Task _timeout;
-        private ServletContext _dispatchContext;
-        private Throwable _cause;
-
-        public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
-        {
-            super(HttpChannelState.this, request,response);
-            _suspendedContext=context;
-
-            // Get the base request So we can remember the initial paths
-            Request r=_channel.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;
-        }
-
-        /**
-         * @return The path in the context
-         */
-        public String getPath()
-        {
-            return _pathInContext;
-        }
-    }
 }
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
index 69433f5..7029fd4 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -207,8 +207,28 @@
                 // Can the parser progress (even with an empty buffer)
                 boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
 
-                // If there is a request buffer, we are re-entering here
-                if (!call_channel && BufferUtil.isEmpty(_requestBuffer))
+                // Parse the buffer
+                if (call_channel)
+                {
+                    // Parse as much content as there is available before calling the channel
+                    // this is both efficient (may queue many chunks), will correctly set available for 100 continues
+                    // and will drive the parser to completion if all content is available.
+                    while (_parser.inContentState())
+                    {
+                        if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+                            break;
+                    }
+
+                    // 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.
+                    boolean handle=_channel.handle();
+
+                    // Return if suspended or upgraded
+                    if (!handle || getEndPoint().getConnection()!=this)
+                        return;
+                }
+                else if (BufferUtil.isEmpty(_requestBuffer))
                 {
                     if (_requestBuffer == null)
                         _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
@@ -242,32 +262,14 @@
                         releaseRequestBuffer();
                         return;
                     }
-
-                    // Parse what we have read
-                    call_channel=_parser.parseNext(_requestBuffer);
                 }
-
-                // Parse the buffer
-                if (call_channel)
+                else
                 {
-                    // Parse as much content as there is available before calling the channel
-                    // this is both efficient (may queue many chunks), will correctly set available for 100 continues
-                    // and will drive the parser to completion if all content is available.
-                    while (_parser.inContentState())
-                    {
-                        if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
-                            break;
-                    }
-
-                    // 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.
-                        
-                    _channel.run();
-                    
-                    // Return if suspended or upgraded
-                    if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this)
-                        return;
+                    // TODO work out how we can get here and a better way to handle it
+                    LOG.warn("Unexpected state: "+this+ " "+_channel+" "+_channel.getRequest());
+                    if (!_channel.getState().isSuspended())
+                        getEndPoint().close();
+                    return;
                 }
             }
         }
@@ -275,7 +277,7 @@
         {
             LOG.debug(e);
         }
-        catch (IOException e)
+        catch (Exception e)
         {
             if (_parser.isIdle())
                 LOG.debug(e);
@@ -283,11 +285,6 @@
                 LOG.warn(this.toString(), e);
             close();
         }
-        catch (Exception e)
-        {
-            LOG.warn(this.toString(), e);
-            close();
-        }
         finally
         {
             setCurrentConnection(null);
@@ -511,13 +508,6 @@
                     getEndPoint().close();
                 }
             }
-
-            // make sure that an oshut connection is driven towards close
-            // TODO this is a little ugly
-            if (getEndPoint().isOpen() && getEndPoint().isOutputShutdown())
-            {
-                fillInterested();
-            }
         }
     }
 
@@ -559,11 +549,12 @@
                         if (getEndPoint().isInputShutdown())
                         {
                             _parser.shutdownInput();
+                            shutdown();
                             return;
                         }
 
                         // Wait until we can read
-                        getEndPoint().fillInterested(_readBlocker);
+                        block(_readBlocker);
                         LOG.debug("{} block readable on {}",this,_readBlocker);
                         _readBlocker.block();
 
@@ -618,7 +609,6 @@
              */
         }
 
-
         @Override
         protected void onAllContentConsumed()
         {
@@ -628,6 +618,12 @@
              */
             releaseRequestBuffer();
         }
+
+        @Override
+        public String toString()
+        {
+            return super.toString()+"{"+_channel+","+HttpConnection.this+"}";
+        }
     }
 
     private class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
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 d5db837..f166e82 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
@@ -96,32 +96,53 @@
         T item = null;
         synchronized (lock())
         {
-            while (item == null)
+            // Get the current head of the input Q
+            item = _inputQ.peekUnsafe();
+            
+            // Skip empty items at the head of the queue
+            while (item != null && remaining(item) == 0)
             {
+                _inputQ.pollUnsafe();
+                onContentConsumed(item);
+                LOG.debug("{} consumed {}", this, item);
                 item = _inputQ.peekUnsafe();
-                while (item != null && remaining(item) == 0)
+                
+                // If that was the last item then notify
+                if (item==null)
+                    onAllContentConsumed();
+            }
+
+            // If we have no item
+            if (item == null)
+            {
+                // Was it unexpectedly EOF'd?
+                if (isEarlyEOF())
+                    throw new EofException();
+
+                // check for EOF
+                if (isShutdown())
                 {
-                    _inputQ.pollUnsafe();
-                    onContentConsumed(item);
-                    LOG.debug("{} consumed {}", this, item);
-                    item = _inputQ.peekUnsafe();
+                    onEOF();
+                    return -1;
                 }
 
-                if (item == null)
+                // OK then block for some more content
+                blockForContent();
+                
+                // If still not content, we must be closed
+                item = _inputQ.peekUnsafe();
+                if (item==null)
                 {
-                    onAllContentConsumed();
-
                     if (isEarlyEOF())
                         throw new EofException();
+                    
+                    // blockForContent will only return with no 
+                    // content if it is closed.
+                    if (!isShutdown())
+                        LOG.warn("Unexpected !EOF: "+this);
 
-                    // check for EOF
-                    if (isShutdown())
-                    {
-                        onEOF();
-                        return -1;
-                    }
-
-                    blockForContent();
+                    onEOF();
+                    return -1;
                 }
             }
         }
@@ -152,20 +173,34 @@
         }
     }
 
+    /* ------------------------------------------------------------ */
+    /** Called by this HttpInput to signal new content has been queued
+     * @param item
+     */
     protected void onContentQueued(T item)
     {
         lock().notify();
     }
 
+    /* ------------------------------------------------------------ */
+    /** Called by this HttpInput to signal all available content has been consumed
+     */
     protected void onAllContentConsumed()
     {
     }
 
+    /* ------------------------------------------------------------ */
+    /** Called by this HttpInput to signal it has reached EOF
+     */
     protected void onEOF()
     {
     }
 
-    public boolean content(T item)
+    /* ------------------------------------------------------------ */
+    /** Add some content to the input stream
+     * @param item
+     */
+    public void content(T item)
     {
         synchronized (lock())
         {
@@ -176,19 +211,26 @@
             onContentQueued(item);
             LOG.debug("{} queued {}", this, item);
         }
-        return true;
     }
 
+    /* ------------------------------------------------------------ */
+    /** This method should be called to signal to the HttpInput
+     * that an EOF has arrived before all the expected content.
+     * Typically this will result in an EOFException being thrown
+     * from a subsequent read rather than a -1 return.
+     */
     public void earlyEOF()
     {
         synchronized (lock())
         {
             _earlyEOF = true;
+            _inputEOF = true;
             lock().notify();
             LOG.debug("{} early EOF", this);
         }
     }
 
+    /* ------------------------------------------------------------ */
     public boolean isEarlyEOF()
     {
         synchronized (lock())
@@ -197,6 +239,7 @@
         }
     }
 
+    /* ------------------------------------------------------------ */
     public void shutdown()
     {
         synchronized (lock())
@@ -207,6 +250,7 @@
         }
     }
 
+    /* ------------------------------------------------------------ */
     public boolean isShutdown()
     {
         synchronized (lock())
@@ -215,13 +259,14 @@
         }
     }
 
+    /* ------------------------------------------------------------ */
     public void consumeAll()
     {
         synchronized (lock())
         {
+            T item = _inputQ.peekUnsafe();
             while (!isShutdown() && !isEarlyEOF())
             {
-                T item = _inputQ.peekUnsafe();
                 while (item != null)
                 {
                     _inputQ.pollUnsafe();
@@ -235,6 +280,9 @@
                 try
                 {
                     blockForContent();
+                    item = _inputQ.peekUnsafe();
+                    if (item==null)
+                        break;
                 }
                 catch (IOException e)
                 {
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 fbe6597..56bc29d 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
@@ -47,9 +47,6 @@
  */
 public class HttpOutput extends ServletOutputStream
 {
-    private static final boolean OUTPUT_BUFFER_DIRECT=false;
-    private static final boolean CHANNEL_BUFFER_DIRECT=true;
-    private static final boolean STREAM_BUFFER_DIRECT=false;
     private static Logger LOG = Log.getLogger(HttpOutput.class);
     private final HttpChannel<?> _channel;
     private boolean _closed;
@@ -165,8 +162,9 @@
                 return;
             }
 
-            // Allocate an aggregate buffer
-            _aggregate = _channel.getByteBufferPool().acquire(size, OUTPUT_BUFFER_DIRECT);
+            // Allocate an aggregate buffer.
+            // Never direct as it is slow to do little writes to a direct buffer.
+            _aggregate = _channel.getByteBufferPool().acquire(size, false);
         }
 
         // Do we have space to aggregate ?
@@ -206,8 +204,10 @@
         if (isClosed())
             throw new EOFException("Closed");
 
+        // Allocate an aggregate buffer.
+        // Never direct as it is slow to do little writes to a direct buffer.
         if (_aggregate == null)
-            _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), OUTPUT_BUFFER_DIRECT);
+            _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
 
         BufferUtil.append(_aggregate, (byte)b);
         _written++;
@@ -256,7 +256,7 @@
             if (etag!=null)
                 response.getHttpFields().put(HttpHeader.ETAG,etag);
 
-            content = httpContent.getDirectBuffer();
+            content = _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
             if (content == null)
                 content = httpContent.getIndirectBuffer();
             if (content == null)
@@ -268,7 +268,7 @@
         {
             Resource resource = (Resource)content;
             _channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified());
-            content = resource.getInputStream();
+            content=resource.getInputStream(); // Closed below
         }
 
         // Process content.
@@ -279,9 +279,8 @@
         }
         else if (content instanceof ReadableByteChannel)
         {
-            ReadableByteChannel channel = (ReadableByteChannel)content;
-            ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), CHANNEL_BUFFER_DIRECT);
-            try
+            ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
+            try (ReadableByteChannel channel = (ReadableByteChannel)content;)
             {
                 while(channel.isOpen())
                 {
@@ -301,12 +300,12 @@
         }
         else if (content instanceof InputStream)
         {
-            InputStream in = (InputStream)content;
-            ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), STREAM_BUFFER_DIRECT);
+            // allocate non direct buffer so array may be directly accessed.
+            ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
             byte[] array = buffer.array();
             int offset=buffer.arrayOffset();
             int size=array.length-offset;
-            try
+            try (InputStream in = (InputStream)content;)
             {
                 while(true)
                 {
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 7910c6e..ebf0025 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
@@ -81,7 +81,7 @@
      * @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 headers, long size)
     {
         Object satRanges=null;
         
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 f2518ff..6831dbc 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
@@ -62,6 +62,7 @@
 import org.eclipse.jetty.http.HttpFields;
 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.HttpURI;
 import org.eclipse.jetty.http.HttpVersion;
@@ -114,9 +115,9 @@
  */
 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";
+    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 Logger LOG = Log.getLogger(Request.class);
     private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
@@ -203,7 +204,8 @@
     private long _dispatchTime;
     private HttpURI _uri;
     private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
-
+    private AsyncContextState _async;
+    
     /* ------------------------------------------------------------ */
     public Request(HttpChannel<?> channel, HttpInput<?> input)
     {
@@ -369,10 +371,11 @@
     @Override
     public AsyncContext getAsyncContext()
     {
-        HttpChannelState continuation = getHttpChannelState();
-        if (continuation.isInitial() && !continuation.isAsync())
-            throw new IllegalStateException(continuation.getStatusString());
-        return continuation;
+        HttpChannelState state = getHttpChannelState();
+        if (_async==null || state.isInitial() && !state.isAsync())
+            throw new IllegalStateException(state.getStatusString());
+        
+        return _async;
     }
 
     /* ------------------------------------------------------------ */
@@ -1027,19 +1030,8 @@
     @Override
     public StringBuffer getRequestURL()
     {
-        final StringBuffer url = new StringBuffer(48);
-        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);
-        }
-
+        final StringBuffer url = new StringBuffer(128);
+        URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
         url.append(getRequestURI());
         return url;
     }
@@ -1063,19 +1055,8 @@
      */
     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;
     }
 
@@ -1105,41 +1086,58 @@
 
         // Return host from absolute URI
         _serverName = _uri.getHost();
-        _port = _uri.getPort();
         if (_serverName != null)
+        {
+            _port = _uri.getPort();
             return _serverName;
+        }
 
         // Return host from header field
         String hostPort = _fields.getStringField(HttpHeader.HOST);
+        
+        _port=0;
         if (hostPort != null)
         {
-            loop: for (int i = hostPort.length(); i-- > 0;)
+            int len=hostPort.length();
+            loop: for (int i = len; i-- > 0;)
             {
-                char ch = (char)(0xff & hostPort.charAt(i));
-                switch (ch)
+                char c2 = (char)(0xff & hostPort.charAt(i));
+                switch (c2)
                 {
                     case ']':
                         break loop;
 
                     case ':':
-                        _serverName = hostPort.substring(0,i);
                         try
                         {
+                            len=i;
                             _port = StringUtil.toInt(hostPort.substring(i+1));
                         }
                         catch (NumberFormatException e)
                         {
                             LOG.warn(e);
+                            _serverName=hostPort;
+                            _port=0;
+                            return _serverName;
                         }
-                        return _serverName;
+                        break loop;
                 }
             }
-
-            if (_serverName == null || _port < 0)
+            if (hostPort.charAt(0)=='[')
             {
-                _serverName = hostPort;
-                _port = 0;
+                if (hostPort.charAt(len-1)!=']') 
+                {
+                    LOG.warn("Bad IPv6 "+hostPort);
+                    _serverName=hostPort;
+                    _port=0;
+                    return _serverName;
+                }
+                _serverName = hostPort.substring(1,len-1);
             }
+            else if (len==hostPort.length())
+                _serverName=hostPort;
+            else
+                _serverName = hostPort.substring(0,len);
 
             return _serverName;
         }
@@ -1506,6 +1504,9 @@
 
         setAuthentication(Authentication.NOT_CHECKED);
         getHttpChannelState().recycle();
+        if (_async!=null)
+            _async.reset();
+        _async=null;
         _asyncSupported = true;
         _handled = false;
         if (_context != null)
@@ -1989,8 +1990,11 @@
         if (!_asyncSupported)
             throw new IllegalStateException("!asyncSupported");
         HttpChannelState state = getHttpChannelState();
-        state.startAsync();
-        return state;
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,this,getResponse());
+        state.startAsync(event);
+        return _async;
     }
 
     /* ------------------------------------------------------------ */
@@ -2000,8 +2004,12 @@
         if (!_asyncSupported)
             throw new IllegalStateException("!asyncSupported");
         HttpChannelState state = getHttpChannelState();
-        state.startAsync(_context, servletRequest, servletResponse);
-        return state;
+        if (_async==null)
+            _async=new AsyncContextState(state);
+        AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,servletRequest,servletResponse);
+        event.setDispatchTarget(getServletContext(),URIUtil.addPaths(getServletPath(),getPathInfo()));
+        state.startAsync(event);
+        return _async;
     }
 
     /* ------------------------------------------------------------ */
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 d800970..6dc7640 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
@@ -57,8 +57,8 @@
     private final ResourceCache _parent;
     private final MimeTypes _mimeTypes;
     private final boolean _etagSupported;
-
-    private boolean  _useFileMappedBuffer=true;
+    private final boolean  _useFileMappedBuffer;
+    
     private int _maxCachedFileSize =4*1024*1024;
     private int _maxCachedFiles=2048;
     private int _maxCacheSize =32*1024*1024;
@@ -143,12 +143,6 @@
     }
 
     /* ------------------------------------------------------------ */
-    public void setUseFileMappedBuffer(boolean useFileMappedBuffer)
-    {
-        _useFileMappedBuffer = useFileMappedBuffer;
-    }
-
-    /* ------------------------------------------------------------ */
     public void flushCache()
     {
         if (_cache!=null)
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 d1e90a2..d18366a 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
@@ -753,6 +753,8 @@
                     break;
                 case STREAM:
                     getOutputStream().close();
+                    break;
+                default:
             }
             return true;
         }
@@ -926,6 +928,7 @@
                         case TE:
                             _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
                             break;
+                        default:
                     }
                 }
             }
@@ -965,6 +968,8 @@
             case STREAM:
             case WRITER:
                 _out.reset();
+                break;
+            default:
         }
 
         _out.resetBuffer();
@@ -1023,7 +1028,7 @@
         return _reason;
     }
 
-    public void complete() throws IOException
+    public void complete()
     {
         _out.close();
     }
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 e8e0311..28e6de3 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,11 +19,9 @@
 package org.eclipse.jetty.server;
 
 import java.io.IOException;
-import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -34,22 +32,18 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
-
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.xml.validation.Schema;
 
 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.HttpScheme;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.HandlerWrapper;
 import org.eclipse.jetty.util.Attributes;
@@ -282,9 +276,9 @@
     {
         if (getStopAtShutdown())
         {
-            ShutdownThread.register(this);    
+            ShutdownThread.register(this);
         }
-        
+
         ShutdownMonitor.getInstance().start(); // initialize
 
         LOG.info("jetty-"+getVersion());
@@ -318,7 +312,7 @@
         if (isDumpAfterStart())
             dumpStdErr();
 
-        
+
         // use DateCache timer for Date field reformat
         final HttpFields.DateGenerator date = new HttpFields.DateGenerator();
         long now=System.currentTimeMillis();
@@ -335,8 +329,8 @@
                     this.cancel();
             }
         },tick,1000);
-        
-        
+
+
         mex.ifExceptionThrow();
     }
 
@@ -374,7 +368,7 @@
         if (stopTimeout>0)
         {
             long stop_by=System.currentTimeMillis()+stopTimeout;
-            LOG.info("Graceful shutdown {} by ",this,new Date(stop_by));
+            LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
 
             // Wait for shutdowns
             for (Future<Void> future: futures)
@@ -476,16 +470,16 @@
      */
     public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
     {
-        final HttpChannelState async = connection.getRequest().getHttpChannelState();
-        final HttpChannelState.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
-            ServletContext context=state.getServletContext();
+            ServletContext context=event.getServletContext();
             HttpURI uri = new HttpURI(context==null?path:URIUtil.addPaths(context.getContextPath(),path));
             baseRequest.setUri(uri);
             baseRequest.setRequestURI(null);
@@ -495,8 +489,8 @@
         }
 
         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())
         {
@@ -509,7 +503,6 @@
 
     }
 
-
     /* ------------------------------------------------------------ */
     public void join() throws InterruptedException
     {
@@ -607,10 +600,10 @@
                 break;
             }
         }
-            
+
         if (connector==null)
             return null;
-        
+
         ContextHandler context = getChildHandlerByClass(ContextHandler.class);
 
         try
@@ -622,7 +615,7 @@
                 host=context.getVirtualHosts()[0];
             if (host==null)
                 host=InetAddress.getLocalHost().getHostAddress();
-            
+
             String path=context==null?null:context.getContextPath();
             if (path==null)
                 path="/";
@@ -634,7 +627,7 @@
             return null;
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     @Override
     public String toString()
@@ -647,7 +640,7 @@
     {
         dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
     }
-    
+
     /* ------------------------------------------------------------ */
     public static void main(String...args) throws Exception
     {
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 95d307c..709476c 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
@@ -40,7 +40,6 @@
 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;
@@ -104,11 +103,11 @@
 {
     public static int SERVLET_MAJOR_VERSION=3;
     public static int SERVLET_MINOR_VERSION=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>();
@@ -141,8 +140,8 @@
             return c.getContextHandler();
         return null;
     }
-    
-    
+
+
     protected Context _scontext;
     private final AttributesMap _attributes;
     private final Map<String, String> _initParams;
@@ -566,7 +565,7 @@
     public void addEventListener(EventListener listener)
     {
         _eventListeners.add(listener);
-        
+
         if (listener instanceof ServletContextListener)
             _contextListeners.add((ServletContextListener)listener);
 
@@ -579,7 +578,7 @@
         if (listener instanceof ServletRequestAttributeListener)
             _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Remove a context event listeners.
@@ -592,7 +591,7 @@
     public void removeEventListener(EventListener listener)
     {
         _eventListeners.remove(listener);
-        
+
         if (listener instanceof ServletContextListener)
             _contextListeners.remove(listener);
 
@@ -616,13 +615,13 @@
     {
         _programmaticListeners.add(listener);
     }
-    
+
     /* ------------------------------------------------------------ */
     protected boolean isProgrammaticListener(EventListener listener)
     {
         return _programmaticListeners.contains(listener);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return true if this context is accepting new requests
@@ -725,7 +724,7 @@
             startContext();
 
             _availability = Availability.AVAILABLE;
-            LOG.info("started {}",this);
+            LOG.info("Started {}", this);
         }
         finally
         {
@@ -771,7 +770,7 @@
             ServletContextEvent event = new ServletContextEvent(_scontext);
             for (ServletContextListener listener:_contextListeners)
                 callContextInitialized(listener, event);
-        } 
+        }
     }
 
     /* ------------------------------------------------------------ */
@@ -818,7 +817,7 @@
             if (!_contextListeners.isEmpty())
             {
                 ServletContextEvent event = new ServletContextEvent(_scontext);
-                for (int i = _contextListeners.size(); i-->0;) 
+                for (int i = _contextListeners.size(); i-->0;)
                     callContextDestroyed(_contextListeners.get(i),event);
             }
 
@@ -831,14 +830,14 @@
                 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 && current_thread!=null)
@@ -894,10 +893,10 @@
                     default:
                         match = contextVhost.equalsIgnoreCase(vhost);
                 }
-                
+
                 if (match)
                     break loop;
-                
+
             }
             if (!match)
                 return false;
@@ -1657,7 +1656,7 @@
 
         return host;
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * Add an AliasCheck instance to possibly permit aliased resources
@@ -1667,7 +1666,7 @@
     {
         _aliasChecks.add(check);
     }
-    
+
     /* ------------------------------------------------------------ */
     /**
      * @return Mutable list of Alias checks
@@ -1798,7 +1797,7 @@
                 return null;
             return _mimeTypes.getMimeByExtension(file);
         }
-        
+
         /* ------------------------------------------------------------ */
         /*
          * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
@@ -2182,13 +2181,13 @@
             return _enabled;
         }
     }
-    
+
 
     public static class NoContext extends AttributesMap implements ServletContext
     {
         private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION;
         private int _effectiveMinorVersion = SERVLET_MINOR_VERSION;
-        
+
         /* ------------------------------------------------------------ */
         public NoContext()
         {
@@ -2522,8 +2521,8 @@
             LOG.warn(__unimplmented);
         }
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /** Interface to check aliases
      */
@@ -2537,7 +2536,7 @@
          */
         boolean check(String path, Resource resource);
     }
-    
+
 
     /* ------------------------------------------------------------ */
     /** Approve all aliases.
@@ -2550,7 +2549,7 @@
             return true;
         }
     }
-    
+
     /* ------------------------------------------------------------ */
     /** Approve Aliases with same suffix.
      * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
@@ -2568,8 +2567,8 @@
             return resource.getAlias().toString().endsWith(suffix);
         }
     }
-    
-    
+
+
     /* ------------------------------------------------------------ */
     /** Approve Aliases with a path prefix.
      * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
@@ -2589,7 +2588,7 @@
     }
     /* ------------------------------------------------------------ */
     /** 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
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 066f6ab..a758c70 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
@@ -27,6 +27,9 @@
 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;
@@ -40,7 +43,7 @@
  * 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;
@@ -69,14 +72,10 @@
         String ex=null;
         try
         {
-            long now=System.currentTimeMillis();
-            final String d=_date.format(now);
-            final int ms=(int)(now%1000);
-
             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);
@@ -104,21 +103,25 @@
         finally
         {
             thread.setName(old_name);
-            long now=System.currentTimeMillis();
-            final String d=_date.format(now);
-            final int ms=(int)(now%1000);
             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());
+                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.format(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()
@@ -129,6 +132,11 @@
         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();
     }
 
@@ -140,6 +148,9 @@
     {
         super.doStop();
         _print.close();
+        for (Connector connector : getServer().getConnectors())
+            if (connector instanceof AbstractConnector)
+                ((AbstractConnector)connector).removeBean(this);
     }
 
     /**
@@ -157,4 +168,17 @@
     {
         _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/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
index 7a2ac70..78e8eb7 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
@@ -28,6 +28,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.eclipse.jetty.server.AsyncContextEvent;
 import org.eclipse.jetty.server.HttpChannelState;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.Response;
@@ -76,12 +77,13 @@
         public void onError(AsyncEvent event) throws IOException
         {
         }
-        
+
         @Override
         public void onComplete(AsyncEvent event) throws IOException
         {
-            HttpChannelState state = (HttpChannelState)event.getAsyncContext();
             
+            HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
+
             Request request = state.getBaseRequest();
             final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
 
@@ -93,7 +95,7 @@
             if (!state.isDispatched())
                 _asyncWaitStats.decrement();
         }
-        
+
     };
 
     /**
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 afe7ae2..e596188 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
@@ -323,17 +323,20 @@
                 super.complete();
                 try
                 {
-                    if (_dirty)
+                    if (isValid())
                     {
-                        //The session attributes have changed, write to the db, ensuring
-                        //http passivation/activation listeners called
-                        willPassivate();                      
-                        updateSession(this);
-                        didActivate();
-                    }
-                    else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
-                    {
-                        updateSessionAccessTime(this);
+                        if (_dirty)
+                        {
+                            //The session attributes have changed, write to the db, ensuring
+                            //http passivation/activation listeners called
+                            willPassivate();                      
+                            updateSession(this);
+                            didActivate();
+                        }
+                        else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
+                        {
+                            updateSessionAccessTime(this);
+                        }
                     }
                 }
                 catch (Exception e)
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 9162374..a1b673d 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,26 +18,38 @@
 
 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.util.Arrays;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Exchanger;
 import java.util.concurrent.TimeUnit;
 
 import javax.servlet.AsyncContext;
 import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BlockingArrayQueue;
 import org.eclipse.jetty.util.IO;
 import org.eclipse.jetty.util.StringUtil;
+import org.hamcrest.Matchers;
+import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -46,116 +58,129 @@
 {
     private static Server server;
     private static ServerConnector connector;
-    private final static Exchanger<Long> __total=new Exchanger<Long>();
+    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 ServerConnector(server);
         connector.setIdleTimeout(10000);
         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
-    public void test() throws Exception
+    public void testPipelined() throws Exception
     {
-        final Socket socket =  new Socket("localhost",connector.getLocalPort());
+        server.setHandler(new AsyncStreamHandler());
+        server.start();
+        
+        try (final Socket socket =  new Socket("localhost",connector.getLocalPort()))
+        {
+            socket.setSoTimeout(1000);
+            
+            byte[] content = new byte[32*4096];
+            Arrays.fill(content, (byte)120);
 
-        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"+
+                    "\r\n";
+            byte[] h=header.getBytes(StringUtil.__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(StringUtil.__ISO_8859_1);
+            out.write(h);
+            out.write(content);
+            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"+
-            "Connection: close\r\n"+
-            "\r\n";
-        byte[] h=header.getBytes(StringUtil.__ISO_8859_1);
+            InputStream in = socket.getInputStream();
+            String response = IO.toString(in);
+            assertTrue(response.indexOf("200 OK")>0);
 
-        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);
-
-        out.flush();
-
-        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);
+
+            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);
+
+            out.flush();
+
+            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);
         }
-        out.write(content,offset,content.length-offset);
-
-        out.flush();
-
-        InputStream in = socket.getInputStream();
-        String response = IO.toString(in);
-        assertTrue(tst,response.indexOf("200 OK")>0);
-
-        long total=__total.exchange(0L,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
@@ -164,6 +189,7 @@
             request.setHandled(true);
 
             final AsyncContext async = request.startAsync();
+            // System.err.println("handle "+request.getContentLength());
             
             new Thread()
             {
@@ -171,9 +197,10 @@
                 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,17 +215,156 @@
                     {
                         httpResponse.setStatus(200);
                         async.complete();
-                        try
-                        {
-                            __total.exchange(total);
-                        }
-                        catch (InterruptedException e)
-                        {
-                            e.printStackTrace();
-                        }
+                        // System.err.println("read "+total);
+                        __total.offer(total);
                     }
                 }
             }.start();
         }
     }
+    
+
+    @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(StringUtil.__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(StringUtil.__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(StringUtil.__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(StringUtil.__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/CheckReverseProxyHeadersTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CheckReverseProxyHeadersTest.java
index 58a22b7..f4eec3e 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.assertTrue;
 
 import java.io.IOException;
+import java.util.concurrent.TimeUnit;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -44,6 +45,7 @@
                     "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 @@
                 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 @@
                     "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 @@
                     "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());
@@ -111,7 +151,8 @@
         try
         {
             server.start();
-            connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\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();
 
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 cc8e4c9..d0c8d3f 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
@@ -81,7 +81,7 @@
     }
 
     @Test
-    public void testFragmentedChunk()
+    public void testFragmentedChunk() throws Exception
     {
         String response=null;
         try
@@ -119,10 +119,9 @@
         }
         catch(Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
+            if(response != null)
                 System.err.println(response);
+            throw e;
         }
     }
 
@@ -249,7 +248,7 @@
     }
 
     @Test
-    public void testCharset()
+    public void testCharset() throws Exception
     {
 
         String response=null;
@@ -306,10 +305,9 @@
         }
         catch(Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
+            if(response != null)
                 System.err.println(response);
+            throw e;
         }
     }
 
@@ -391,7 +389,7 @@
     }
 
     @Test
-    public void testConnection()
+    public void testConnection() throws Exception
     {
         String response=null;
         try
@@ -412,10 +410,9 @@
         }
         catch (Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
-                 System.err.println(response);
+            if(response != null)
+                System.err.println(response);
+            throw e;
         }
     }
 
@@ -525,7 +522,7 @@
     }
 
     @Test
-    public void testAsterisk()
+    public void testAsterisk() throws Exception
     {
         String response = null;
 
@@ -572,10 +569,9 @@
         }
         catch (Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
-                 System.err.println(response);
+            if(response != null)
+                System.err.println(response);
+            throw e;
         }
         finally
         {
@@ -585,7 +581,7 @@
     }
 
     @Test
-    public void testCONNECT()
+    public void testCONNECT() throws Exception
     {
         String response = null;
 
@@ -601,10 +597,9 @@
         }
         catch (Exception e)
         {
-            e.printStackTrace();
-            assertTrue(false);
-            if (response!=null)
-                 System.err.println(response);
+            if(response != null)
+                System.err.println(response);
+            throw e;
         }
 
     }
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 fbe4baa..b717edf 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
@@ -960,7 +960,7 @@
 
 
             String in = IO.toString(is);
-            System.err.println(in);
+            // System.err.println(in);
 
             int index = in.indexOf("123456789");
             assertTrue(index > 0);
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 6cb0a2b..a770f95 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
@@ -135,11 +135,11 @@
        /*33*/ {"/?abc=test",null, null, null,null,"/", null,"abc=test",null},
        /*34*/ {"/#fragment",null, null, null,null,"/", null,null,"fragment"},
        /*35*/ {"http://192.0.0.1:8080/","http","//192.0.0.1:8080","192.0.0.1","8080","/",null,null,null},
-       /*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
-       /*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
-       /*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","[2001:db8::1]",null,"/",null,null,null},
+       /*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
+       /*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
+       /*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","2001:db8::1",null,"/",null,null,null},
        /*39*/ {"//[2001:db8::1]:8080/",null,null,null,null,"//[2001:db8::1]:8080/",null,null,null},
-       /*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
+       /*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
        /*41*/ {"*",null,null,null,null,"*",null, null,null}
     };
 
@@ -366,7 +366,7 @@
     {
        /* 0*/ {"  localhost:8080  ","localhost","8080"},
        /* 1*/ {"  127.0.0.1:8080  ","127.0.0.1","8080"},
-       /* 2*/ {"  [127::0::0::1]:8080  ","[127::0::0::1]","8080"},
+       /* 2*/ {"  [127::0::0::1]:8080  ","127::0::0::1","8080"},
        /* 3*/ {"  error  ",null,null},
        /* 4*/ {"  http://localhost:8080/  ",null,null},
     };
@@ -382,7 +382,7 @@
                 byte[] buf = connect_tests[i][0].getBytes(StringUtil.__UTF8);
 
                 uri.parseConnect(buf,2,buf.length-4);
-                assertEquals("path"+i,connect_tests[i][1]+":"+connect_tests[i][2],uri.getPath());
+                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());
             }
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 6bb09c1..b1c9bde 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
@@ -241,7 +241,7 @@
         multipart;
 
         String responses=_connector.getResponses(request);
-        System.err.println(responses);
+        // System.err.println(responses);
         assertTrue(responses.startsWith("HTTP/1.1 200"));
     }
 
@@ -354,6 +354,7 @@
             @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()));
@@ -361,71 +362,121 @@
             }
         };
 
-        String responses=_connector.getResponses(
+        results.clear();
+        _connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: myhost\n"+
-                "\n"+
-
+                "Connection: close\n"+
+                "\n");
+        int i=0;
+        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();
+        _connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: myhost:8888\n"+
-                "\n"+
-
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        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();
+        _connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: 1.2.3.4\n"+
-                "\n"+
-
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        
+        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();
+        _connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: 1.2.3.4:8888\n"+
-                "\n"+
-
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        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();
+        _connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: [::1]\n"+
-                "\n"+
-
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        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();
+        _connector.getResponses(
                 "GET / HTTP/1.1\n"+
                 "Host: [::1]:8888\n"+
-                "\n"+
-
+                "Connection: close\n"+
+                "\n");
+        i=0;
+        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();
+        _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;
+        assertEquals("https://[::1]/",results.get(i++));
+        assertEquals("remote",results.get(i++));
+        assertEquals("::1",results.get(i++));
+        assertEquals("443",results.get(i++));
+        
+        
+        results.clear();
+        _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",10,TimeUnit.SECONDS);
-
-
-        int i=0;
-        assertEquals("0.0.0.0",results.get(i++));
-        assertEquals("myhost",results.get(i++));
-        assertEquals("80",results.get(i++));
-        assertEquals("0.0.0.0",results.get(i++));
-        assertEquals("myhost",results.get(i++));
-        assertEquals("8888",results.get(i++));
-        assertEquals("0.0.0.0",results.get(i++));
-        assertEquals("1.2.3.4",results.get(i++));
-        assertEquals("80",results.get(i++));
-        assertEquals("0.0.0.0",results.get(i++));
-        assertEquals("1.2.3.4",results.get(i++));
-        assertEquals("8888",results.get(i++));
-        assertEquals("0.0.0.0",results.get(i++));
-        assertEquals("[::1]",results.get(i++));
-        assertEquals("80",results.get(i++));
-        assertEquals("0.0.0.0",results.get(i++));
-        assertEquals("[::1]",results.get(i++));
-        assertEquals("8888",results.get(i++));
+                "\n");
+        i=0;
+        
+        assertEquals("https://[::1]:8888/",results.get(i++));
         assertEquals("remote",results.get(i++));
-        assertEquals("[::1]",results.get(i++));
-        assertEquals("443",results.get(i++));
-        assertEquals("remote",results.get(i++));
-        assertEquals("[::1]",results.get(i++));
+        assertEquals("::1",results.get(i++));
         assertEquals("8888",results.get(i++));
 
+        
+
+
+        
+        
+        
     }
 
     @Test
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 afa0107..b4d00a1 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
@@ -432,43 +432,54 @@
         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/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"},
+                {"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)
         {
-            Response response = newResponse();
-            Request request = response.getHttpChannel().getRequest();
+            for (String host : hosts)
+            {
+                for (int i=0;i<tests.length;i++)
+                {
+                    Response response = newResponse();
+                    Request request = response.getHttpChannel().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);
-            HashSessionManager manager = new HashSessionManager();
-            manager.setSessionIdManager(new HashSessionIdManager());
-            request.setSessionManager(manager);
-            request.setSession(new TestSession(manager, "12345"));
-            manager.setCheckingRemoteSessionIdEncoding(false);
+                    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]);
+                    response.sendRedirect(tests[i][0]);
 
-            String location = response.getHeader("Location");
-            assertEquals("test-"+i,tests[i][1],location);
+                    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);
+                }
+            }
         }
     }
 
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 1de5bc7..269fce5 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
@@ -122,46 +122,46 @@
             q.clear();
     }
 
+    @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 testMinNonPersistent() throws Throwable
-    {
-        doThreads(2,2,false);
-    }
-
-    @Test
     public void testNonPersistent() throws Throwable
     {
         // TODO needs to be further investigated
-        assumeTrue(!OS.IS_OSX || PropertyFlag.isEnabled("test.stress"));
+        assumeTrue(!OS.IS_OSX);
 
-        doThreads(10,10,false);
-        if (PropertyFlag.isEnabled("test.stress"))
-        {
-            doThreads(20,20,false);
-            Thread.sleep(1000);
-            doThreads(200,10,false);
-            Thread.sleep(1000);
-            doThreads(200,200,false);
-        }
+        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(10,10,true);
-        if (PropertyFlag.isEnabled("test.stress"))
-        {
-            doThreads(40,40,true);
-            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
@@ -238,7 +238,6 @@
                     {
                         System.err.println("STALLED!!!");
                         System.err.println(_server.getThreadPool().toString());
-                        ((ServerConnector)(_server.getConnectors()[0])).dump();
                         Thread.sleep(5000);
                         System.exit(1);
                     }
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 ef91963..2a59567 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,19 +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;
 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;
@@ -46,6 +38,12 @@
 import org.junit.Before;
 import org.junit.Test;
 
+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;
+
 public class StatisticsHandlerTest
 {
     private Server _server;
@@ -60,7 +58,7 @@
         _server = new Server();
 
         _connector = new LocalConnector(_server);
-        _statistics=new ConnectorStatistics();
+        _statistics = new ConnectorStatistics();
         _connector.addBean(_statistics);
         _server.addConnector(_connector);
 
@@ -105,8 +103,8 @@
         _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();
@@ -174,8 +172,8 @@
         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);
@@ -208,37 +206,33 @@
         assertEquals(0, _statsHandler.getAsyncDispatches());
         assertEquals(0, _statsHandler.getExpires());
         assertEquals(4, _statsHandler.getResponses2xx());
-
-
     }
 
     @Test
     public void testSuspendResume() throws Exception
     {
+        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)};
+        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);
-                    
+                    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
                 {
@@ -246,50 +240,41 @@
                     {
                         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, _statistics.getConnectionsOpen());
-
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(1, _statsHandler.getDispatchedActive());
 
         barrier[1].await();
-
         assertTrue(_latchHandler.await());
         assertNotNull(asyncHolder.get());
-        assertTrue(asyncHolder.get()!=null);
 
         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();
 
-        Thread.sleep(50);
+        Thread.sleep(requestTime);
 
         asyncHolder.get().addListener(new AsyncListener()
         {
@@ -297,30 +282,34 @@
             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 { barrier[2].await(); } catch(Exception e) {}
+                try
+                {
+                    barrier[2].await();
+                }
+                catch (Exception ignored)
+                {
+                }
             }
         });
-        
         asyncHolder.get().dispatch();
 
         barrier[0].await(); // entered app handler
 
         assertEquals(1, _statistics.getConnectionsOpen());
-
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(2, _statsHandler.getDispatched());
@@ -335,50 +324,49 @@
         assertEquals(2, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-
         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);
 
-        assertThat(_statsHandler.getRequestTimeTotal(),greaterThanOrEqualTo(50L));
-        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 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)};
+        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);
+
+                    Thread.sleep(dispatchTime);
+
                     if (asyncHolder.get() == null)
                     {
-                        AsyncContext async=request.startAsync();
-                        async.setTimeout(100);
+                        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
                 {
@@ -386,28 +374,22 @@
                     {
                         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, _statistics.getConnectionsOpen());
-
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
@@ -423,31 +405,35 @@
             {
                 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
             {
-                try { barrier[2].await(); } catch(Exception e) {}                
+                try
+                {
+                    barrier[2].await();
+                }
+                catch (Exception ignored)
+                {
+                }
             }
         });
 
-
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-        
         barrier[2].await();
 
         assertEquals(1, _statsHandler.getRequests());
@@ -460,67 +446,42 @@
         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);
-
-        assertThat(_statsHandler.getDispatchedTimeTotal(),greaterThanOrEqualTo(10L));
-
+        assertThat(_statsHandler.getDispatchedTimeTotal(), greaterThanOrEqualTo(dispatchTime * 3 / 4));
     }
 
     @Test
     public void testSuspendComplete() throws Exception
     {
+        final long dispatchTime = 10;
         final AtomicReference<AsyncContext> asyncHolder = new AtomicReference<>();
-        final CyclicBarrier barrier[] = { new CyclicBarrier(2), new CyclicBarrier(2), new CyclicBarrier(2)};
+        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);
+
+                    Thread.sleep(dispatchTime);
+
                     if (asyncHolder.get() == null)
                     {
-                        AsyncContext async=request.startAsync();
-                        async.setTimeout(1000);
+                        AsyncContext async = request.startAsync();
                         asyncHolder.set(async);
-                        asyncHolder.get().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 { barrier[2].await(); } catch(Exception e) {}                
-                            }
-                        });
                     }
-
                 }
                 catch (Exception x)
                 {
-                    Thread.currentThread().interrupt();
-                    throw (IOException)new IOException().initCause(x);
+                    throw new ServletException(x);
                 }
                 finally
                 {
@@ -528,11 +489,8 @@
                     {
                         barrier[1].await();
                     }
-                    catch (Exception x)
+                    catch (Exception ignored)
                     {
-                        x.printStackTrace();
-                        Thread.currentThread().interrupt();
-                        fail();
                     }
                 }
 
@@ -541,21 +499,18 @@
         _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, _statistics.getConnectionsOpen());
-
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(1, _statsHandler.getRequestsActive());
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(1, _statsHandler.getDispatchedActive());
 
-
         barrier[1].await();
         assertTrue(_latchHandler.await());
         assertNotNull(asyncHolder.get());
@@ -565,9 +520,39 @@
         assertEquals(1, _statsHandler.getDispatched());
         assertEquals(0, _statsHandler.getDispatchedActive());
 
-        Thread.sleep(10);
+        asyncHolder.get().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
+                {
+                    latch.countDown();
+                }
+                catch (Exception ignored)
+                {
+                }
+            }
+        });
+        long requestTime = 20;
+        Thread.sleep(requestTime);
         asyncHolder.get().complete();
-        barrier[2].await();
+        latch.await();
 
         assertEquals(1, _statsHandler.getRequests());
         assertEquals(0, _statsHandler.getRequestsActive());
@@ -579,17 +564,16 @@
         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
@@ -602,7 +586,7 @@
         @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);
@@ -615,12 +599,12 @@
 
         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() throws InterruptedException
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 76e373f..03d4e32 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
@@ -125,7 +125,6 @@
     {
         server.setHandler(new HelloWorldHandler());
         server.start();
-        server.dumpStdErr();
 
         SSLContext ctx=SSLContext.getInstance("TLS");
         ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom());
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 f95a58d..ac1dca9 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
@@ -27,7 +27,6 @@
 import java.nio.ByteBuffer;
 import java.util.Enumeration;
 import java.util.List;
-import java.util.Map;
 
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
@@ -42,9 +41,8 @@
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpMethod;
 import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.PathMap.MappedEntry;
 import org.eclipse.jetty.io.WriterOutputStream;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.HttpChannel;
 import org.eclipse.jetty.server.HttpOutput;
 import org.eclipse.jetty.server.InclusiveByteRange;
 import org.eclipse.jetty.server.ResourceCache;
@@ -160,7 +158,6 @@
     private ServletHandler _servletHandler;
     private ServletHolder _defaultHolder;
 
-
     /* ------------------------------------------------------------ */
     @Override
     public void init()
@@ -268,7 +265,7 @@
             throw new UnavailableException(e.toString());
         }
 
-        _servletHandler= (ServletHandler) _contextHandler.getChildHandlerByClass(ServletHandler.class);
+        _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
         for (ServletHolder h :_servletHandler.getServlets())
             if (h.getServletInstance()==this)
                 _defaultHolder=h;
@@ -343,6 +340,7 @@
      * @param pathInContext The path to find a resource for.
      * @return The resource to serve.
      */
+    @Override
     public Resource getResource(String pathInContext)
     {
         Resource r=null;
@@ -632,7 +630,7 @@
 
             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;
@@ -828,24 +826,10 @@
             boolean include,
             Resource resource,
             HttpContent content,
-            Enumeration reqRanges)
+            Enumeration<String> reqRanges)
     throws IOException
     {
-        boolean direct;
-        long content_length;
-        if (content==null)
-        {
-            direct=false;
-            content_length=resource.length();
-        }
-        else
-        {
-            // TODO sometimes we should be direct!
-            Connector connector = HttpChannel.getCurrentHttpChannel().getConnector();
-            direct=false;
-            content_length=content.getContentLength();
-        }
-
+        final long content_length = (content==null)?resource.length():content.getContentLength();
 
         // Get the output stream (or writer)
         OutputStream out =null;
@@ -884,17 +868,8 @@
                     }
                     else
                     {
-                        ByteBuffer buffer = direct?content.getDirectBuffer():content.getIndirectBuffer();
-                        if (buffer!=null)
-                        {
-                            writeHeaders(response,content,content_length);
-                            ((HttpOutput)out).sendContent(buffer);
-                        }
-                        else
-                        {
-                            writeHeaders(response,content,content_length);
-                            resource.writeTo(out,0,content_length);
-                        }
+                        writeHeaders(response,content,content_length);
+                        ((HttpOutput)out).sendContent(content.getResource());
                     }
                 }
                 else
@@ -914,7 +889,7 @@
         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)
@@ -931,8 +906,7 @@
             //  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);
@@ -947,7 +921,7 @@
             //  content-length header
             //
             writeHeaders(response,content,-1);
-            String mimetype=(content.getContentType()==null?null:content.getContentType().toString());
+            String mimetype=(content==null||content.getContentType()==null?null:content.getContentType().toString());
             if (mimetype==null)
                 LOG.warn("Unknown mimetype for "+request.getRequestURI());
             MultiPartOutputStream multi = new MultiPartOutputStream(out);
@@ -971,7 +945,7 @@
             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)+
@@ -986,7 +960,7 @@
 
             for (int i=0;i<ranges.size();i++)
             {
-                InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
+                InclusiveByteRange ibr =  ranges.get(i);
                 multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
 
                 long start=ibr.getFirst(content_length);
@@ -1023,7 +997,6 @@
 
     /* ------------------------------------------------------------ */
     protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
-    throws IOException
     {        
         if (content.getContentType()!=null && response.getContentType()==null)
             response.setContentType(content.getContentType().toString());
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 cfb87fc..fd8f43e 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
@@ -24,6 +24,7 @@
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 
 import org.eclipse.jetty.server.Dispatcher;
 import org.eclipse.jetty.server.HttpConnection;
@@ -96,7 +97,11 @@
     @Override
     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
     {           
-        Request request=(req instanceof Request)?(Request)req:HttpConnection.getCurrentConnection().getHttpChannel().getRequest();
+        HttpServletRequest request = null;
+        if (req instanceof HttpServletRequest)
+            request = (HttpServletRequest)req;
+        else
+            throw new ServletException("Request not HttpServletRequest");
 
         String servletPath=null;
         String pathInfo=null;
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 9baefac..3dcd213 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
@@ -113,10 +113,10 @@
     private MultiMap<FilterMapping> _filterNameMappings;
 
     private final Map<String,ServletHolder> _servletNameMap=new HashMap<>();
-    private PathMap _servletPathMap;
+    private PathMap<ServletHolder> _servletPathMap;
 
-    protected final ConcurrentMap _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
-    protected final Queue[] _chainLRU = new Queue[FilterMapping.ALL];
+    protected final ConcurrentMap<?, ?> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+    protected final Queue<?>[] _chainLRU = new Queue[FilterMapping.ALL];
 
 
     /* ------------------------------------------------------------ */
@@ -208,6 +208,9 @@
         _filterNameMappings=null;
 
         _servletPathMap=null;
+        
+        _matchBeforeIndex=-1;
+        _matchAfterIndex=-1;
     }
 
     /* ------------------------------------------------------------ */
@@ -250,7 +253,7 @@
      * @param pathInContext Path within _context.
      * @return PathMap Entries pathspec to ServletHolder
      */
-    public PathMap.MappedEntry getHolderEntry(String pathInContext)
+    public PathMap.MappedEntry<ServletHolder> getHolderEntry(String pathInContext)
     {
         if (_servletPathMap==null)
             return null;
@@ -331,10 +334,10 @@
         if (target.startsWith("/"))
         {
             // Look for the servlet by path
-            PathMap.MappedEntry 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= entry.getKey();
                 String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
@@ -342,8 +345,8 @@
 
                 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
                 {
@@ -615,8 +618,8 @@
             if (filters.size() > 0)
                 chain= new CachedChain(filters, servletHolder);
 
-            final Map<String,FilterChain> cache=_chainCache[dispatch];
-            final Queue<String> lru=_chainLRU[dispatch];
+            final Map<String,FilterChain> cache=(Map<String, FilterChain>)_chainCache[dispatch];
+            final Queue<String> lru=(Queue<String>)_chainLRU[dispatch];
 
         	// Do we have too many cached chains?
         	while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
@@ -718,7 +721,7 @@
                 {
                     if (servlet.getClassName() == null && servlet.getForcedPath() != null)
                     {
-                        ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlet.getForcedPath());
+                        ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath());
                         if (forced_holder == null || forced_holder.getClassName() == null)
                         {
                             mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath()));
@@ -1053,7 +1056,7 @@
     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)
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 1e50dd1..77b3d4b 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
@@ -385,8 +385,8 @@
         if (o==null)
             return;
         Servlet servlet =  ((Servlet)o);
-        servlet.destroy();
         getServletHandler().destroyServlet(servlet);
+        servlet.destroy();
     }
 
     /* ------------------------------------------------------------ */
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 b66764b..a34d185 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
@@ -103,6 +103,7 @@
         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
@@ -121,6 +122,16 @@
         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:",br.readLine());
         Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/servletPath",br.readLine());
+
+        try
+        {
+            __asyncContext.getRequest();
+            Assert.fail();
+        }
+        catch (IllegalStateException e)
+        {
+            
+        }
     }
 
     @Test
@@ -193,8 +204,11 @@
     @Test
     public void testDispatchRequestResponse() throws Exception
     {
-        String request = "GET /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 /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);
 
@@ -233,10 +247,12 @@
         }
     }
 
+    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
         {
@@ -253,13 +269,14 @@
                 {
                     wrapped = true;
                     asyncContext = request.startAsync(request, new Wrapper(response));
+                    __asyncContext=asyncContext;
                 }
                 else
                 {
                     asyncContext = request.startAsync();
+                    __asyncContext=asyncContext;
                 }
 
-
                 new Thread(new DispatchingRunnable(asyncContext, wrapped)).start();
             }
         }
@@ -301,12 +318,14 @@
             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));
 
@@ -323,6 +342,7 @@
         {
             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));
         }
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 d5ad4ee..c2703fe 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,7 +19,6 @@
 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;
@@ -345,6 +344,45 @@
         Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
     }
     
+
+    @Test
+    public void testAsyncRead() throws Exception
+    {
+        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("ISO-8859-1"));
+            Thread.sleep(500);
+            socket.getOutputStream().write(body.getBytes("ISO-8859-1"),0,2);
+            Thread.sleep(500);
+            socket.getOutputStream().write(body.getBytes("ISO-8859-1"),2,8);
+            socket.getOutputStream().write(close.getBytes("ISO-8859-1"));
+            
+            String response = IO.toString(socket.getInputStream());
+            assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
+            assertContains(
+                "history: REQUEST\r\n"+
+                "history: initial\r\n"+
+                "history: suspend\r\n"+
+                "history: async-read=10\r\n"+
+                "history: resume\r\n"+
+                "history: ASYNC\r\n"+
+                "history: !initial\r\n"+
+                "history: onComplete\r\n",response);
+        }
+    }
+    
+    
     public synchronized String process(String query,String content) throws Exception
     {
         String request = "GET /ctx/path/info";
@@ -364,9 +402,8 @@
         
         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"));
 
@@ -379,11 +416,10 @@
             throw e;
         }        
         
-        // System.err.println(response);
-
         return response;
     }
-    
+
+
        
     
     private static class AsyncServlet extends HttpServlet
@@ -429,7 +465,7 @@
             
             if (request.getDispatcherType()==DispatcherType.REQUEST)
             {
-                ((HttpServletResponse)response).addHeader("history","initial");
+                response.addHeader("history","initial");
                 if (read_before>0)
                 {
                     byte[] buf=new byte[read_before];
@@ -442,6 +478,30 @@
                     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)
                 {
@@ -449,7 +509,7 @@
                     if (suspend_for>0)
                         async.setTimeout(suspend_for);
                     async.addListener(__listener);
-                    ((HttpServletResponse)response).addHeader("history","suspend");
+                    response.addHeader("history","suspend");
                     
                     if (complete_after>0)
                     {
@@ -527,7 +587,7 @@
             }
             else
             {
-                ((HttpServletResponse)response).addHeader("history","!initial");
+                response.addHeader("history","!initial");
 
                 if (suspend2_for>=0 && request.getAttribute("2nd")==null)
                 {
@@ -540,7 +600,7 @@
                         async.setTimeout(suspend2_for);
                     }
                     // continuation.addContinuationListener(__listener);
-                    ((HttpServletResponse)response).addHeader("history","suspend");
+                    response.addHeader("history","suspend");
 
                     if (complete2_after>0)
                     {
@@ -581,7 +641,7 @@
                             @Override
                             public void run()
                             {
-                                ((HttpServletResponse)response).addHeader("history","resume");
+                                response.addHeader("history","resume");
                                 async.dispatch();
                             }
                         };
@@ -592,7 +652,7 @@
                     }
                     else if (resume2_after==0)
                     {
-                        ((HttpServletResponse)response).addHeader("history","dispatch");
+                        response.addHeader("history","dispatch");
                         async.dispatch();
                     }
                 }
@@ -633,15 +693,11 @@
         @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
diff --git a/jetty-spdy/pom.xml b/jetty-spdy/pom.xml
index 8b3fa2d..dd13d3c 100644
--- a/jetty-spdy/pom.xml
+++ b/jetty-spdy/pom.xml
@@ -89,6 +89,18 @@
                 <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>
     </profiles>
 
     <modules>
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 fc6c4d9..a8455d1 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
@@ -59,7 +59,9 @@
      */
     public void setStreamFrameListener(StreamFrameListener listener);
 
-    //TODO: javadoc thomas
+    /**
+     * @return the stream frame listener associated to this stream
+     */
     public StreamFrameListener getStreamFrameListener();
 
     /**
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 7f50304..de411b4 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
@@ -32,7 +32,7 @@
     private int associatedStreamId;
     
     public PushSynInfo(int associatedStreamId, PushInfo pushInfo){
-        super(pushInfo.getHeaders(), pushInfo.isClose());
+        super(pushInfo.getTimeout(), pushInfo.getUnit(), pushInfo.getHeaders(), pushInfo.isClose(), (byte)0);
         this.associatedStreamId = associatedStreamId;
     }
     
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
index 1a9d686..30d443f 100644
--- 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
@@ -20,6 +20,7 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -46,6 +47,7 @@
 import org.eclipse.jetty.util.BlockingCallback;
 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;
@@ -74,11 +76,21 @@
         this.requestHeaders = requestHeaders;
     }
 
+    protected Stream getStream()
+    {
+        return stream;
+    }
+
+    protected Fields getRequestHeaders()
+    {
+        return requestHeaders;
+    }
+
     @Override
     public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
     {
         if (LOG.isDebugEnabled())
-            LOG.debug("send  {} {} {} {} last={}", this, stream, info, BufferUtil.toDetailString(content), lastContent);
+            LOG.debug("Sending {} {} {} {} last={}", this, stream, info, BufferUtil.toDetailString(content), lastContent);
 
         if (stream.isClosed() || stream.isReset())
         {
@@ -86,7 +98,6 @@
             callback.failed(exception);
             return;
         }
-        // new Throwable().printStackTrace();
 
         // info==null content==null lastContent==false          should not happen
         // info==null content==null lastContent==true           signals no more content - complete
@@ -149,7 +160,7 @@
         {
             // Is the stream still open?
             if (stream.isClosed() || stream.isReset())
-                // tell the callback about the EOF 
+                // tell the callback about the EOF
                 callback.failed(new EofException("stream closed"));
             else
                 // send the data and let it call the callback
@@ -171,7 +182,6 @@
         else
             // No data and no close so tell callback we are completed
             callback.succeeded();
-
     }
 
     @Override
@@ -188,11 +198,10 @@
         }
     }
 
-
     @Override
     public void completed()
     {
-        LOG.debug("completed");
+        LOG.debug("Completed");
     }
 
     private void reply(Stream stream, ReplyInfo replyInfo)
@@ -206,65 +215,160 @@
         short version = stream.getSession().getVersion();
         if (responseHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value().startsWith("200") && !stream.isClosed())
         {
-            // We have a 200 OK with some content to send, check the push strategy
-            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));
             Set<String> pushResources = pushStrategy.apply(stream, requestHeaders, responseHeaders);
-
-            for (String pushResource : pushResources)
+            if (pushResources.size() > 0)
             {
-                Fields pushHeaders = createPushHeaders(scheme, host, pushResource);
-                final Fields pushRequestHeaders = createRequestHeaders(scheme, host, uri, pushResource);
-
-                // TODO: handle the timeout better
-                stream.push(new PushInfo(0, TimeUnit.MILLISECONDS, pushHeaders, false), new Promise.Adapter<Stream>()
-                {
-                    @Override
-                    public void succeeded(Stream pushStream)
-                    {
-                        HttpChannelOverSPDY pushChannel = newHttpChannelOverSPDY(pushStream, pushRequestHeaders);
-                        pushChannel.requestStart(pushRequestHeaders, true);
-                    }
-                });
+                PushResourceCoordinator pushResourceCoordinator = new PushResourceCoordinator(pushResources);
+                pushResourceCoordinator.coordinate();
             }
         }
     }
 
-    private Fields createRequestHeaders(Fields.Field scheme, Fields.Field host, Fields.Field uri, String pushResourcePath)
+    private static class PushHttpTransportOverSPDY extends HttpTransportOverSPDY
     {
-        final Fields newRequestHeaders = new Fields(requestHeaders, false);
-        short version = stream.getSession().getVersion();
-        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.value() + "://" + host.value() + uri.value();
-        newRequestHeaders.put("referer", referrer);
-        newRequestHeaders.put("x-spdy-push", "true");
-        return newRequestHeaders;
-    }
+        private final PushResourceCoordinator coordinator;
 
-    private Fields createPushHeaders(Fields.Field scheme, Fields.Field host, String pushResourcePath)
-    {
-        final Fields pushHeaders = new Fields();
-        short version = stream.getSession().getVersion();
-        if (version == SPDY.V2)
-            pushHeaders.put(HTTPSPDYHeader.URI.name(version), scheme.value() + "://" + host.value() + pushResourcePath);
-        else
+        private PushHttpTransportOverSPDY(Connector connector, HttpConfiguration configuration, EndPoint endPoint,
+                                          PushStrategy pushStrategy, Stream stream, Fields requestHeaders,
+                                          PushResourceCoordinator coordinator)
         {
-            pushHeaders.put(HTTPSPDYHeader.URI.name(version), pushResourcePath);
-            pushHeaders.put(scheme);
-            pushHeaders.put(host);
+            super(connector, configuration, endPoint, pushStrategy, stream, requestHeaders);
+            this.coordinator = coordinator;
         }
-        return pushHeaders;
+
+        @Override
+        public void completed()
+        {
+            Stream stream = getStream();
+            LOG.debug("Resource pushed for {} on {}",
+                    getRequestHeaders().get(HTTPSPDYHeader.URI.name(stream.getSession().getVersion())), stream);
+            coordinator.complete();
+        }
     }
 
-    private HttpChannelOverSPDY newHttpChannelOverSPDY(Stream pushStream, Fields pushRequestHeaders)
+    private class PushResourceCoordinator
     {
-        HttpTransport transport = new HttpTransportOverSPDY(connector, configuration, endPoint, pushStrategy, pushStream, pushRequestHeaders);
-        HttpInputOverSPDY input = new HttpInputOverSPDY();
-        return new HttpChannelOverSPDY(connector, configuration, endPoint, transport, input, pushStream);
+        private final Queue<PushResource> queue = new ConcurrentArrayQueue<>();
+        private final Set<String> resources;
+        private boolean active;
+
+        private PushResourceCoordinator(Set<String> resources)
+        {
+            this.resources = resources;
+        }
+
+        private void coordinate()
+        {
+            // 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()
+        {
+            PushResource resource;
+            synchronized (this)
+            {
+                if (active)
+                    return;
+                resource = queue.poll();
+                if (resource == null)
+                    return;
+                active = true;
+            }
+            HttpChannelOverSPDY pushChannel = newHttpChannelOverSPDY(resource.getPushStream(), resource.getPushRequestHeaders());
+            pushChannel.requestStart(resource.getPushRequestHeaders(), true);
+        }
+
+        private HttpChannelOverSPDY newHttpChannelOverSPDY(Stream pushStream, Fields pushRequestHeaders)
+        {
+            HttpTransport transport = new PushHttpTransportOverSPDY(connector, configuration, endPoint, pushStrategy,
+                    pushStream, pushRequestHeaders, this);
+            HttpInputOverSPDY input = new HttpInputOverSPDY();
+            return new HttpChannelOverSPDY(connector, configuration, endPoint, transport, input, pushStream);
+        }
+
+        private void pushResource(String pushResource)
+        {
+            final short version = stream.getSession().getVersion();
+            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.Adapter<Stream>()
+            {
+                @Override
+                public void succeeded(Stream pushStream)
+                {
+                    LOG.debug("Headers pushed for {} on {}", pushHeaders.get(HTTPSPDYHeader.URI.name(version)), pushStream);
+                    queue.offer(new PushResource(pushStream, pushRequestHeaders));
+                    sendNextResourceData();
+                }
+            });
+        }
+
+        private Fields createRequestHeaders(Fields.Field scheme, Fields.Field host, Fields.Field uri, String pushResourcePath)
+        {
+            final Fields newRequestHeaders = new Fields(requestHeaders, false);
+            short version = stream.getSession().getVersion();
+            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.value() + "://" + host.value() + uri.value();
+            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();
+            short version = stream.getSession().getVersion();
+            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);
+            }
+            return pushHeaders;
+        }
+
+        private void complete()
+        {
+            synchronized (this)
+            {
+                active = false;
+            }
+            sendNextResourceData();
+        }
+    }
+
+    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;
+        }
     }
 }
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
index 0e905c7..43bb3cc 100644
--- 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
@@ -34,6 +34,8 @@
     /**
      * <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
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
index be893aa..2178869 100644
--- 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
@@ -27,6 +27,7 @@
 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;
@@ -262,7 +263,7 @@
     private class MainResource
     {
         private final String name;
-        private final Set<String> resources = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+        private final CopyOnWriteArraySet<String> resources = new CopyOnWriteArraySet<>();
         private final AtomicLong firstResourceAdded = new AtomicLong(-1);
 
         private MainResource(String name)
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
index e960d12..2c05ec7 100644
--- 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
@@ -158,10 +158,9 @@
     }
 
     @Override
-    public boolean earlyEOF()
+    public void earlyEOF()
     {
         // TODO
-        return false;
     }
 
     @Override
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
index 099f736..c2e1b7d 100644
--- 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
@@ -22,9 +22,13 @@
 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;
 
@@ -41,6 +45,8 @@
 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;
@@ -56,6 +62,7 @@
 import org.junit.Test;
 
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertThat;
 
 public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
@@ -159,25 +166,186 @@
     public void testMaxConcurrentStreamsToDisablePush() throws Exception
     {
         final CountDownLatch pushReceivedLatch = new CountDownLatch(1);
-        Session session = sendMainRequestAndCSSRequest(new SessionFrameListener.Adapter()
+
+        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);
+
+        session.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
         {
             @Override
-            public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
+            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));
+    }
+
+    @Test
+    public void testPushResourceOrder() throws Exception
+    {
+        final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);
+
+        Session pushCacheBuildSession = startClient(version, serverAddress, null);
+
+        sendRequest(pushCacheBuildSession, mainRequestHeaders, null, null);
+        sendRequest(pushCacheBuildSession, associatedCSSRequestHeaders, null, null);
+        sendRequest(pushCacheBuildSession, associatedJSRequestHeaders, null, null);
+        sendRequest(pushCacheBuildSession, createHeaders("/image1.jpg", mainResource), null, null);
+        sendRequest(pushCacheBuildSession, createHeaders("/image2.jpg", mainResource), null, null);
+
+        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)).value();
+                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 super.onPush(stream, pushInfo);
+            }
+        });
+
+        assertThat("All expected push resources have been received", allExpectedPushesReceivedLatch.await(5,
+                TimeUnit.SECONDS), is(true));
+    }
+
+    @Test
+    public void testThatPushResourcesAreUnique() throws Exception
+    {
+        final CountDownLatch pushReceivedLatch = new CountDownLatch(2);
+        sendMainRequestAndCSSRequest(null);
+        sendMainRequestAndCSSRequest(null);
+
+        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;
             }
         });
 
-//        Settings settings = new Settings();
-//        settings.put(new Settings.Setting(Settings.ID.MAX_CONCURRENT_STREAMS, 0));
-//        SettingsInfo settingsInfo = new SettingsInfo(settings);
-//
-//        session.settings(settingsInfo);
+        assertThat("style.css has been pushed only once", pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+    }
 
-        sendRequest(session, mainRequestHeaders, null, null);
+    @Test
+    public void testPushResourceAreSentNonInterleaved() throws Exception
+    {
+        final CountDownLatch allExpectedPushesReceivedLatch = new CountDownLatch(4);
+        final CountDownLatch allPushDataReceivedLatch = new CountDownLatch(4);
+        final CopyOnWriteArrayList<Integer> dataReceivedOrder = new CopyOnWriteArrayList<>();
 
-        assertThat(pushReceivedLatch.await(1, TimeUnit.SECONDS), is(false));
+        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);
+            }
+        });
+        Session pushCacheBuildSession = startClient(version, bigResponseServerAddress, null);
+
+        Fields mainResourceHeaders = createHeadersWithoutReferrer(mainResource);
+        sendRequest(pushCacheBuildSession, mainResourceHeaders, null, null);
+        sendRequest(pushCacheBuildSession, createHeaders("/style.css", mainResource), null, null);
+        sendRequest(pushCacheBuildSession, createHeaders("/javascript.js", mainResource), null, null);
+        sendRequest(pushCacheBuildSession, createHeaders("/image1.jpg", mainResource), null, null);
+        sendRequest(pushCacheBuildSession, createHeaders("/image2.jpg", mainResource), null, null);
+
+        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)).value();
+                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
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
index d11c84a..7d038bc 100644
--- 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
@@ -18,10 +18,6 @@
 
 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;
 
@@ -35,6 +31,10 @@
 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;
+
 @RunWith(MockitoJUnitRunner.class)
 public class ReferrerPushStrategyUnitTest
 {
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
index 4286a47..30da0a8 100644
--- a/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
+++ b/jetty-spdy/spdy-http-server/src/test/resources/jetty-logging.properties
@@ -4,4 +4,5 @@
 #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.mortbay.LEVEL=DEBUG
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 0be66d8..3ea743a 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
@@ -39,6 +39,7 @@
 import java.net.InetAddress;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
+import java.net.URL;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -50,6 +51,8 @@
 import java.util.Properties;
 import java.util.Set;
 
+import javax.naming.OperationNotSupportedException;
+
 /*-------------------------------------------*/
 /**
  * <p>
@@ -175,6 +178,12 @@
                 stop(port,key,timeout);
                 return null;
             }
+            
+            if (arg.startsWith("--download="))
+            {
+                download(arg);
+                continue;
+            }
 
             if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
             {
@@ -302,6 +311,53 @@
         return xmls;
     }
 
+    private void download(String arg)
+    {
+        try
+        {
+            String[] split = arg.split(":",3);
+            if (split.length!=3 || "http".equalsIgnoreCase(split[0]) || !split[1].startsWith("//"))
+                throw new IllegalArgumentException("Not --download=<http uri>:<location>");
+            
+            String location=split[2];
+            if (File.separatorChar!='/')
+                location.replaceAll("/",File.separator);
+            File file = new File(location);
+            
+            if (Config.isDebug())
+                System.err.println("Download to "+file.getAbsolutePath()+(file.exists()?" Exists!":""));
+            if (file.exists())
+                return;
+            
+            URL url = new URL(split[0].substring(11)+":"+split[1]);
+
+            System.err.println("DOWNLOAD: "+url+" to "+location);
+
+            if (!file.getParentFile().exists())
+                file.getParentFile().mkdirs();
+
+            byte[] buf=new byte[8192];
+            try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);)
+            {
+                while(true)
+                {
+                    int len = in.read(buf);
+
+                    if (len>0)
+                        out.write(buf,0,len);
+                    if (len<0)
+                        break;
+                }
+            }
+        }
+        catch(Exception e)
+        {
+            System.err.println("ERROR: processing "+arg+"\n"+e);
+            e.printStackTrace();
+            usageExit(EXIT_USAGE);
+        }
+    }
+
     private void usage()
     {
         String usageResource = "org/eclipse/jetty/start/usage.txt";
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 7272c5f..e25a73d 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
@@ -45,6 +45,10 @@
                    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.
+                   
+  --download=<http-uri>:location
+                   If the file does not exist at the given location, then
+                   download it from the given http URI
 
 System Properties:
   These are set with a command line like "java -Dname=value ..." and are
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
index 312bd96..ba5c9f9 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java
@@ -131,8 +131,6 @@
                     _tree[row]=c;
                     _tree[last]=(char)t;
                     last=row+EQ;
-                    t=0;
-                    break;
                 }
 
                 int row=ROW_SIZE*t;
@@ -419,4 +417,22 @@
     {
         return _rows+1==_key.length;
     }
+    
+    
+    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/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java
index a3e549e..ddae8d0 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
@@ -657,6 +657,30 @@
         return false;
     }
     
+    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 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+            url.append(':').append(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 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+                url.append(':').append(port);
+        }
+    }
 }
 
 
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
index 7c9e971..2751fc8 100644
--- 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
@@ -22,8 +22,9 @@
 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
+public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener
 {
     private ByteBuffer remainingBuffer;
 
@@ -43,6 +44,7 @@
         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/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java
index b37a02a..c24a17b 100644
--- 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
@@ -39,11 +39,12 @@
 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.client.io.HttpResponseHeaderParser.ParseException;
 import org.eclipse.jetty.websocket.common.AcceptHash;
 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.
@@ -92,7 +93,7 @@
         this.request = connectPromise.getRequest();
 
         // Setup the parser
-        this.parser = new HttpResponseHeaderParser();
+        this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse());
     }
 
     public void disconnect(boolean onlyOutput)
@@ -173,7 +174,7 @@
                     {
                         LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
                     }
-                    ClientUpgradeResponse resp = parser.parse(buffer);
+                    ClientUpgradeResponse resp = (ClientUpgradeResponse)parser.parse(buffer);
                     if (resp != null)
                     {
                         // Got a response!
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
index f404e6c..ecfd50c 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java
@@ -104,8 +104,8 @@
             // Make sure idle timeout takes less than 5 total seconds
             Assert.assertThat("Idle Timeout",dur,lessThanOrEqualTo(5000L));
 
-            // Client should see a close event, with status NO_CLOSE
-            wsocket.assertCloseCode(StatusCode.NORMAL);
+            // Client should see a close event, with status SHUTDOWN
+            wsocket.assertCloseCode(StatusCode.SHUTDOWN);
         }
         finally
         {
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
index c55c103..eb1c4ae 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TrackingSocket.java
@@ -20,13 +20,12 @@
 
 import static org.hamcrest.Matchers.*;
 
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Exchanger;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import org.eclipse.jetty.util.BlockingArrayQueue;
+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;
@@ -46,8 +45,8 @@
     public CountDownLatch openLatch = new CountDownLatch(1);
     public CountDownLatch closeLatch = new CountDownLatch(1);
     public CountDownLatch dataLatch = new CountDownLatch(1);
-    public BlockingQueue<String> messageQueue = new BlockingArrayQueue<String>();
-    public BlockingQueue<Throwable> errorQueue = new BlockingArrayQueue<>();
+    public EventQueue<String> messageQueue = new EventQueue<>();
+    public EventQueue<Throwable> errorQueue = new EventQueue<>();
 
     public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
     {
@@ -93,29 +92,9 @@
         Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
     }
 
-    public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException
+    public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
     {
-        long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
-        long now = System.currentTimeMillis();
-        long expireOn = now + msDur;
-        LOG.debug("Await Message.. Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
-
-        while (messageQueue.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,
-                        messageQueue.size()));
-            }
-        }
+        messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
     }
 
     public void clear()
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
index b4d5ea4..cb19368 100644
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/blockhead/BlockheadServer.java
@@ -54,6 +54,7 @@
 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;
@@ -219,6 +220,12 @@
             }
             WebSocketFrame copy = new WebSocketFrame(frame);
             incomingFrames.incomingFrame(copy);
+
+            if (frame.getType() == Type.CLOSE)
+            {
+                CloseInfo close = new CloseInfo(frame);
+                LOG.debug("Close frame: {}",close);
+            }
         }
 
         @Override
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java
deleted file mode 100644
index d2f8822..0000000
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/internal/io/HttpResponseHeaderParserTest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://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.internal.io;
-
-import static org.hamcrest.Matchers.*;
-
-import java.nio.ByteBuffer;
-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.eclipse.jetty.websocket.api.UpgradeResponse;
-import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser;
-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.getBytes(line,StringUtil.__UTF8)));
-    }
-
-    @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
-        HttpResponseHeaderParser parser = new HttpResponseHeaderParser();
-        UpgradeResponse response = parser.parse(buf);
-        Assert.assertThat("Response",response,notNullValue());
-
-        Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200));
-        Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK"));
-
-        Assert.assertThat("Response.header[age]",response.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
-        HttpResponseHeaderParser parser = new HttpResponseHeaderParser();
-        UpgradeResponse response;
-
-        // Parse small 1
-        response = parser.parse(small1);
-        Assert.assertThat("Small 1",response,nullValue());
-
-        // Parse small 2
-        response = parser.parse(small2);
-        Assert.assertThat("Small 2",response,nullValue());
-
-        // Parse small 3
-        response = parser.parse(small3);
-        Assert.assertThat("Small 3",response,notNullValue());
-
-        Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200));
-        Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK"));
-
-        Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097"));
-    }
-}
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
index d40073f..aefc719 100644
--- 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
@@ -18,13 +18,43 @@
 
 package org.eclipse.jetty.websocket.common;
 
+import org.eclipse.jetty.websocket.common.io.IOState;
+
 /**
  * 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 IOState.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/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java
index 8abdfff..7ca103d 100644
--- 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
@@ -48,9 +48,11 @@
 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;
 
-@ManagedObject
-public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames
+@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;
@@ -80,6 +82,8 @@
         this.outgoingHandler = connection;
         this.incomingHandler = websocket;
 
+        this.connection.getIOState().addListener(this);
+
         // Get the parameter map (use the jetty MultiMap to do this right)
         MultiMap<String> params = new MultiMap<>();
         String query = requestURI.getQuery();
@@ -254,6 +258,11 @@
         return remote.getInetSocketAddress();
     }
 
+    public URI getRequestURI()
+    {
+        return requestURI;
+    }
+
     @Override
     public UpgradeRequest getUpgradeRequest()
     {
@@ -281,12 +290,11 @@
     @Override
     public void incomingError(WebSocketException e)
     {
-        if (connection.getIOState().isInputClosed())
+        if (connection.getIOState().isInputAvailable())
         {
-            return; // input is closed
+            // Forward Errors to User WebSocket Object
+            websocket.incomingError(e);
         }
-        // Forward Errors to User WebSocket Object
-        websocket.incomingError(e);
     }
 
     /**
@@ -295,13 +303,11 @@
     @Override
     public void incomingFrame(Frame frame)
     {
-        if (connection.getIOState().isInputClosed())
+        if (connection.getIOState().isInputAvailable())
         {
-            return; // input is closed
+            // Forward Frames Through Extension List
+            incomingHandler.incomingFrame(frame);
         }
-
-        // Forward Frames Through Extension List
-        incomingHandler.incomingFrame(frame);
     }
 
     @Override
@@ -332,6 +338,24 @@
         websocket.onClose(new CloseInfo(statusCode,reason));
     }
 
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        if (state == ConnectionState.CLOSED)
+        {
+            IOState ioState = this.connection.getIOState();
+            // The session only cares about abnormal close, as we need to notify
+            // the endpoint of this close scenario.
+            if (ioState.wasAbnormalClose())
+            {
+                CloseInfo close = ioState.getCloseInfo();
+                LOG.debug("Detected abnormal close: {}",close);
+                // notify local endpoint
+                notifyClose(close.getStatusCode(),close.getReason());
+            }
+        }
+    }
+
     /**
      * Open/Activate the session
      * 
@@ -345,12 +369,18 @@
             return;
         }
 
+        // Upgrade success
+        connection.getIOState().onConnected();
+
         // Connect remote
         remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
 
         // Open WebSocket
         websocket.openSession(this);
 
+        // Open connection
+        connection.getIOState().onOpened();
+
         if (LOG.isDebugEnabled())
         {
             LOG.debug("open -> {}",dump());
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
index bf74188..d217937 100644
--- 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
@@ -103,16 +103,7 @@
                     onClose(close);
 
                     // process handshake
-                    if (session.getConnection().getIOState().onCloseHandshake(true))
-                    {
-                        // handshake resolved, disconnect.
-                        session.getConnection().disconnect();
-                    }
-                    else
-                    {
-                        // respond
-                        session.close(close.getStatusCode(),close.getReason());
-                    }
+                    session.getConnection().getIOState().onCloseRemote(close);
 
                     return;
                 }
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
index b8bd825..c3c42ce 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/mux/MuxChannel.java
@@ -38,11 +38,12 @@
 import org.eclipse.jetty.websocket.common.WebSocketSession;
 import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
 import org.eclipse.jetty.websocket.common.io.IOState;
+import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
 
 /**
  * MuxChannel, acts as WebSocketConnection for specific sub-channel.
  */
-public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken
+public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken, ConnectionStateListener
 {
     private static final Logger LOG = Log.getLogger(MuxChannel.class);
 
@@ -65,7 +66,7 @@
 
         this.suspendToken = new AtomicBoolean(false);
         this.ioState = new IOState();
-        ioState.setState(ConnectionState.CONNECTING);
+        this.ioState.addListener(this);
 
         this.inputClosed = new AtomicBoolean(false);
         this.outputClosed = new AtomicBoolean(false);
@@ -88,7 +89,6 @@
     @Override
     public void disconnect()
     {
-        this.ioState.setState(ConnectionState.CLOSED);
         // TODO: disconnect the virtual end-point?
     }
 
@@ -173,12 +173,18 @@
 
     public void onClose()
     {
-        this.ioState.setState(ConnectionState.CLOSED);
+    }
+
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        // TODO Auto-generated method stub
+
     }
 
     public void onOpen()
     {
-        this.ioState.setState(ConnectionState.OPEN);
+        this.ioState.onOpened();
     }
 
     /**
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
index f3dffcb..cf37519 100644
--- 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
@@ -56,11 +56,12 @@
 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 WebSocketConnection} within the framework of the new {@link Connection} framework of jetty-io
  */
-public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection
+public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener
 {
     private class FlushCallback implements Callback
     {
@@ -141,21 +142,6 @@
         }
     }
 
-    private class OnCloseCallback implements WriteCallback
-    {
-        @Override
-        public void writeFailed(Throwable x)
-        {
-            disconnect();
-        }
-
-        @Override
-        public void writeSuccess()
-        {
-            onWriteWebSocketClose();
-        }
-    }
-
     public static class Stats
     {
         private AtomicLong countFillInterestedEvents = new AtomicLong(0);
@@ -211,7 +197,7 @@
         this.extensions = new ArrayList<>();
         this.suspendToken = new AtomicBoolean(false);
         this.ioState = new IOState();
-        this.ioState.setState(ConnectionState.CONNECTING);
+        this.ioState.addListener(this);
         this.writeBytes = new WriteBytesProvider(generator,new FlushCallback());
         this.setInputBufferSize(policy.getInputBufferSize());
     }
@@ -247,12 +233,18 @@
     @Override
     public void disconnect()
     {
+        synchronized (writeBytes)
+        {
+            if (!writeBytes.isClosed())
+            {
+                writeBytes.close();
+            }
+        }
         disconnect(false);
     }
 
     public void disconnect(boolean onlyOutput)
     {
-        ioState.setState(ConnectionState.CLOSED);
         EndPoint endPoint = getEndPoint();
         // We need to gently close first, to allow
         // SSL close alerts to be sent by Jetty
@@ -276,18 +268,8 @@
      */
     private void enqueClose(int statusCode, String reason)
     {
-        synchronized (writeBytes)
-        {
-            // It is possible to get close events from many different sources.
-            // Make sure we only sent 1 over the network.
-            if (writeBytes.isClosed())
-            {
-                // already sent the close
-                return;
-            }
-        }
         CloseInfo close = new CloseInfo(statusCode,reason);
-        outgoingFrame(close.asFrame(),new OnCloseCallback());
+        ioState.onCloseLocal(close);
     }
 
     protected void execute(Runnable task)
@@ -438,11 +420,32 @@
     public void onClose()
     {
         super.onClose();
-        this.getIOState().setState(ConnectionState.CLOSED);
         writeBytes.close();
     }
 
     @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        LOG.debug("Connection State Change: {}",state);
+        switch (state)
+        {
+            case OPEN:
+                LOG.debug("fillInterested");
+                fillInterested();
+                break;
+            case CLOSED:
+                this.disconnect();
+                break;
+            case CLOSING:
+                CloseInfo close = ioState.getCloseInfo();
+                // append close frame
+                outgoingFrame(close.asFrame(),null);
+            default:
+                break;
+        }
+    }
+
+    @Override
     public void onFillable()
     {
         LOG.debug("{} onFillable()",policy.getBehavior());
@@ -482,18 +485,16 @@
     public void onOpen()
     {
         super.onOpen();
-        this.ioState.setState(ConnectionState.OPEN);
-        LOG.debug("fillInterested");
-        fillInterested();
+        this.ioState.onOpened();
     }
 
     @Override
     protected boolean onReadTimeout()
     {
-        LOG.info("Read Timeout");
+        LOG.debug("Read Timeout");
 
         IOState state = getIOState();
-        if ((state.getState() == ConnectionState.CLOSING) || (state.getState() == ConnectionState.CLOSED))
+        if ((state.getConnectionState() == ConnectionState.CLOSING) || (state.getConnectionState() == ConnectionState.CLOSED))
         {
             // close already initiated, extra timeouts not relevant
             // allow underlying connection and endpoint to disconnect on its own
@@ -501,24 +502,12 @@
         }
 
         // Initiate close - politely send close frame.
-        // Note: it is not possible in 100% of cases during read timeout to send this close frame.
         session.incomingError(new WebSocketTimeoutException("Timeout on Read"));
-        session.close(StatusCode.NORMAL,"Idle Timeout");
-
-        // Force closure of writeBytes
-        writeBytes.close();
+        close(StatusCode.SHUTDOWN,"Idle Timeout");
 
         return false;
     }
 
-    public void onWriteWebSocketClose()
-    {
-        if (ioState.onCloseHandshake(false))
-        {
-            disconnect();
-        }
-    }
-
     /**
      * Frame from API, User, or Internal implementation destined for network.
      */
@@ -550,6 +539,7 @@
                 else if (filled < 0)
                 {
                     LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
+                    ioState.onReadEOF();
                     return -1;
                 }
                 else
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
index e90ab7e..3836ae6 100644
--- 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
@@ -19,39 +19,79 @@
 package org.eclipse.jetty.websocket.common.io;
 
 import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 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.ConnectionState;
 
 /**
- * Simple state tracker for Input / Output and {@link 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 AtomicBoolean inputClosed;
-    private final AtomicBoolean outputClosed;
+    private final List<ConnectionStateListener> listeners = new CopyOnWriteArrayList<>();
+
+    private final AtomicBoolean inputAvailable;
+    private final AtomicBoolean outputAvailable;
+    private final AtomicReference<CloseHandshakeSource> closeHandshakeSource;
+    private final AtomicReference<CloseInfo> closeInfo;
 
     private final AtomicBoolean cleanClose;
-    private final AtomicBoolean remoteCloseInitiated;
-    private final AtomicBoolean localCloseInitiated;
 
+    /**
+     * Create a new IOState, initialized to {@link ConnectionState#CONNECTING}
+     */
     public IOState()
     {
         this.state = ConnectionState.CONNECTING;
-        this.inputClosed = new AtomicBoolean(false);
-        this.outputClosed = new AtomicBoolean(false);
-        this.remoteCloseInitiated = new AtomicBoolean(false);
-        this.localCloseInitiated = new AtomicBoolean(false);
+        this.inputAvailable = new AtomicBoolean(false);
+        this.outputAvailable = new AtomicBoolean(false);
+        this.closeHandshakeSource = new AtomicReference<>(CloseHandshakeSource.NONE);
+        this.closeInfo = new AtomicReference<>();
         this.cleanClose = new AtomicBoolean(false);
     }
 
+    public void addListener(ConnectionStateListener listener)
+    {
+        listeners.add(listener);
+    }
+
     public void assertInputOpen() throws IOException
     {
-        if (isInputClosed())
+        if (!isInputAvailable())
         {
             throw new IOException("Connection input is closed");
         }
@@ -59,15 +99,15 @@
 
     public void assertOutputOpen() throws IOException
     {
-        if (isOutputClosed())
+        if (!isOutputAvailable())
         {
             throw new IOException("Connection output is closed");
         }
     }
 
-    public boolean awaitClosed(long duration)
+    public CloseInfo getCloseInfo()
     {
-        return (isInputClosed() && isOutputClosed());
+        return closeInfo.get();
     }
 
     public ConnectionState getConnectionState()
@@ -75,90 +115,283 @@
         return state;
     }
 
-    public ConnectionState getState()
-    {
-        return state;
-    }
-
     public boolean isClosed()
     {
-        return (isInputClosed() && isOutputClosed());
+        synchronized (state)
+        {
+            return (state == ConnectionState.CLOSED);
+        }
     }
 
-    public boolean isCloseInitiated()
+    public boolean isInputAvailable()
     {
-        return remoteCloseInitiated.get() || localCloseInitiated.get();
-    }
-
-    public boolean isInputClosed()
-    {
-        return inputClosed.get();
+        return inputAvailable.get();
     }
 
     public boolean isOpen()
     {
-        return (getState() != ConnectionState.CLOSED);
+        return (getConnectionState() != ConnectionState.CLOSED);
     }
 
-    public boolean isOutputClosed()
+    public boolean isOutputAvailable()
     {
-        return outputClosed.get();
+        return outputAvailable.get();
+    }
+
+    private void notifyStateListeners(ConnectionState state)
+    {
+        for (ConnectionStateListener listener : listeners)
+        {
+            listener.onConnectionStateChange(state);
+        }
     }
 
     /**
-     * Test for if connection should disconnect or response on a close handshake.
-     * 
-     * @param incoming
-     *            true if incoming close
-     * @param close
-     *            the close details.
-     * @return true if connection should be disconnected now, or false if response to close should be issued.
+     * 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 boolean onCloseHandshake(boolean incoming)
+    public void onAbnormalClose(CloseInfo close)
     {
-        boolean in = inputClosed.get();
-        boolean out = outputClosed.get();
-        if (incoming)
+        ConnectionState event = null;
+        synchronized (this.state)
         {
-            in = true;
-            this.inputClosed.set(true);
-
-            if (!localCloseInitiated.get())
+            if (this.state == ConnectionState.CLOSED)
             {
-                remoteCloseInitiated.set(true);
+                // already closed
+                return;
             }
-        }
-        else
-        {
-            out = true;
-            this.outputClosed.set(true);
 
-            if ( !remoteCloseInitiated.get() )
+            if (this.state == ConnectionState.OPEN)
             {
-                localCloseInitiated.set(true);
+                this.cleanClose.set(false);
+            }
+
+            this.state = ConnectionState.CLOSED;
+            this.closeInfo.compareAndSet(null,close);
+            this.inputAvailable.set(false);
+            this.outputAvailable.set(false);
+            this.closeHandshakeSource.set(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 initialState = this.state;
+        if (initialState == ConnectionState.CLOSED)
+        {
+            // already closed
+            return;
+        }
+
+        if (initialState == ConnectionState.CONNECTED)
+        {
+            // fast close. a local close request from end-user onConnected() method
+            LOG.debug("FastClose in CONNECTED detected");
+            // Force the state open (to allow read/write to endpoint)
+            onOpened();
+        }
+
+        synchronized (this.state)
+        {
+            closeInfo.compareAndSet(null,close);
+
+            boolean in = inputAvailable.get();
+            boolean out = outputAvailable.get();
+            closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.LOCAL);
+            out = false;
+            outputAvailable.set(false);
+
+            LOG.debug("onCloseLocal(), input={}, output={}",in,out);
+
+            if (!in && !out)
+            {
+                LOG.debug("Close Handshake satisfied, disconnecting");
+                cleanClose.set(true);
+                this.state = ConnectionState.CLOSED;
+                event = this.state;
+            }
+            else if (this.state == ConnectionState.OPEN)
+            {
+                // We are now entering CLOSING (or half-closed)
+                this.state = ConnectionState.CLOSING;
+                event = this.state;
             }
         }
 
-        LOG.debug("onCloseHandshake({}), input={}, output={}",incoming,in,out);
-
-        if (in && out)
+        // Only notify on state change events
+        if (event != null)
         {
-            LOG.debug("Close Handshake satisfied, disconnecting");
-            cleanClose.set(true);
-            return true;
+            notifyStateListeners(event);
+
+            // if SHUTDOWN, we don't expect an answer.
+            if (close.getStatusCode() == StatusCode.SHUTDOWN)
+            {
+                synchronized (this.state)
+                {
+                    this.state = ConnectionState.CLOSED;
+                    cleanClose.set(false);
+                    outputAvailable.set(false);
+                    inputAvailable.set(false);
+                    this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+                    event = this.state;
+                }
+                notifyStateListeners(event);
+                return;
+            }
+        }
+    }
+
+    /**
+     * A close handshake has been received from the remote endpoint
+     */
+    public void onCloseRemote(CloseInfo close)
+    {
+        ConnectionState event = null;
+        synchronized (this.state)
+        {
+            if (this.state == ConnectionState.CLOSED)
+            {
+                // already closed
+                return;
+            }
+
+            closeInfo.compareAndSet(null,close);
+
+            boolean in = inputAvailable.get();
+            boolean out = outputAvailable.get();
+            closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.REMOTE);
+            in = false;
+            inputAvailable.set(false);
+
+            LOG.debug("onCloseRemote(), input={}, output={}",in,out);
+
+            if (!in && !out)
+            {
+                LOG.debug("Close Handshake satisfied, disconnecting");
+                cleanClose.set(true);
+                this.state = ConnectionState.CLOSED;
+                event = this.state;
+            }
+            else if (this.state == ConnectionState.OPEN)
+            {
+                // We are now entering CLOSING (or half-closed)
+                this.state = ConnectionState.CLOSING;
+                event = this.state;
+            }
         }
 
-        return false;
+        // Only notify on state change events
+        if (event != null)
+        {
+            notifyStateListeners(event);
+        }
     }
 
-    public void setConnectionState(ConnectionState connectionState)
+    /**
+     * 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()
     {
-        this.state = connectionState;
+        if (this.state != ConnectionState.CONNECTING)
+        {
+            LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state);
+            return;
+        }
+
+        ConnectionState event = null;
+        synchronized (this.state)
+        {
+            this.state = ConnectionState.CONNECTED;
+            this.inputAvailable.set(false); // cannot read (yet)
+            this.outputAvailable.set(true); // write allowed
+            event = this.state;
+        }
+        notifyStateListeners(event);
     }
 
-    public void setState(ConnectionState state)
+    /**
+     * A websocket connection has failed its upgrade handshake, and is now closed.
+     */
+    public void onFailedUpgrade()
     {
-        this.state = state;
+        assert (this.state == ConnectionState.CONNECTING);
+        ConnectionState event = null;
+        synchronized (this.state)
+        {
+            this.state = ConnectionState.CLOSED;
+            this.cleanClose.set(false);
+            this.inputAvailable.set(false);
+            this.outputAvailable.set(false);
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    /**
+     * A websocket connection has finished its upgrade handshake, and is now open.
+     */
+    public void onOpened()
+    {
+        if (this.state != ConnectionState.CONNECTED)
+        {
+            LOG.debug("Unable to open, not in CONNECTED state: {}",this.state);
+            return;
+        }
+
+        assert (this.state == ConnectionState.CONNECTED);
+
+        ConnectionState event = null;
+        synchronized (this.state)
+        {
+            this.state = ConnectionState.OPEN;
+            this.inputAvailable.set(true);
+            this.outputAvailable.set(true);
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    /**
+     * The local endpoint has reached a read EOF.
+     * <p>
+     * This could be a normal result after a proper close handshake, or even a premature close due to a connection disconnect.
+     */
+    public void onReadEOF()
+    {
+        ConnectionState event = null;
+        synchronized (this.state)
+        {
+            if (this.state == ConnectionState.CLOSED)
+            {
+                // already closed
+                return;
+            }
+
+            CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE,"Read EOF");
+
+            this.cleanClose.set(false);
+            this.state = ConnectionState.CLOSED;
+            this.closeInfo.compareAndSet(null,close);
+            this.inputAvailable.set(false);
+            this.outputAvailable.set(false);
+            this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
+            event = this.state;
+        }
+        notifyStateListeners(event);
+    }
+
+    public boolean wasAbnormalClose()
+    {
+        return closeHandshakeSource.get() == CloseHandshakeSource.ABNORMAL;
     }
 
     public boolean wasCleanClose()
@@ -168,11 +401,11 @@
 
     public boolean wasLocalCloseInitiated()
     {
-        return localCloseInitiated.get();
+        return closeHandshakeSource.get() == CloseHandshakeSource.LOCAL;
     }
 
     public boolean wasRemoteCloseInitiated()
     {
-        return remoteCloseInitiated.get();
+        return closeHandshakeSource.get() == CloseHandshakeSource.REMOTE;
     }
 }
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
index e34e31b..1cf29b2 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/WriteBytesProvider.java
@@ -258,6 +258,10 @@
 
     private void notifySafeFailure(Callback callback, Throwable t)
     {
+        if (callback == null)
+        {
+            return;
+        }
         try
         {
             callback.failed(t);
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..6cb2ae9
--- /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-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://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);
+}
\ No newline at end of file
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
similarity index 79%
rename from jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java
rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
index 8b67dfd..b1b01c9 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/HttpResponseHeaderParser.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParser.java
@@ -16,7 +16,7 @@
 //  ========================================================================
 //
 
-package org.eclipse.jetty.websocket.client.io;
+package org.eclipse.jetty.websocket.common.io.http;
 
 import java.nio.ByteBuffer;
 import java.util.regex.Matcher;
@@ -25,7 +25,6 @@
 import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.Utf8LineParser;
-import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
 
 /**
  * Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object.
@@ -56,12 +55,13 @@
     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 ClientUpgradeResponse response;
-    private Utf8LineParser lineParser;
+    private final HttpResponseHeaderParseListener listener;
+    private final Utf8LineParser lineParser;
     private State state;
 
-    public HttpResponseHeaderParser()
+    public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener)
     {
+        this.listener = listener;
         this.lineParser = new Utf8LineParser();
         this.state = State.STATUS_LINE;
     }
@@ -71,7 +71,7 @@
         return (state == State.END);
     }
 
-    public ClientUpgradeResponse parse(ByteBuffer buf) throws ParseException
+    public HttpResponseHeaderParseListener parse(ByteBuffer buf) throws ParseException
     {
         while (!isDone() && (buf.remaining() > 0))
         {
@@ -84,8 +84,8 @@
                     ByteBuffer copy = ByteBuffer.allocate(buf.remaining());
                     BufferUtil.put(buf,copy);
                     BufferUtil.flipToFlush(copy,0);
-                    this.response.setRemainingBuffer(copy);
-                    return this.response;
+                    this.listener.setRemainingBuffer(copy);
+                    return listener;
                 }
             }
         }
@@ -98,22 +98,21 @@
         {
             case STATUS_LINE:
             {
-                this.response = new ClientUpgradeResponse();
                 Matcher mat = PAT_STATUS_LINE.matcher(line);
                 if (!mat.matches())
                 {
-                    throw new ParseException("Unexpected HTTP upgrade response status line [" + line + "]");
+                    throw new ParseException("Unexpected HTTP response status line [" + line + "]");
                 }
 
                 try
                 {
-                    response.setStatusCode(Integer.parseInt(mat.group(1)));
+                    listener.setStatusCode(Integer.parseInt(mat.group(1)));
                 }
                 catch (NumberFormatException e)
                 {
-                    throw new ParseException("Unexpected HTTP upgrade response status code",e);
+                    throw new ParseException("Unexpected HTTP response status code",e);
                 }
-                response.setStatusReason(mat.group(2));
+                listener.setStatusReason(mat.group(2));
                 state = State.HEADER;
                 break;
             }
@@ -130,8 +129,8 @@
                 {
                     String headerName = header.group(1);
                     String headerValue = header.group(2);
-                    // TODO: need to split header/value if comma delimited
-                    response.addHeader(headerName,headerValue);
+                    // do need to split header/value if comma delimited?
+                    listener.addHeader(headerName,headerValue);
                 }
                 break;
             }
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
index a0cca6d..ad6b3cf 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java
@@ -20,8 +20,6 @@
 
 import java.io.IOException;
 
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.websocket.api.WebSocketAdapter;
 
 /**
@@ -29,26 +27,21 @@
  */
 public class AdapterEchoSocket extends WebSocketAdapter
 {
-    private static final Logger LOG = Log.getLogger(AdapterEchoSocket.class);
-
     @Override
     public void onWebSocketText(String message)
     {
-        if (isNotConnected())
+        if (isConnected())
         {
-            LOG.debug("WebSocket Not Connected");
-            return;
-        }
-
-        try
-        {
-            LOG.debug("Echoing back message [{}]",message);
-            // echo the data back
-            getRemote().sendString(message);
-        }
-        catch (IOException e)
-        {
-            e.printStackTrace();
+            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
index a2af920..0b71bb5 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java
@@ -33,8 +33,9 @@
     {
         if (session.isOpen())
         {
-            return;
+            System.out.printf("Echoing back message [%s]%n",message);
+            // echo the message back
+            session.getRemote().sendStringByFuture(message);
         }
-        session.getRemote().sendStringByFuture(message);
     }
 }
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
index 04fc6f7..6d76f0e 100644
--- a/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
+++ b/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java
@@ -18,9 +18,6 @@
 
 package examples.echo;
 
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 import org.eclipse.jetty.websocket.api.Session;
 import org.eclipse.jetty.websocket.api.WebSocketListener;
 
@@ -29,7 +26,6 @@
  */
 public class ListenerEchoSocket implements WebSocketListener
 {
-    private static final Logger LOG = Logger.getLogger(ListenerEchoSocket.class.getName());
     private Session outbound;
 
     @Override
@@ -53,17 +49,17 @@
     @Override
     public void onWebSocketError(Throwable cause)
     {
-        LOG.log(Level.WARNING,"onWebSocketError",cause);
+        cause.printStackTrace(System.err);
     }
 
     @Override
     public void onWebSocketText(String message)
     {
-        if (outbound == null)
+        if ((outbound != null) && (outbound.isOpen()))
         {
-            return;
+            System.out.printf("Echoing back message [%s]%n",message);
+            // echo the message back
+            outbound.getRemote().sendStringByFuture(message);
         }
-
-        outbound.getRemote().sendStringByFuture(message);
     }
 }
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
index 56bc513..ecb5adb 100644
--- 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
@@ -116,7 +116,7 @@
             driver.incomingFrame(new WebSocketFrame(OpCode.PING).setPayload("PING"));
             driver.incomingFrame(WebSocketFrame.text("Text Me"));
             driver.incomingFrame(WebSocketFrame.binary().setPayload("Hello Bin"));
-            driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN).asFrame());
+            driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN,"testcase").asFrame());
 
             socket.capture.assertEventCount(6);
             socket.capture.assertEventStartsWith(0,"onConnect(");
@@ -148,13 +148,14 @@
     }
 
     @Test
-    public void testListener_Text() throws IOException
+    public void testListener_Text() throws Exception
     {
         ListenerBasicSocket socket = new ListenerBasicSocket();
         EventDriver driver = wrap(socket);
 
         try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver))
         {
+            conn.start();
             conn.open();
             driver.incomingFrame(WebSocketFrame.text("Hello World"));
             driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());
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..24393b1
--- /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-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://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.*;
+import static org.junit.Assert.*;
+
+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
index 5387af3..92e5e77 100644
--- 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
@@ -22,22 +22,25 @@
 
 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.SuspendToken;
 import org.eclipse.jetty.websocket.api.WebSocketException;
 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
+public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames, ConnectionStateListener
 {
     private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class);
     private final String id;
     private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
-    private boolean open = false;
     private IncomingFrames incoming;
     private IOState ioState = new IOState();
 
@@ -49,29 +52,33 @@
     public LocalWebSocketConnection(String id)
     {
         this.id = id;
+        this.ioState.addListener(this);
     }
 
     public LocalWebSocketConnection(TestName testname)
     {
         this.id = testname.getMethodName();
+        this.ioState.addListener(this);
     }
 
     @Override
     public void close()
     {
-        open = false;
+        close(StatusCode.NORMAL,null);
     }
 
     @Override
     public void close(int statusCode, String reason)
     {
-        open = false;
+        LOG.debug("close({}, {})",statusCode,reason);
+        CloseInfo close = new CloseInfo(statusCode,reason);
+        ioState.onCloseLocal(close);
     }
 
     @Override
     public void disconnect()
     {
-        open = false;
+        LOG.debug("disconnect()");
     }
 
     public IncomingFrames getIncoming()
@@ -131,7 +138,7 @@
     @Override
     public boolean isOpen()
     {
-        return open;
+        return getIOState().isOpen();
     }
 
     @Override
@@ -140,9 +147,31 @@
         return false;
     }
 
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
+    {
+        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 onOpen() {
         LOG.debug("onOpen()");
-        open = true;
+        ioState.onOpened();
     }
 
     @Override
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..837cf10
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseHeaderParserTest.java
@@ -0,0 +1,193 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://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.*;
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+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.getBytes(line,StringUtil.__UTF8)));
+    }
+
+    @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(),StringUtil.__UTF8_CHARSET);
+
+        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(),StringUtil.__UTF8_CHARSET);
+
+        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..a7973e6
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/http/HttpResponseParseCapture.java
@@ -0,0 +1,76 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://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.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public class HttpResponseParseCapture implements HttpResponseHeaderParseListener
+{
+    private int statusCode;
+    private String statusReason;
+    private Map<String, String> headers = new HashMap<>();
+    private ByteBuffer remainingBuffer;
+
+    @Override
+    public void addHeader(String name, String value)
+    {
+        headers.put(name.toLowerCase(Locale.ENGLISH),value);
+    }
+
+    public String getHeader(String name)
+    {
+        return headers.get(name.toLowerCase(Locale.ENGLISH));
+    }
+
+    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;
+    }
+}
\ No newline at end of file
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
index afe2e78..752972d 100644
--- 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
@@ -76,13 +76,6 @@
     }
 
     @Override
-    public void onWriteWebSocketClose()
-    {
-        // as server, always disconnect if writing close
-        disconnect();
-    }
-
-    @Override
     public void setNextIncomingFrames(IncomingFrames incoming)
     {
         getParser().setIncomingFramesHandler(incoming);
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
index 09d06a5..2db4a48 100644
--- 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
@@ -103,7 +103,7 @@
 
             // Read frame (hopefully text frame)
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
-            WebSocketFrame tf = capture.getFrames().get(0);
+            WebSocketFrame tf = capture.getFrames().poll();
             Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
         }
         finally
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
index fd16ef7..68bc1b1 100644
--- 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
@@ -24,6 +24,7 @@
 
 import org.eclipse.jetty.websocket.common.WebSocketFrame;
 import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
 import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
 import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
 import org.junit.AfterClass;
@@ -60,8 +61,8 @@
             client.setProtocols("chat");
             client.connect();
             client.sendStandardRequest();
-            String response = client.expectUpgradeResponse();
-            Assert.assertThat("Response",response,containsString("x-webkit-deflate-frame"));
+            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";
@@ -69,7 +70,7 @@
 
             // Read frame (hopefully text frame)
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
-            WebSocketFrame tf = capture.getFrames().get(0);
+            WebSocketFrame tf = capture.getFrames().poll();
             Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
         }
         finally
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
index cab75e1..b84ee24 100644
--- 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
@@ -24,6 +24,7 @@
 
 import org.eclipse.jetty.websocket.common.WebSocketFrame;
 import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
 import org.eclipse.jetty.websocket.server.helper.EchoServlet;
 import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
 import org.junit.AfterClass;
@@ -80,9 +81,9 @@
             client.setTimeout(TimeUnit.SECONDS,1);
             client.connect();
             client.sendStandardRequest();
-            String resp = client.expectUpgradeResponse();
+            HttpResponse resp = client.expectUpgradeResponse();
 
-            Assert.assertThat("Response",resp,containsString("fragment"));
+            Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("fragment"));
 
             String msg = "Sent as a long message that should be split";
             client.write(WebSocketFrame.text(msg));
@@ -91,7 +92,7 @@
             IncomingFramesCapture capture = client.readFrames(parts.length,TimeUnit.MILLISECONDS,1000);
             for (int i = 0; i < parts.length; i++)
             {
-                WebSocketFrame frame = capture.getFrames().get(i);
+                WebSocketFrame frame = capture.getFrames().poll();
                 Assert.assertThat("text[" + i + "].payload",frame.getPayloadAsUTF8(),is(parts[i]));
             }
         }
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
index 9e0dfeb..78a6be2 100644
--- 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
@@ -24,6 +24,7 @@
 
 import org.eclipse.jetty.websocket.common.WebSocketFrame;
 import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
 import org.eclipse.jetty.websocket.server.helper.EchoServlet;
 import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
 import org.junit.AfterClass;
@@ -64,9 +65,9 @@
             client.setTimeout(TimeUnit.SECONDS,1);
             client.connect();
             client.sendStandardRequest();
-            String resp = client.expectUpgradeResponse();
+            HttpResponse resp = client.expectUpgradeResponse();
 
-            Assert.assertThat("Response",resp,containsString("x-webkit-deflate-frame"));
+            Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("x-webkit-deflate-frame"));
 
             String msg = "Hello";
 
@@ -74,7 +75,7 @@
             client.write(WebSocketFrame.text(msg));
 
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
-            WebSocketFrame frame = capture.getFrames().get(0);
+            WebSocketFrame frame = capture.getFrames().poll();
             Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
 
             // Client sends second message
@@ -83,7 +84,7 @@
             client.write(WebSocketFrame.text(msg));
 
             capture = client.readFrames(1,TimeUnit.SECONDS,1);
-            frame = capture.getFrames().get(0);
+            frame = capture.getFrames().poll();
             Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
         }
         finally
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
index e56dc95..47fa7ca 100644
--- 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
@@ -24,6 +24,7 @@
 
 import org.eclipse.jetty.websocket.common.WebSocketFrame;
 import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
 import org.eclipse.jetty.websocket.server.helper.EchoServlet;
 import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
 import org.junit.AfterClass;
@@ -65,14 +66,14 @@
             client.setTimeout(TimeUnit.SECONDS,1);
             client.connect();
             client.sendStandardRequest();
-            String resp = client.expectUpgradeResponse();
+            HttpResponse resp = client.expectUpgradeResponse();
 
-            Assert.assertThat("Response",resp,containsString("identity"));
+            Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("identity"));
 
             client.write(WebSocketFrame.text("Hello"));
 
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
-            WebSocketFrame frame = capture.getFrames().get(0);
+            WebSocketFrame frame = capture.getFrames().poll();
             Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is("Hello"));
         }
         finally
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
index b7e80d3..00c001b 100644
--- 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
@@ -18,6 +18,8 @@
 
 package org.eclipse.jetty.websocket.server;
 
+import static org.hamcrest.Matchers.*;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -43,15 +45,11 @@
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 
-import static org.hamcrest.Matchers.is;
-
 /**
  * Tests various close scenarios
  */
-@Ignore
 public class WebSocketCloseTest
 {
     @SuppressWarnings("serial")
@@ -145,7 +143,7 @@
             client.expectUpgradeResponse();
 
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
-            WebSocketFrame frame = capture.getFrames().get(0);
+            WebSocketFrame frame = capture.getFrames().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));
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
index 5d47449..410fa82 100644
--- 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
@@ -21,6 +21,7 @@
 import static org.hamcrest.Matchers.*;
 
 import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
+import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
 import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -56,9 +57,10 @@
         {
             client.connect();
             client.sendStandardRequest();
-            String respHeader = client.readResponseHeader();
-            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"));
+            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
         {
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
index f2a6dc5..6194090 100644
--- 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
@@ -21,6 +21,7 @@
 import static org.hamcrest.Matchers.*;
 
 import java.net.URI;
+import java.util.Queue;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jetty.toolchain.test.AdvancedRunner;
@@ -96,13 +97,14 @@
 
             // Read frame (hopefully text frame)
             IncomingFramesCapture capture = client.readFrames(4,TimeUnit.MILLISECONDS,500);
-            WebSocketFrame tf = capture.getFrames().pop();
+            Queue<WebSocketFrame> frames = capture.getFrames();
+            WebSocketFrame tf = frames.poll();
             Assert.assertThat("Parameter Map[snack]",tf.getPayloadAsUTF8(),is("[cashews]"));
-            tf = capture.getFrames().pop();
+            tf = frames.poll();
             Assert.assertThat("Parameter Map[amount]",tf.getPayloadAsUTF8(),is("[handful]"));
-            tf = capture.getFrames().pop();
+            tf = frames.poll();
             Assert.assertThat("Parameter Map[brand]",tf.getPayloadAsUTF8(),is("[off]"));
-            tf = capture.getFrames().pop();
+            tf = frames.poll();
             Assert.assertThat("Parameter Map[cost]",tf.getPayloadAsUTF8(),is("<null>"));
         }
         finally
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
index 294ee65..8ca6db3 100644
--- 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
@@ -115,7 +115,7 @@
 
             // Read frame echo'd back (hopefully a single binary frame)
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
-            Frame binmsg = capture.getFrames().get(0);
+            Frame binmsg = capture.getFrames().poll();
             int expectedSize = buf1.length + buf2.length + buf3.length;
             Assert.assertThat("BinaryFrame.payloadLength",binmsg.getPayloadLength(),is(expectedSize));
 
@@ -181,7 +181,7 @@
 
             // Read frame (hopefully text frame)
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
-            WebSocketFrame tf = capture.getFrames().get(0);
+            WebSocketFrame tf = capture.getFrames().poll();
             Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
         }
         finally
@@ -209,7 +209,7 @@
 
             // Read frame (hopefully close frame)
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
-            Frame cf = capture.getFrames().get(0);
+            Frame cf = capture.getFrames().poll();
             CloseInfo close = new CloseInfo(cf);
             Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
         }
@@ -252,7 +252,7 @@
 
             // Read frame (hopefully text frame)
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
-            WebSocketFrame tf = capture.getFrames().get(0);
+            WebSocketFrame tf = capture.getFrames().poll();
             Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
         }
         finally
@@ -292,7 +292,7 @@
             }
 
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
-            WebSocketFrame frame = capture.getFrames().get(0);
+            WebSocketFrame frame = capture.getFrames().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.MESSAGE_TOO_LARGE));
@@ -334,7 +334,7 @@
             }
 
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
-            WebSocketFrame frame = capture.getFrames().get(0);
+            WebSocketFrame frame = capture.getFrames().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.MESSAGE_TOO_LARGE));
@@ -367,7 +367,7 @@
             client.writeRaw(bb);
 
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
-            WebSocketFrame frame = capture.getFrames().get(0);
+            WebSocketFrame frame = capture.getFrames().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));
@@ -413,7 +413,7 @@
 
             // Read frame (hopefully text frame)
             IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
-            WebSocketFrame tf = capture.getFrames().get(0);
+            WebSocketFrame tf = capture.getFrames().poll();
             Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
         }
         finally
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
index 09e9f3e..5c9d07c 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/Fuzzer.java
@@ -151,7 +151,7 @@
         for (int i = 0; i < expectedCount; i++)
         {
             WebSocketFrame expected = expect.get(i);
-            WebSocketFrame actual = capture.getFrames().pop();
+            WebSocketFrame actual = capture.getFrames().poll();
 
             prefix = "Frame[" + i + "]";
 
@@ -188,14 +188,16 @@
         // we expect that the close handshake to have occurred and the server should have closed the connection
         try
         {
-            @SuppressWarnings("unused")
-            int val = client.read();
+            ByteBuffer buf = ByteBuffer.wrap(new byte[]
+            { 0x00 });
+            BufferUtil.flipToFill(buf);
+            int len = client.read(buf);
 
-            Assert.fail("Server has not closed socket");
+            Assert.assertThat("Server has not closed socket",len,lessThanOrEqualTo(0));
         }
-        catch (SocketException e)
+        catch (IOException e)
         {
-
+            // valid path
         }
 
         IOState ios = client.getIOState();
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
index 561f9a8..fcb40ff 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/BlockheadClient.java
@@ -19,13 +19,10 @@
 package org.eclipse.jetty.websocket.server.blockhead;
 
 import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 
-import java.io.BufferedReader;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.InetAddress;
@@ -41,8 +38,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import javax.net.ssl.HttpsURLConnection;
 
@@ -51,7 +46,6 @@
 import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.IO;
 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.api.WebSocketException;
@@ -71,6 +65,8 @@
 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.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
 import org.junit.Assert;
 
@@ -86,7 +82,7 @@
  * 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 IncomingFrames, OutgoingFrames
+public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener
 {
     private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ==";
     private static final int BUFFER_SIZE = 8192;
@@ -117,6 +113,7 @@
     private ExtensionStack extensionStack;
     private IOState ioState;
     private CountDownLatch disconnectedLatch = new CountDownLatch(1);
+    private ByteBuffer remainingBuffer;
 
     public BlockheadClient(URI destWebsocketURI) throws URISyntaxException
     {
@@ -144,6 +141,7 @@
 
         this.extensionFactory = new WebSocketExtensionFactory(policy,bufferPool);
         this.ioState = new IOState();
+        this.ioState.addListener(this);
     }
 
     public void addExtensions(String xtension)
@@ -179,24 +177,22 @@
 
     public void close(int statusCode, String message)
     {
-        try
-        {
-            CloseInfo close = new CloseInfo(statusCode,message);
+        CloseInfo close = new CloseInfo(statusCode,message);
 
-            if (ioState.onCloseHandshake(false))
+        ioState.onCloseLocal(close);
+
+        if (!ioState.isClosed())
+        {
+            WebSocketFrame frame = close.asFrame();
+            LOG.debug("Issuing: {}",frame);
+            try
             {
-                this.disconnect();
-            }
-            else
-            {
-                WebSocketFrame frame = close.asFrame();
-                LOG.debug("Issuing: {}",frame);
                 write(frame);
             }
-        }
-        catch (IOException e)
-        {
-            LOG.debug(e);
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
         }
     }
 
@@ -234,32 +230,31 @@
         }
     }
 
-    public String expectUpgradeResponse() throws IOException
+    public HttpResponse expectUpgradeResponse() throws IOException
     {
-        String respHeader = readResponseHeader();
+        HttpResponse response = readResponseHeader();
 
         if (LOG.isDebugEnabled())
         {
-            LOG.debug("Response Header: {}{}",'\n',respHeader);
+            LOG.debug("Response Header: {}{}",'\n',response);
         }
 
-        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"));
+        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
-        Pattern patAcceptHeader = Pattern.compile("Sec-WebSocket-Accept: (.*=)",Pattern.CASE_INSENSITIVE);
-        Matcher matAcceptHeader = patAcceptHeader.matcher(respHeader);
-        Assert.assertThat("Response Header Sec-WebSocket-Accept Exists?",matAcceptHeader.find(),is(true));
+        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);
-        String acceptKey = matAcceptHeader.group(1);
 
         Assert.assertThat("Valid Sec-WebSocket-Accept Hash?",acceptKey,is(expectedHash));
 
         // collect extensions configured in response header
-        List<ExtensionConfig> configs = getExtensionConfigs(respHeader);
+        List<ExtensionConfig> configs = getExtensionConfigs(response);
         extensionStack = new ExtensionStack(this.extensionFactory);
         extensionStack.negotiate(configs);
 
@@ -283,12 +278,12 @@
 
         // configure parser
         parser.setIncomingFramesHandler(extensionStack);
-        ioState.setState(ConnectionState.OPEN);
+        ioState.onOpened();
 
         LOG.debug("outgoing = {}",outgoing);
         LOG.debug("incoming = {}",extensionStack);
 
-        return respHeader;
+        return response;
     }
 
     public void flush() throws IOException
@@ -296,22 +291,16 @@
         out.flush();
     }
 
-    private List<ExtensionConfig> getExtensionConfigs(String respHeader)
+    private List<ExtensionConfig> getExtensionConfigs(HttpResponse response)
     {
         List<ExtensionConfig> configs = new ArrayList<>();
 
-        Pattern expat = Pattern.compile("Sec-WebSocket-Extensions: (.*)\r",Pattern.CASE_INSENSITIVE);
-        Matcher mat = expat.matcher(respHeader);
-        int offset = 0;
-        while (mat.find(offset))
+        String econf = response.getHeader("Sec-WebSocket-Extensions");
+        if (econf != null)
         {
-            String econf = mat.group(1);
             LOG.debug("Found Extension Response: {}",econf);
-
             ExtensionConfig config = ExtensionConfig.parse(econf);
             configs.add(config);
-
-            offset = mat.end(1);
         }
         return configs;
     }
@@ -404,14 +393,7 @@
         if (frame.getType() == Frame.Type.CLOSE)
         {
             CloseInfo close = new CloseInfo(frame);
-            if (ioState.onCloseHandshake(true))
-            {
-                this.disconnect();
-            }
-            else
-            {
-                close(close.getStatusCode(),close.getReason());
-            }
+            ioState.onCloseRemote(close);
         }
 
         WebSocketFrame copy = new WebSocketFrame(frame);
@@ -423,32 +405,24 @@
         return (socket != null) && (socket.isConnected());
     }
 
-    public void lookFor(String string) throws IOException
+    @Override
+    public void onConnectionStateChange(ConnectionState state)
     {
-        String orig = string;
-        Utf8StringBuilder scanned = new Utf8StringBuilder();
-        try
+        switch (state)
         {
-            while (true)
-            {
-                int b = in.read();
-                if (b < 0)
+            case CLOSED:
+                this.disconnect();
+                break;
+            case CLOSING:
+                if (ioState.wasRemoteCloseInitiated())
                 {
-                    throw new EOFException();
+                    CloseInfo close = ioState.getCloseInfo();
+                    close(close.getStatusCode(),close.getReason());
                 }
-                scanned.append((byte)b);
-                assertEquals("looking for\"" + orig + "\" in '" + scanned + "'",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;
+                break;
+            default:
+                /* do nothing */
+                break;
         }
     }
 
@@ -487,17 +461,18 @@
         }
     }
 
-    public int read() throws IOException
-    {
-        return in.read();
-    }
-
     public int read(ByteBuffer buf) throws IOException
     {
         if (eof)
         {
             throw new EOFException("Hit EOF");
         }
+
+        if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0))
+        {
+            return BufferUtil.put(remainingBuffer,buf);
+        }
+
         int len = 0;
         int b;
         while ((in.available() > 0) && (buf.remaining() > 0))
@@ -572,25 +547,24 @@
         return incomingFrames;
     }
 
-    public String readResponseHeader() throws IOException
+    public HttpResponse readResponseHeader() 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)
+        HttpResponse response = new HttpResponse();
+        HttpResponseHeaderParser parser = new HttpResponseHeaderParser(response);
+
+        ByteBuffer buf = BufferUtil.allocate(512);
+
+        do
         {
-            if (line.trim().length() == 0)
-            {
-                break;
-            }
-            header.append(line).append("\r\n");
+            BufferUtil.flipToFill(buf);
+            read(buf);
+            BufferUtil.flipToFlush(buf,0);
         }
-        return header.toString();
+        while (parser.parse(buf) == null);
+
+        remainingBuffer = response.getRemainingBuffer();
+
+        return response;
     }
 
     public void sendStandardRequest() throws IOException
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java
new file mode 100644
index 0000000..59d44eb
--- /dev/null
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/blockhead/HttpResponse.java
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://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.blockhead;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+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 HashMap<>();
+    private ByteBuffer remainingBuffer;
+
+    @Override
+    public void addHeader(String name, String value)
+    {
+        headers.put(name.toLowerCase(Locale.ENGLISH),value);
+    }
+
+    public String getExtensionsHeader()
+    {
+        return getHeader("Sec-WebSocket-Extensions");
+    }
+
+    public String getHeader(String name)
+    {
+        return headers.get(name.toLowerCase(Locale.ENGLISH));
+    }
+
+    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-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
index a00bc71..d618681 100644
--- 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
@@ -116,6 +116,9 @@
 
                 // Setup the desired Socket to use for all incoming upgrade requests
                 factory.setCreator(BrowserDebugTool.this);
+
+                // Set the timeout
+                factory.getPolicy().setIdleTimeout(2000);
             }
         };
 
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
index e9ce087..f54f12e 100644
--- 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
@@ -31,6 +31,15 @@
  */
 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)
@@ -96,14 +105,7 @@
         connector.setPort(port);
 
         server.addConnector(connector);
-        wsHandler = new WebSocketHandler()
-        {
-            @Override
-            public void configure(WebSocketServletFactory factory)
-            {
-                factory.setCreator(new EchoCreator());
-            }
-        };
+        wsHandler = new EchoSocketHandler();
 
         server.setHandler(wsHandler);
 
@@ -126,6 +128,14 @@
     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();
     }
 
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
index a0e2889..82a4723 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/IncomingFramesCapture.java
@@ -20,8 +20,9 @@
 
 import static org.hamcrest.Matchers.*;
 
-import java.util.LinkedList;
+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;
@@ -35,8 +36,8 @@
 public class IncomingFramesCapture implements IncomingFrames
 {
     private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
-    private LinkedList<WebSocketFrame> frames = new LinkedList<>();
-    private LinkedList<WebSocketException> errors = new LinkedList<>();
+    private EventQueue<WebSocketFrame> frames = new EventQueue<>();
+    private EventQueue<WebSocketException> errors = new EventQueue<>();
 
     public void assertErrorCount(int expectedCount)
     {
@@ -81,10 +82,10 @@
     public void dump()
     {
         System.err.printf("Captured %d incoming frames%n",frames.size());
-        for (int i = 0; i < frames.size(); i++)
+        int i = 0;
+        for (Frame frame : frames)
         {
-            Frame frame = frames.get(i);
-            System.err.printf("[%3d] %s%n",i,frame);
+            System.err.printf("[%3d] %s%n",i++,frame);
             System.err.printf("          %s%n",BufferUtil.toDetailString(frame.getPayload()));
         }
     }
@@ -102,7 +103,7 @@
         return count;
     }
 
-    public LinkedList<WebSocketException> getErrors()
+    public Queue<WebSocketException> getErrors()
     {
         return errors;
     }
@@ -120,7 +121,7 @@
         return count;
     }
 
-    public LinkedList<WebSocketFrame> getFrames()
+    public Queue<WebSocketFrame> getFrames()
     {
         return frames;
     }
diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
index c27be61..e7122e6 100644
--- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties
@@ -5,7 +5,6 @@
 # org.eclipse.jetty.websocket.LEVEL=WARN
 # org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG
 # org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG
-# org.eclipse.jetty.websocket.common.io.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
diff --git a/pom.xml b/pom.xml
index 7b480d9..417706f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -542,7 +542,7 @@
       <dependency>
         <groupId>org.eclipse.jetty.toolchain</groupId>
         <artifactId>jetty-test-helper</artifactId>
-        <version>2.0</version>
+        <version>2.2</version>
       </dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
diff --git a/tests/test-webapps/test-jaas-webapp/pom.xml b/tests/test-webapps/test-jaas-webapp/pom.xml
index a743267..c7f27b1 100644
--- a/tests/test-webapps/test-jaas-webapp/pom.xml
+++ b/tests/test-webapps/test-jaas-webapp/pom.xml
@@ -34,7 +34,7 @@
               <!-- 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/etc/login.conf</value>
+              <value>${basedir}/src/main/config/webapps.demo/test-jaas.d/login.conf</value>
             </systemProperty>
           </systemProperties>
            <webAppConfig>
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
index 04effea..3fca8c6 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml
+++ b/tests/test-webapps/test-jaas-webapp/src/main/assembly/config.xml
@@ -11,7 +11,7 @@
       <outputDirectory></outputDirectory>
       <includes>
         <include>webapps/test-jaas.xml</include>
-        <include>etc/**</include>
+        <include>**</include>
       </includes>
     </fileSet>
   </fileSets>
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.conf
deleted file mode 100644
index c7a2103..0000000
--- a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-xyz {
-org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
-debug="true"
-file="${jetty.home}/etc/login.properties";
-};
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf
new file mode 100644
index 0000000..0978de6
--- /dev/null
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.conf
@@ -0,0 +1,5 @@
+xyz {
+org.eclipse.jetty.jaas.spi.PropertyFileLoginModule required
+debug="true"
+file="${jetty.home}/webapps.demo/test-jaas.d/login.properties";
+};
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.properties b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.properties
similarity index 100%
rename from tests/test-webapps/test-jaas-webapp/src/main/config/etc/login.properties
rename to tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.d/login.properties
diff --git a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml
similarity index 89%
rename from tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml
rename to tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml
index 1720ba9..f3b0a18 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/config/webapps/test-jaas.xml
+++ b/tests/test-webapps/test-jaas-webapp/src/main/config/webapps.demo/test-jaas.xml
@@ -7,7 +7,7 @@
 <Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
 
   <Set name="contextPath">/test-jaas</Set>
-  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test-jaas.war</Set>
+  <Set name="war"><Property name="jetty.webapps" default="."/>/test-jaas.war</Set>
   <Set name="extractWAR">true</Set>
 
   <Set name="securityHandler">
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
index 829c925..249f958 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/auth.html
@@ -2,11 +2,12 @@
   <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>Congratulations, you are AUTHENTICATED and web AUTHORIZED</H1>
-  Well done. In order to see this page, you must have been JAAS authentictated using the
+    <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:
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
index a390511..f57687b 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/authfail.html
@@ -1,6 +1,7 @@
 <html>
   <head>
     <title>Authentication Failure</title>
+    <link rel="stylesheet" type="text/css" href="stylesheet.css"/>
   </head>
   <body>
     <h1>Authentication Failure</h1>
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
index 62b744f..d9f637b 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/index.html
@@ -3,12 +3,7 @@
     <TITLE>JAAS Authentication and Authorization Test</TITLE>
     <META http-equiv="Pragma" content="no-cache">
     <META http-equiv="Cache-Control" content="no-cache,no-store">
-    <style>
-       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;}
-    </style>
-
+    <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>
@@ -22,26 +17,17 @@
 
   <H1>JAAS Authentication and Authorization Demo </H1>
   <h2>Preparation</h2>
-  <ol>
-   <li>You will need to edit your $JETTY_HOME/start.ini file and add the following lines:
+  <p>To enable JAAS, edit your start.ini or start.d/*.ini files and add the following lines:
     <pre>
      OPTIONS=jaas
+     jaas.login.conf=etc/login.conf
      etc/jetty-jaas.xml
     </pre>
-    </li>
+  </p>
+  <p>For the jetty distribution demos, jaas is already enabled in the start.d/900-demo.ini file and sets the jaas.login.conf property to webapps.demo/test-jaas.d/login.conf  for use with the webapps.demo/test-jaas.war web application.  </p>
 
-    <li>Unjar the test-jaas-webapp-&lt;version&gt;-config.jar inside $JETTY_HOME. The following files will be added:
-     <pre>
-       etc/login.conf
-       etc/login.properties
-       contexts/test-jaas.xml
-    </pre>
-    </li>
-  </ol>
-
-  <p>Now start jetty as usual.</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>
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
index 4de6491..608ea5e 100644
--- a/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html
+++ b/tests/test-webapps/test-jaas-webapp/src/main/webapp/login.html
@@ -1,5 +1,8 @@
 
-<HTML><HEAD><TITLE>JAAS Authentication and Authorization Test</TITLE></HEAD>
+<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>
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/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini b/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini
deleted file mode 100644
index 2c4006b..0000000
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/start.d/test-webapp.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-# Enabled services and configuration needed by the test webapp
-OPTIONS=client
-etc/test-realm.xml
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.d/override-web.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml
similarity index 100%
rename from tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.d/override-web.xml
rename to tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.d/override-web.xml
diff --git a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
similarity index 92%
rename from tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml
rename to tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
index ff7eb6b..44cbce5 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/config/webapps/test.xml
+++ b/tests/test-webapps/test-jetty-webapp/src/main/config/webapps.demo/test.xml
@@ -19,15 +19,15 @@
   <!--  + war OR resourceBase                                          -->
   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
   <Set name="contextPath">/test</Set>
-  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test.war</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"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
-  <Set name="overrideDescriptor"><SystemProperty name="jetty.home" default="."/>/webapps/test.d/override-web.xml</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>
 
   <!-- Allow directory symbolic links  -->
   <Call name="addAliasCheck">
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
index 93a04e2..326015b 100644
--- 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
@@ -498,6 +498,10 @@
             pout.write("<td>"+request.isSecure()+"</td>");
 
             pout.write("</tr><tr>\n");
+            pout.write("<th align=\"right\">encodeRedirectURL(/foo?bar):&nbsp;</th>");
+            pout.write("<td>"+response.encodeRedirectURL("/foo?bar")+"</td>");
+
+            pout.write("</tr><tr>\n");
             pout.write("<th align=\"right\">isUserInRole(admin):&nbsp;</th>");
             pout.write("<td>"+request.isUserInRole("admin")+"</td>");
 
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
index c088e0e..c81fcb9 100644
--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/index.html
@@ -13,49 +13,37 @@
 <A HREF="http://jetty.eclipse.org"><IMG SRC="jetty_banner.gif"></A>
 <h1>Welcome to Jetty 9</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> hub.<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>
+This is the Test webapp for the Jetty 9 HTTP Server and Servlet Container. It is 
+deployed in $JETTY_HOME/webapp.demo and configured by $JETTY_HOME/start.d/900-demo.ini
 <ul>
+<li>Servet: <a href="hello/">Hello World</a></li>
+<li>Dump: <a href="dump/info">Request</a>, <a href="session/">Session</a>, <a href="cookie/">Cookie</a></li>
+<li>JSP: <a href="jsp/">examples</a></li>
+<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket</a></li>
+<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:
 <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>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>
+Useful links are:<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/>
--->
+</p>
 
-This webapp is deployed in $JETTY_HOME/webapp/test and configured by $JETTY_HOME/contexts/test.xml
-
+<p>
+<a href="/">MAIN MENU</a>
+</p>
 </BODY>
 </HTML>
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
index e307ad4..f4b5d58 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
+++ b/tests/test-webapps/test-jndi-webapp/src/main/assembly/config.xml
@@ -18,16 +18,16 @@
     </fileSet>
     <fileSet>
       <directory>target</directory>
-      <outputDirectory>webapps</outputDirectory>
+      <outputDirectory>webapps.demo</outputDirectory>
       <includes>
         <include>test-jndi.xml</include>
       </includes>
     </fileSet>
     <fileSet>
-      <directory>target</directory>
-      <outputDirectory></outputDirectory>
+      <directory>target/lib/jndi</directory>
+      <outputDirectory>lib/jndi.demo</outputDirectory>
       <includes>
-        <include>lib/jndi/**</include>
+        <include>*.jar</include>
       </includes>
     </fileSet>
   </fileSets>
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
index fbd3626..12bc848 100644
--- 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
@@ -55,8 +55,8 @@
     
     private String resourceNameMappingInjectionResult;
     private String envEntryOverrideResult;
-    private String postConstructResult = "PostConstruct method called: FALSE";
-    private String preDestroyResult = "PreDestroy method called: NOT YET";
+    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;
@@ -71,15 +71,14 @@
     
     private void postConstruct ()
     {
-        String tmp = (myDS == null?"":myDS.toString());
-        resourceNameMappingInjectionResult= "Injection of resource to locally mapped name (java:comp/env/mydatasource as java:comp/env/mydatasource1): "+String.valueOf(myDS);
-        envEntryOverrideResult = "Override of EnvEntry in jetty-env.xml (java:comp/env/wiggle): "+(wiggle==55.0?"PASS":"FAIL(expected 55.0, got "+wiggle+")");
-        postConstructResult = "PostConstruct method called: PASS";
+        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: PASS";
+        preDestroyResult = "PreDestroy method called: <span class=\"pass\">PASS</span>";
     }
     
     
@@ -90,13 +89,13 @@
         {
             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?"PASS":"FAIL(expected 4000, got "+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?"PASS":"FAIL(expected 100, got "+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?"PASS":"FAIL");
+            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?"PASS": "FAIL");
+            mailSessionResult = "Mail Session lookup result (java:comp/env/mail/Session): "+(myMailSession!=null?"<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>";
         }
         catch (Exception e)
         {
@@ -129,6 +128,7 @@
             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>");
             
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
index de012e7..4f4ded0 100644
--- 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
@@ -35,7 +35,7 @@
  -->
 
   <Set name="contextPath">/test-jndi</Set>
-  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test-jndi.war</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
index 1fe7679..3e877a5 100644
--- 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
@@ -7,14 +7,9 @@
 <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>
-
-
-  <!-- only necessary for mvn jetty:run -->
-
-
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
 
 
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
index 80bb1c7..5382876 100644
--- a/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
+++ b/tests/test-webapps/test-jndi-webapp/src/main/webapp/index.html
@@ -3,12 +3,7 @@
     <TITLE>JNDI Test WebApp</TITLE>
     <META http-equiv="Pragma" content="no-cache">
     <META http-equiv="Cache-Control" content="no-cache,no-store">
-    <style>
-       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;}
-    </style>
+    <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>
@@ -29,21 +24,15 @@
 
 <h2>Preparation</h2>
 <p>
-  <ol>
-    <li>Ensure that you have downloaded and unpacked the test-jndi-webapp-&lt;version&gt;-config.jar, where &lt;version&gt; is replaced by the desired version number. The following files will be created:
-     <pre>
-      lib/jndi/test-mock-resources-&lt;version&gt;.jar (where &lt;version&gt; is replaced by the particular version number)
-      webapps/test-jndi.xml
-     </pre>
-    </li>
-    <li>Edit your $JETTY_HOME/start.ini file and uncomment the following lines:
-      <pre>
-      OPTIONS=plus
-      etc/jetty-plus.xml
-      </pre>
-    </li>
-  </ol>
+To enable JNDI edit the start.ini or start.d/*.ini files to include "OPTIONS=jndi".
 </p>
+<p>
+For the jetty distribution demos, jndi is already enabled in the start.d/900-demo.ini file and also enables the
+jndi.demo option which includes some mock resources used by the test.
+</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>
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-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
index d1c38d2..2fc52ba 100644
--- 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
@@ -17,17 +17,17 @@
       </excludes>
     </fileSet>
     <fileSet>
-       <directory>target</directory>
-      <outputDirectory>webapps</outputDirectory>
+      <directory>target</directory>
+      <outputDirectory>webapps.demo</outputDirectory>
       <includes>
         <include>test-spec.xml</include>
       </includes>
     </fileSet>
     <fileSet>
-      <directory>target</directory>
-      <outputDirectory></outputDirectory>
+      <directory>target/lib/jndi</directory>
+      <outputDirectory>lib/jndi.demo</outputDirectory>
       <includes>
-        <include>lib/jndi/**</include>
+        <include>*.jar</include>
       </includes>
     </fileSet>
   </fileSets>
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
index b3eecea..b90f356 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/AnnotationTest.java
@@ -94,18 +94,18 @@
     @PostConstruct
     private void myPostConstructMethod ()
     {       
-        postConstructResult = "Called";
+        postConstructResult = "<span class=\"pass\">PASS</span>";
        try 
        {
-           dsResult = (myDS==null?"FAIL":"myDS="+myDS.toString());
+           dsResult = (myDS==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">myDS="+myDS.toString()+"</span>");
        }
        catch (Exception e)
        {
-           dsResult = "FAIL: "+e;
+           dsResult = "<span class=\"fail\">FAIL:</span> "+e;
        }
 
 
-       envResult = (maxAmount==null?"FAIL":"maxAmount="+maxAmount.toString());
+       envResult = (maxAmount==null?"FAIL</span>":"<span class=\"pass\">maxAmount="+maxAmount.toString()+"</span>");
        
        try
        {
@@ -114,10 +114,10 @@
        }
        catch (Exception e)
        {
-           envLookupResult = "FAIL: "+e;
+           envLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
        }
 
-      envResult2 = (minAmount==null?"FAIL":"minAmount="+minAmount.toString());
+      envResult2 = (minAmount==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">minAmount="+minAmount.toString()+"</span>");
       try
       {
           InitialContext ic = new InitialContext();
@@ -125,9 +125,9 @@
       }
       catch (Exception e)
       {
-          envLookupResult2 = "FAIL: "+e;
+          envLookupResult2 = "<span class=\"fail\">FAIL:</span> "+e;
       }
-      envResult3 = (minAmount==null?"FAIL":"avgAmount="+avgAmount.toString());
+      envResult3 = (minAmount==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">avgAmount="+avgAmount.toString()+"</span>");
       try
       {
           InitialContext ic = new InitialContext();
@@ -135,7 +135,7 @@
       }
       catch (Exception e)
       {
-          envLookupResult3 = "FAIL: "+e;
+          envLookupResult3 = "<span class=\"fail\">FAIL:</span> "+e;
       }
       
       
@@ -147,10 +147,10 @@
        }
        catch (Exception e)
        {
-           dsLookupResult = "FAIL: "+e;
+           dsLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
        }
        
-       txResult = (myUserTransaction==null?"FAIL":"myUserTransaction="+myUserTransaction);
+       txResult = (myUserTransaction==null?"<span class=\"fail\">FAIL</span>":"<span class=\"pass\">myUserTransaction="+myUserTransaction+"</span>");
        try
        {
            InitialContext ic = new InitialContext();
@@ -158,7 +158,7 @@
        }
        catch (Exception e)
        {
-           txLookupResult = "FAIL: "+e;
+           txLookupResult = "<span class=\"fail\">FAIL:</span> "+e;
        }
     }
     
@@ -189,6 +189,7 @@
             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>");
 
@@ -196,14 +197,14 @@
             out.println("<pre>");
             out.println("initParams={@WebInitParam(name=\"fromAnnotation\", value=\"xyz\")}");
             out.println("</pre>");
-            out.println("<br/><b>Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "PASS": "FAIL"));
+            out.println("<br/><b>Result: "+("xyz".equals(config.getInitParameter("fromAnnotation"))? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>");
 
             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("<br/><b>Result: "+(fragInitParamResult? "PASS": "FAIL"));
+            out.println("<br/><b>Result: "+(fragInitParamResult? "<span class=\"pass\">PASS": "<span class=\"fail\">FAIL")+"</span>");
 
 
              __HandlesTypes = Arrays.asList( "javax.servlet.GenericServlet", 
@@ -231,27 +232,27 @@
                  }
                 
                  if (classNames.size() != __HandlesTypes.size())
-                     out.println("<br/>FAIL");
+                     out.println("<br/><span class=\"fail\">FAIL</span>");
                  else if (!classNames.containsAll(__HandlesTypes))
-                     out.println("<br/>FAIL");
+                     out.println("<br/><span class=\"fail\">FAIL</span>");
                  else
-                     out.println("<br/>PASS");
+                     out.println("<br/><span class=\"pass\">PASS</span>");
              }
              else
-                 out.print("<br/>FAIL (No such attribute com.acme.Foo)");
+                 out.print("<br/><span class=\"fail\">FAIL</span> (No such attribute com.acme.Foo)");
              out.println("</b>");
 
             out.println("<h2>Complete Servlet Registration</h2>");
             Boolean complete = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.complete");
-            out.println("<br/><b>Result: "+(complete.booleanValue()?"PASS":"FAIL")+"</b>");
+            out.println("<br/><b>Result: "+(complete.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
             
             out.println("<h2>ServletContextListener Programmatic Registration from ServletContainerInitializer</h2>");
             Boolean programmaticListener = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerTest");
-            out.println("<br/><b>Result: "+(programmaticListener.booleanValue()?"PASS":"FAIL")+"</b>");
+            out.println("<br/><b>Result: "+(programmaticListener.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
             
             out.println("<h2>ServletContextListener Programmatic Registration Prevented from ServletContextListener</h2>");
             Boolean programmaticListenerPrevention = (Boolean)config.getServletContext().getAttribute("com.acme.AnnotationTest.listenerRegoTest");
-            out.println("<br/><b>Result: "+(programmaticListenerPrevention.booleanValue()?"PASS":"FAIL")+"</b>");
+            out.println("<br/><b>Result: "+(programmaticListenerPrevention.booleanValue()?"<span class=\"pass\">PASS":"<span class=\"fail\">FAIL")+"</span></b>");
             
             out.println("<h2>@PostConstruct Callback</h2>");
             out.println("<pre>");
@@ -281,11 +282,11 @@
             out.println("@Resource(name=\"minAmount\")");
             out.println("private Double minAmount;");
             out.println("</pre>");
-            out.println("<br/><b>Result: "+envResult+": "+(maxAmount.compareTo(new Double(55))==0?" PASS":" FAIL")+"</b>");     
+            out.println("<br/><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>");
-            out.println("<br/><b>Result: "+envResult2+": "+(minAmount.compareTo(new Double("0.99"))==0?" PASS":" FAIL")+"</b>");     
+            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>");
-            out.println("<br/><b>Result: "+envResult3+": "+(avgAmount.compareTo(new Double("1.25"))==0?" PASS":" FAIL")+"</b>");     
+            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>");          
             out.println("<h2>@Resource Injection for UserTransaction </h2>");
             out.println("<pre>");
@@ -294,22 +295,6 @@
             out.println("</pre>");
             out.println("<br/><b>Result: "+txResult+"</b>");
             out.println("<br/><b>JNDI Lookup Result: "+txLookupResult+"</b>");
-            out.println("<h2>DeclaresRoles</h2>");
-            out.println("<p>Login as user \"admin\" with password \"admin\" when prompted after clicking the button below to test @DeclareRoles annotation</p>");
-            String context = request.getContextPath();
-            if (!context.endsWith("/"))
-                context += "/";
-            context += "role/";
-            out.println("<form action="+context+" method=\"post\"><button type=\"submit\">Test Role Annotations</button></form>");
-
-            out.println("<h2>ServletSecurity</h2>");
-            out.println("<p>Login as user \"admin\" with password \"admin\" when prompted after clicking the button below to test @ServletSecurity annotation</p>");
-            context = request.getContextPath();
-            if (!context.endsWith("/"))
-                context += "/";
-            context += "sec/foo";
-            out.println("<form action="+context+" method=\"post\"><button type=\"submit\">Test ServletSecurity Annotation</button></form>");
-
 
             out.println("</body>");            
             out.println("</html>");
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java
index 9498e06..8732297 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/MultiPartTest.java
@@ -62,8 +62,9 @@
             response.setContentType("text/html");
             ServletOutputStream out = response.getOutputStream();
             out.println("<html>");
-            out.println("<h1>Results</h1>");
+            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();
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java
index 191c153..303f35c 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/RoleAnnotationTest.java
@@ -64,17 +64,18 @@
             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?" PASS":" FAIL")+"</b>");
+            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?" PASS":" FAIL")+"</b>");
+            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==false?" PASS":" FAIL")+"</b>");
+            out.println("<br/><b>Result: isUserInRole(\"user\")="+result+":"+ (result==false?" <span class=\"pass\">PASS":" <span class=\"fail\">FAIL")+"</span></b>");
             String context = _config.getServletContext().getContextPath();
             if (!context.endsWith("/"))
                 context += "/";
diff --git a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java
index 0c5989a..017f374 100644
--- a/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java
+++ b/tests/test-webapps/test-servlet-spec/test-spec-webapp/src/main/java/com/acme/SecuredServlet.java
@@ -40,13 +40,14 @@
     {
         PrintWriter writer = resp.getWriter();
         writer.println( "<html>");
-        writer.println( "<body>");
-        writer.println("<h1>@ServletSecurity</h2>");
+        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("<br/><b>Result: "+true+"</b>");
+        writer.println("<p><b>Result: <span class=\"pass\">PASS</span></b></p>");
         String context = getServletConfig().getServletContext().getContextPath();
         if (!context.endsWith("/"))
             context += "/";
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
index 1ec49c7..aa5c782 100644
--- 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
@@ -9,10 +9,10 @@
 <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>
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
 
 
   <!-- =============================================================== -->
@@ -39,7 +39,7 @@
   -->
 
   <Set name="contextPath">/test-spec</Set>
-  <Set name="war"><SystemProperty name="jetty.home"/>/webapps/test-spec.war</Set>
+  <Set name="war"><Property name="jetty.webapps"/>/test-spec.war</Set>
   <Set name="configurationDiscovered">true</Set>
 
   <Get name="securityHandler">
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
index 88202d3..450edda 100644
--- 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
@@ -8,13 +8,10 @@
 
 <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>
-
-
+    <Arg>
+      <New class="com.acme.MockUserTransaction"/>
+    </Arg>
+  </New>
 
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
index 6f63eaf..854082f 100644
--- 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
@@ -3,13 +3,7 @@
     <TITLE>Test Specification WebApp</TITLE>
     <META http-equiv="Pragma" content="no-cache">
     <META http-equiv="Cache-Control" content="no-cache,no-store">
-    <style>
-       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;}
-    </style>
-
+    <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>
@@ -21,47 +15,45 @@
   <span style="color:red; font-variant:small-caps; font-weight:bold">Test Web Application Only - Do NOT Deploy in Production</span>
 </center>
 <P>
-<h1>Advanced Servlet Specification Test WebApp</h1>
+<h1>Servlet 3.1 Test WebApp</h1>
 
 <p>
-This example uses and tests newer aspects of the servlet specification such as annotations, web-fragments and servlet container initializers.
+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>
 
-<h2>Preparation</h2>
-<p>
- <ol>
-  <li>Ensure that you have downloaded and unpacked the test-spec-webapp-&lt;version&gt;-config.jar, where <version> is replaced by the desired version number. The following files will be created:
-      <pre>
-      lib/jndi/test-mock-resources-&lt;version&gt;.jar (where &lt;version&gt; is replaced by the particular version number)
-      webapps/test-spec.xml
-      </pre>
-  </li>
-  <li>Edit your $JETTY_HOME/start.ini file and uncomment the following lines:
-      <pre>
-      OPTIONS=plus
-      etc/jetty-plus.xml
-      OPTIONS=annotations
-      etc/jetty-annotations.xml
-      </pre>
-  </li>
- </ol>
-
-</p>
-
-<h2>The Tests</h2>
-
 <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>
+@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>
 
-
 <center>
   <hr/>
   <a href="http://www.eclipse.org/jetty"><img style="border:0" src="images/small_powered_by.gif"/></a>
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
index 1b44dea..c2f6ade 100644
--- 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
@@ -1,5 +1,9 @@
 
-<HTML><HEAD><TITLE>Annotation Test</TITLE></HEAD>
+<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>
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..4ecc2cb
--- /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;}
+h3 {font-size:100%; letter-spacing: 0.1em;}
+
+span.pass { color: green; }
+span.fail { color:red; }