Merge branch 'master' into release-9
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
index 55a82f7..3f34a23 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
@@ -85,8 +85,7 @@
  */
 public class DeferredContentProvider implements AsyncContentProvider, Closeable
 {
-    private static final Callback EMPTY_CALLBACK = new Callback.Adapter();
-    private static final AsyncChunk CLOSE = new AsyncChunk(BufferUtil.EMPTY_BUFFER, EMPTY_CALLBACK);
+    private static final AsyncChunk CLOSE = new AsyncChunk(BufferUtil.EMPTY_BUFFER, Callback.Adapter.INSTANCE);
 
     private final Object lock = this;
     private final Queue<AsyncChunk> chunks = new ArrayQueue<>(4, 64, lock);
@@ -130,7 +129,7 @@
      */
     public boolean offer(ByteBuffer buffer)
     {
-        return offer(buffer, EMPTY_CALLBACK);
+        return offer(buffer, Callback.Adapter.INSTANCE);
     }
 
     public boolean offer(ByteBuffer buffer, Callback callback)
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
index caafeba..a4625e3 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
@@ -126,7 +126,7 @@
         {
             try
             {
-                if (!parse(buffer))
+                if (parse(buffer))
                     return false;
 
                 int read = endPoint.fill(buffer);
@@ -134,7 +134,7 @@
                     LOG.debug("Read {} bytes from {}", read, endPoint);
                 if (read > 0)
                 {
-                    if (!parse(buffer))
+                    if (parse(buffer))
                         return false;
                 }
                 else if (read == 0)
@@ -159,7 +159,7 @@
 
     private boolean parse(ByteBuffer buffer)
     {
-        return !parser.parse(buffer);
+        return parser.parse(buffer);
     }
 
     private void shutdown()
@@ -199,9 +199,11 @@
             // from an onFailure() handler or by blocking code waiting for completion.
             getHttpDestination().close(this);
             getEndPoint().shutdownOutput();
-            LOG.debug("{} oshut", this);
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} oshut", this);
             getEndPoint().close();
-            LOG.debug("{} closed", this);
+            if (LOG.isDebugEnabled())
+                LOG.debug("{} closed", this);
 
             abort(failure);
         }
@@ -347,7 +349,8 @@
                             @Override
                             public void resume()
                             {
-                                LOG.debug("Content consumed asynchronously, resuming processing");
+                                if (LOG.isDebugEnabled())
+                                    LOG.debug("Content consumed asynchronously, resuming processing");
                                 process();
                             }
 
@@ -357,11 +360,14 @@
                                 close(x);
                             }
                         };
-                        channel.content(buffer, callback);
+                        if (!channel.content(buffer, callback))
+                            return true;
                         return callback.tryComplete();
                     }
                     else
+                    {
                         noChannel(request);
+                    }
                     break;
                 }
                 case STD_ERR:
@@ -409,7 +415,7 @@
 
         private void noChannel(int request)
         {
-            // TODO: what here ?
+            LOG.debug("Channel not found for request {}", request);
         }
     }
 }
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java
index 5e1175b..8fc2ad2 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java
@@ -99,7 +99,7 @@
         int id = getHttpChannel().getRequest();
         boolean hasContent = content.hasContent();
         Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders,
-                hasContent ? callback : new Callback.Adapter());
+                hasContent ? callback : Callback.Adapter.INSTANCE);
         if (hasContent)
         {
             getHttpChannel().flush(headersResult);
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
index e113dd2..b737f22 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
@@ -37,9 +37,17 @@
     private static final byte[] COLON = new byte[]{':', ' '};
     private static final byte[] EOL = new byte[]{'\r', '\n'};
 
+    private final boolean sendStatus200;
+
     public ServerGenerator(ByteBufferPool byteBufferPool)
     {
+        this(byteBufferPool, true);
+    }
+
+    public ServerGenerator(ByteBufferPool byteBufferPool, boolean sendStatus200)
+    {
         super(byteBufferPool);
+        this.sendStatus200 = sendStatus200;
     }
 
     public Result generateResponseHeaders(int request, int code, String reason, HttpFields fields, Callback callback)
@@ -50,14 +58,17 @@
         List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
         int length = 0;
 
-        // Special 'Status' header
-        bytes.add(STATUS);
-        length += STATUS.length + COLON.length;
-        if (reason == null)
-            reason = HttpStatus.getMessage(code);
-        byte[] responseBytes = (code + " " + reason).getBytes(utf8);
-        bytes.add(responseBytes);
-        length += responseBytes.length + EOL.length;
+        if (code != 200 || sendStatus200)
+        {
+            // Special 'Status' header
+            bytes.add(STATUS);
+            length += STATUS.length + COLON.length;
+            if (reason == null)
+                reason = HttpStatus.getMessage(code);
+            byte[] responseBytes = (code + " " + reason).getBytes(utf8);
+            bytes.add(responseBytes);
+            length += responseBytes.length + EOL.length;
+        }
 
         // Other headers
         for (HttpField field : fields)
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
index 45a348f..8303553 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
@@ -29,6 +29,10 @@
     private State state = State.HEADER;
     private int padding;
 
+    /**
+     * @param buffer the bytes to parse
+     * @return true if the caller should stop parsing, false if the caller should continue parsing
+     */
     public boolean parse(ByteBuffer buffer)
     {
         while (true)
@@ -53,9 +57,16 @@
                     {
                         ContentParser.Result result = contentParser.parse(buffer);
                         if (result == ContentParser.Result.PENDING)
+                        {
+                            // Not enough data, signal to read/parse more.
                             return false;
-                        else if (result == ContentParser.Result.ASYNC)
+                        }
+                        if (result == ContentParser.Result.ASYNC)
+                        {
+                            // The content will be processed asynchronously, signal to stop
+                            // parsing; the async operation will eventually resume parsing.
                             return true;
+                        }
                     }
                     padding = headerParser.getPaddingLength();
                     state = State.PADDING;
@@ -99,6 +110,13 @@
 
         public void onHeaders(int request);
 
+        /**
+         * @param request the request id
+         * @param stream the stream type
+         * @param buffer the content bytes
+         * @return true to signal to the parser to stop parsing, false to continue parsing
+         * @see Parser#parse(java.nio.ByteBuffer)
+         */
         public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer);
 
         public void onEnd(int request);
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
index 26983fe..83a9a79 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
@@ -98,7 +98,7 @@
                 {
                     case HEADERS:
                     {
-                        if (httpParser.parseHeaders(buffer))
+                        if (httpParser.parseNext(buffer))
                             state = State.CONTENT_MODE;
                         remaining = buffer.remaining();
                         break;
@@ -124,7 +124,8 @@
                     }
                     case HTTP_CONTENT:
                     {
-                        httpParser.parseContent(buffer);
+                        if (httpParser.parseNext(buffer))
+                            return true;
                         remaining = buffer.remaining();
                         break;
                     }
@@ -165,21 +166,22 @@
 
                         // Need to set the response status so the
                         // HttpParser can handle the content properly.
-                        String[] parts = httpField.getValue().split(" ");
-                        int code = Integer.parseInt(parts[0]);
+                        String value = httpField.getValue();
+                        String[] parts = value.split(" ");
+                        String status = parts[0];
+                        int code = Integer.parseInt(status);
                         httpParser.setResponseStatus(code);
-                        String reason = parts.length > 1 ? parts[1] : HttpStatus.getMessage(code);
+                        String reason = parts.length > 1 ? value.substring(status.length()) : HttpStatus.getMessage(code);
 
-                        notifyBegin(code, reason);
+                        notifyBegin(code, reason.trim());
                         notifyHeaders(fields);
                     }
                 }
                 else
                 {
+                    fields.add(httpField);
                     if (seenResponseCode)
                         notifyHeader(httpField);
-                    else
-                        fields.add(httpField);
                 }
             }
             catch (Throwable x)
@@ -251,8 +253,7 @@
         @Override
         public boolean content(ByteBuffer buffer)
         {
-            notifyContent(buffer);
-            return false;
+            return notifyContent(buffer);
         }
 
         private boolean notifyContent(ByteBuffer buffer)
@@ -302,22 +303,11 @@
         public void reset()
         {
             super.reset();
+            setResponseStatus(200);
             setState(State.HEADER);
         }
 
         @Override
-        protected boolean parseHeaders(ByteBuffer buffer)
-        {
-            return super.parseHeaders(buffer);
-        }
-
-        @Override
-        protected boolean parseContent(ByteBuffer buffer)
-        {
-            return super.parseContent(buffer);
-        }
-
-        @Override
         protected void setResponseStatus(int status)
         {
             super.setResponseStatus(status);
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java
index 91459ed..f174833 100644
--- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java
@@ -40,9 +40,9 @@
     private volatile boolean shutdown;
     private volatile boolean aborted;
 
-    public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request)
+    public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request, boolean sendStatus200)
     {
-        this.generator = new ServerGenerator(byteBufferPool);
+        this.generator = new ServerGenerator(byteBufferPool, sendStatus200);
         this.flusher = flusher;
         this.request = request;
     }
@@ -57,23 +57,20 @@
         {
             if (lastContent)
             {
-                Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
-                        info.getHttpFields(), new Callback.Adapter());
-                Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
+                Generator.Result headersResult = generateResponseHeaders(info, Callback.Adapter.INSTANCE);
+                Generator.Result contentResult = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
                 flusher.flush(headersResult, contentResult);
             }
             else
             {
-                Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
-                        info.getHttpFields(), callback);
+                Generator.Result headersResult = generateResponseHeaders(info, callback);
                 flusher.flush(headersResult);
             }
         }
         else
         {
-            Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
-                    info.getHttpFields(), new Callback.Adapter());
-            Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, aborted, callback);
+            Generator.Result headersResult = generateResponseHeaders(info, Callback.Adapter.INSTANCE);
+            Generator.Result contentResult = generateResponseContent(content, lastContent, callback);
             flusher.flush(headersResult, contentResult);
         }
 
@@ -88,7 +85,7 @@
         {
             if (lastContent)
             {
-                Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
+                Generator.Result result = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
                 flusher.flush(result);
             }
             else
@@ -99,7 +96,7 @@
         }
         else
         {
-            Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback);
+            Generator.Result result = generateResponseContent(content, lastContent, callback);
             flusher.flush(result);
         }
 
@@ -107,6 +104,16 @@
             flusher.shutdown();
     }
 
+    protected Generator.Result generateResponseHeaders(HttpGenerator.ResponseInfo info, Callback callback)
+    {
+        return generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), callback);
+    }
+
+    protected Generator.Result generateResponseContent(ByteBuffer buffer, boolean lastContent, Callback callback)
+    {
+        return generator.generateResponseContent(request, buffer, lastContent, aborted, callback);
+    }
+
     @Override
     public void abort()
     {
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java
index 1aa253a..027bf00 100644
--- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java
@@ -41,16 +41,18 @@
 
     private final ConcurrentMap<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>();
     private final Connector connector;
+    private final boolean sendStatus200;
     private final Flusher flusher;
     private final HttpConfiguration configuration;
     private final ServerParser parser;
 
-    public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration)
+    public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration, boolean sendStatus200)
     {
         super(endPoint, connector.getExecutor());
         this.connector = connector;
         this.flusher = new Flusher(endPoint);
         this.configuration = configuration;
+        this.sendStatus200 = sendStatus200;
         this.parser = new ServerParser(new ServerListener());
     }
 
@@ -119,7 +121,8 @@
         {
             // TODO: handle flags
             HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(),
-                    new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request), new ByteBufferQueuedHttpInput());
+                    new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request, sendStatus200),
+                    new ByteBufferQueuedHttpInput());
             HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel);
             if (existing != null)
                 throw new IllegalStateException();
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java
index 66ec4f3..9f820e9 100644
--- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java
@@ -27,16 +27,23 @@
 public class ServerFCGIConnectionFactory extends AbstractConnectionFactory
 {
     private final HttpConfiguration configuration;
+    private final boolean sendStatus200;
 
     public ServerFCGIConnectionFactory(HttpConfiguration configuration)
     {
+        this(configuration, true);
+    }
+
+    public ServerFCGIConnectionFactory(HttpConfiguration configuration, boolean sendStatus200)
+    {
         super("fcgi/1.0");
         this.configuration = configuration;
+        this.sendStatus200 = sendStatus200;
     }
 
     @Override
     public Connection newConnection(Connector connector, EndPoint endPoint)
     {
-        return new ServerFCGIConnection(connector, endPoint, configuration);
+        return new ServerFCGIConnection(connector, endPoint, configuration, sendStatus200);
     }
 }
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
index b1a85c1..a52a044 100644
--- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
@@ -20,7 +20,11 @@
 
 import java.io.IOException;
 import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Random;
+import java.util.concurrent.TimeUnit;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -28,30 +32,49 @@
 
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.FutureResponseListener;
 import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.Callback;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class FastCGIProxyServletTest
 {
+    @Parameterized.Parameters
+    public static Collection<Object[]> parameters()
+    {
+        return Arrays.asList(new Object[]{true}, new Object[]{false});
+    }
+
+    private final boolean sendStatus200;
     private Server server;
     private ServerConnector httpConnector;
     private ServerConnector fcgiConnector;
     private HttpClient client;
 
+    public FastCGIProxyServletTest(boolean sendStatus200)
+    {
+        this.sendStatus200 = sendStatus200;
+    }
+
     public void prepare(HttpServlet servlet) throws Exception
     {
         server = new Server();
         httpConnector = new ServerConnector(server);
         server.addConnector(httpConnector);
 
-        fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration()));
+        fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration(), sendStatus200));
         server.addConnector(fcgiConnector);
 
         final String contextPath = "/";
@@ -89,7 +112,24 @@
     @Test
     public void testGETWithSmallResponseContent() throws Exception
     {
-        final byte[] data = new byte[1024];
+        testGETWithResponseContent(1024, 0);
+    }
+
+    @Test
+    public void testGETWithLargeResponseContent() throws Exception
+    {
+        testGETWithResponseContent(16 * 1024 * 1024, 0);
+    }
+
+    @Test
+    public void testGETWithLargeResponseContentWithSlowClient() throws Exception
+    {
+        testGETWithResponseContent(16 * 1024 * 1024, 1);
+    }
+
+    private void testGETWithResponseContent(int length, final long delay) throws Exception
+    {
+        final byte[] data = new byte[length];
         new Random().nextBytes(data);
 
         final String path = "/foo/index.php";
@@ -99,13 +139,35 @@
             protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
             {
                 Assert.assertTrue(req.getRequestURI().endsWith(path));
+                resp.setContentLength(data.length);
                 resp.getOutputStream().write(data);
             }
         });
 
-        ContentResponse response = client.newRequest("localhost", httpConnector.getLocalPort())
-                .path(path)
-                .send();
+        Request request = client.newRequest("localhost", httpConnector.getLocalPort())
+                .onResponseContentAsync(new Response.AsyncContentListener()
+                {
+                    @Override
+                    public void onContent(Response response, ByteBuffer content, Callback callback)
+                    {
+                        try
+                        {
+                            if (delay > 0)
+                                TimeUnit.MILLISECONDS.sleep(delay);
+                            callback.succeeded();
+                        }
+                        catch (InterruptedException x)
+                        {
+                            callback.failed(x);
+                        }
+                    }
+                })
+                .path(path);
+        FutureResponseListener listener = new FutureResponseListener(request, length);
+        request.send(listener);
+
+        ContentResponse response = listener.get(30, TimeUnit.SECONDS);
+
         Assert.assertEquals(200, response.getStatus());
         Assert.assertArrayEquals(data, response.getContent());
     }
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
index e4d8348..700d0b9 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
@@ -236,8 +236,7 @@
 
     public void doStart () throws Exception
     {
-        //Set up the pattern that tells us where the jars are that need scanning for
-        //stuff like taglibs so we can tell jasper about it (see TagLibConfiguration)
+        //Set up the pattern that tells us where the jars are that need scanning
 
         //Allow user to set up pattern for names of jars from the container classpath
         //that will be scanned - note that by default NO jars are scanned
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java b/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java
deleted file mode 100644
index 3bf681e..0000000
--- a/jetty-osgi/jetty-osgi-boot-jsp/src/main/java/org/eclipse/jetty/osgi/boot/jsp/TagLibOSGiConfiguration.java
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.osgi.boot.jsp;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.LinkedHashSet;
-
-import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
-import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
-import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.webapp.TagLibConfiguration;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleReference;
-import org.osgi.service.packageadmin.PackageAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * <p>
- * Replacement for {@link TagLibConfiguration} for the OSGi integration.
- * </p>
- * <p>
- * In the case of a WAB, tlds can be located in OSGi bundles that are
- * dependencies of the WAB. It is expected that each WAB lists the
- * symbolic-names of the bundles that contain tld files. The list is defined as
- * the value of the header 'Require-TldBundle'
- * </p>
- * <p>
- * Discussions about this are logged in
- * https://bugs.eclipse.org/bugs/show_bug.cgi?id=306971
- * </p>
- */
-public class TagLibOSGiConfiguration extends TagLibConfiguration
-{
-    private static final Logger LOG = Log.getLogger(TagLibOSGiConfiguration.class);
-
-    private ServiceTracker packageAdminServiceTracker = null;
-
-    /**
-     * Override the preConfigure; locates the bundles that contain tld files
-     * according to the value of the manifest header Require-TldBundle.
-     * <p>
-     * Set or add to the property TldProcessor.TLDResources the list of located
-     * jars so that the super class will scan those.
-     * </p>
-     */
-    public void preConfigure(WebAppContext context) throws Exception
-    {
-        String requireTldBundle = (String) context.getAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE);
-        if (requireTldBundle != null)
-        {
-            Collection<Resource> resources = getRequireTldBundleAsJettyResources(context, requireTldBundle);
-            if (resources != null && !resources.isEmpty())
-            {
-                Collection<Resource> previouslySet = (Collection<Resource>) context.getAttribute(TagLibConfiguration.TLD_RESOURCES);
-                if (previouslySet != null)
-                {
-                    resources.addAll(previouslySet);
-                }
-                context.setAttribute(TagLibConfiguration.TLD_RESOURCES, resources);
-            }
-        }
-        super.preConfigure(context);
-
-    }
-
-    /**
-     * @param requireTldBundle The comma separated list of bundles' symbolic
-     *            names that contain tld for this osgi webapp.
-     * @return The collection of jars or folders that match those bundles.
-     */
-    private Collection<Resource> getRequireTldBundleAsJettyResources(WebAppContext context, String requireTldBundle)
-    {
-        Bundle bundle = (Bundle) context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
-        PackageAdmin packAdmin = getBundleAdmin();
-        String[] symbNames = requireTldBundle.split(", ");
-        Collection<Resource> tlds = new LinkedHashSet<Resource>();
-        for (String symbName : symbNames)
-        {
-            Bundle[] bs = packAdmin.getBundles(symbName, null);
-            if (bs == null || bs.length == 0) 
-            { 
-                throw new IllegalArgumentException("Unable to locate the bundle '" + symbName
-                                                                                   + "' specified in the "
-                                                                                   + OSGiWebappConstants.REQUIRE_TLD_BUNDLE
-                                                                                   + " of the manifest of "
-                                                                                   + bundle.getSymbolicName()); 
-            }
-            // take the first one as it is the most recent version?
-            Enumeration<URL> en = bs[0].findEntries("META-INF", "*.tld", false);
-            boolean atLeastOneTldFound = false;
-            while (en.hasMoreElements())
-            {
-                atLeastOneTldFound = true;
-                URL oriUrl = en.nextElement();
-                try
-                {
-                    URL url = BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(oriUrl);
-                    Resource tldResource;
-                    tldResource = Resource.newResource(url);
-                    tlds.add(tldResource);
-                }
-                catch (Exception e)
-                {
-                    throw new IllegalArgumentException("Unable to locate the " + "tld resource in '"
-                            + oriUrl.toString()
-                            + "' in the bundle '"
-                            + bs[0].getSymbolicName()
-                            + "' while registering the "
-                            + OSGiWebappConstants.REQUIRE_TLD_BUNDLE
-                            + " of the manifest of "
-                            + bundle.getSymbolicName(), e);
-                }
-            }
-            if (!atLeastOneTldFound)
-            {
-                LOG.warn("No '/META-INF/*.tld' resources were found " + " in the bundle '"
-                         + bs[0].getSymbolicName()
-                         + "' while registering the "
-                         + OSGiWebappConstants.REQUIRE_TLD_BUNDLE
-                         + " of the manifest of "
-                         + bundle.getSymbolicName());
-            }
-        }
-        return tlds;
-    }
-
-    private PackageAdmin getBundleAdmin()
-    {
-        if (packageAdminServiceTracker == null)
-        {
-            Bundle bootBundle = ((BundleReference) OSGiWebappConstants.class.getClassLoader()).getBundle();
-            packageAdminServiceTracker = new ServiceTracker(bootBundle.getBundleContext(), PackageAdmin.class.getName(), null);
-            packageAdminServiceTracker.open();
-        }
-        return (PackageAdmin) packageAdminServiceTracker.getService();
-    }
-
-}
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
index 6a26482..6d31ebd 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/AbstractWebAppProvider.java
@@ -330,8 +330,6 @@
             // the webapp first
             applyMetaInfContextXml(rootResource);
 
-            // pass the value of the require tld bundle so that the TagLibOSGiConfiguration
-            // can pick it up.
             _webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles);
 
             //Set up some attributes
diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
index b4f6769..ce3c141 100644
--- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
+++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java
@@ -63,7 +63,7 @@
      *  </ol>
      *  
      *  We also allow individual bundles to specify particular bundles that might include TLDs via the Require-Tlds
-     *  MANIFEST.MF header. This is processed in the TagLibOSGiConfiguration class.
+     *  MANIFEST.MF header. 
      *  
      * @see org.eclipse.jetty.webapp.WebInfConfiguration#preConfigure(org.eclipse.jetty.webapp.WebAppContext)
      */
diff --git a/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java b/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java
index a233f1c..8a6f4e7 100644
--- a/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java
+++ b/jetty-overlay-deployer/src/test/java/org/eclipse/jetty/overlays/OverlayServer.java
@@ -51,7 +51,6 @@
                     org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
                     org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
                     org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName(),
-                    org.eclipse.jetty.webapp.TagLibConfiguration.class.getCanonicalName()
                 }
         );
         
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java
index ed24771..6ebc85c 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java
@@ -46,10 +46,15 @@
     {
         ServletInputStream input = request.getInputStream();
         DeferredContentProvider provider = new DeferredContentProvider();
-        input.setReadListener(new StreamReader(proxyRequest, request, provider));
+        input.setReadListener(newReadListener(proxyRequest, request, provider));
         return provider;
     }
 
+    protected ReadListener newReadListener(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
+    {
+        return new StreamReader(proxyRequest, request, provider);
+    }
+
     @Override
     protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
     {
@@ -59,7 +64,7 @@
             StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE);
             if (writeListener == null)
             {
-                writeListener = new StreamWriter(request, proxyResponse);
+                writeListener = newWriteListener(request, proxyResponse);
                 request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener);
 
                 // Set the data to write before calling setWriteListener(), because
@@ -82,7 +87,12 @@
             onResponseFailure(request, response, proxyResponse, x);
         }
     }
-    
+
+    protected StreamWriter newWriteListener(HttpServletRequest request, Response proxyResponse)
+    {
+        return new StreamWriter(request, proxyResponse);
+    }
+
     public static class Transparent extends AsyncProxyServlet
     {
         private final TransparentDelegate delegate = new TransparentDelegate(this);
@@ -101,14 +111,14 @@
         }
     }
 
-    private class StreamReader implements ReadListener, Callback
+    protected class StreamReader implements ReadListener, Callback
     {
         private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()];
         private final Request proxyRequest;
         private final HttpServletRequest request;
         private final DeferredContentProvider provider;
 
-        public StreamReader(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
+        protected StreamReader(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider)
         {
             this.proxyRequest = proxyRequest;
             this.request = request;
@@ -131,7 +141,7 @@
                 if (read > 0)
                 {
                     _log.debug("{} proxying content to upstream: {} bytes", requestId, read);
-                    provider.offer(ByteBuffer.wrap(buffer, 0, read), this);
+                    onRequestContent(proxyRequest, request, provider, buffer, 0, read, this);
                     // Do not call isReady() so that we can apply backpressure.
                     break;
                 }
@@ -140,6 +150,11 @@
                 _log.debug("{} asynchronous read pending on {}", requestId, input);
         }
 
+        protected void onRequestContent(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider, byte[] buffer, int offset, int length, Callback callback)
+        {
+            provider.offer(ByteBuffer.wrap(buffer, offset, length), callback);
+        }
+
         @Override
         public void onAllDataRead() throws IOException
         {
@@ -174,7 +189,7 @@
         }
     }
 
-    private class StreamWriter implements WriteListener
+    protected class StreamWriter implements WriteListener
     {
         private final HttpServletRequest request;
         private final Response proxyResponse;
@@ -184,14 +199,14 @@
         private int length;
         private Callback callback;
 
-        private StreamWriter(HttpServletRequest request, Response proxyResponse)
+        protected StreamWriter(HttpServletRequest request, Response proxyResponse)
         {
             this.request = request;
             this.proxyResponse = proxyResponse;
             this.state = WriteState.IDLE;
         }
 
-        private void data(byte[] bytes, int offset, int length, Callback callback)
+        protected void data(byte[] bytes, int offset, int length, Callback callback)
         {
             if (state != WriteState.IDLE)
                 throw new WritePendingException();
@@ -235,7 +250,7 @@
             }
         }
 
-        private void complete()
+        protected void complete()
         {
             buffer = null;
             offset = 0;
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
index 1c7cc26..7dbe2a9 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.proxy;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.UnknownHostException;
@@ -477,32 +478,17 @@
                     proxyRequest.getHeaders().toString().trim());
         }
 
-        proxyRequest.send(new ProxyResponseListener(request, response));
+        proxyRequest.send(newProxyResponseListener(request, response));
     }
 
     protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
     {
-        return new InputStreamContentProvider(request.getInputStream())
-        {
-            @Override
-            public long getLength()
-            {
-                return request.getContentLength();
-            }
+        return new ProxyInputStreamContentProvider(proxyRequest, request, request.getInputStream());
+    }
 
-            @Override
-            protected ByteBuffer onRead(byte[] buffer, int offset, int length)
-            {
-                _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
-                return super.onRead(buffer, offset, length);
-            }
-
-            @Override
-            protected void onReadFailure(Throwable failure)
-            {
-                onClientRequestFailure(proxyRequest, request, failure);
-            }
-        };
+    protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
+    {
+        return new ProxyResponseListener(request, response);
     }
 
     protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
@@ -716,12 +702,12 @@
         }
     }
 
-    private class ProxyResponseListener extends Response.Listener.Adapter
+    protected class ProxyResponseListener extends Response.Listener.Adapter
     {
         private final HttpServletRequest request;
         private final HttpServletResponse response;
 
-        public ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
+        protected ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
         {
             this.request = request;
             this.response = response;
@@ -811,4 +797,41 @@
             _log.debug("{} proxying complete", getRequestId(request));
         }
     }
+
+    protected class ProxyInputStreamContentProvider extends InputStreamContentProvider
+    {
+        private final Request proxyRequest;
+        private final HttpServletRequest request;
+
+        protected ProxyInputStreamContentProvider(Request proxyRequest, HttpServletRequest request, InputStream input)
+        {
+            super(input);
+            this.proxyRequest = proxyRequest;
+            this.request = request;
+        }
+
+        @Override
+        public long getLength()
+        {
+            return request.getContentLength();
+        }
+
+        @Override
+        protected ByteBuffer onRead(byte[] buffer, int offset, int length)
+        {
+            _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
+            return onRequestContent(proxyRequest, request, buffer, offset, length);
+        }
+
+        protected ByteBuffer onRequestContent(Request proxyRequest, final HttpServletRequest request, byte[] buffer, int offset, int length)
+        {
+            return super.onRead(buffer, offset, length);
+        }
+
+        @Override
+        protected void onReadFailure(Throwable failure)
+        {
+            onClientRequestFailure(proxyRequest, request, failure);
+        }
+    }
 }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
index 69ecf53..cb1479f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSession.java
@@ -109,6 +109,18 @@
         if (_invalid)
             throw new IllegalStateException();
     }
+    
+    /* ------------------------------------------------------------- */
+    /** Check to see if session has expired as at the time given.
+     * @param time
+     * @return
+     */
+    protected boolean checkExpiry(long time)
+    {
+        if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
+            return true;
+        return false;
+    }
 
     /* ------------------------------------------------------------- */
     @Override
@@ -317,7 +329,7 @@
             _lastAccessed=_accessed;
             _accessed=time;
 
-            if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
+            if (checkExpiry(time))
             {
                 invalidate();
                 return false;
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
index 786833d..b810e5c 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYClient.java
@@ -380,7 +380,7 @@
         private void closeConnections()
         {
             for (Session session : sessions)
-                session.goAway(new GoAwayInfo(), new Callback.Adapter());
+                session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
             sessions.clear();
         }
 
diff --git a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
index a91dc08..f7d984b 100644
--- a/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
+++ b/jetty-spdy/spdy-client/src/main/java/org/eclipse/jetty/spdy/client/SPDYConnection.java
@@ -167,7 +167,7 @@
     protected void goAway(ISession session)
     {
         if (session != null)
-            session.goAway(new GoAwayInfo(), new Callback.Adapter());
+            session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
     }
 
     private void shutdown(ISession session)
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java
index 449e2c2..cc7afb7 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/SPDYv3FlowControlStrategy.java
@@ -84,7 +84,7 @@
         if (dataInfo.consumed() == length && !stream.isClosed() && length > 0)
         {
             WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), stream.getId(), length);
-            session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, new Callback.Adapter());
+            session.control(stream, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, Callback.Adapter.INSTANCE);
         }
     }
 }
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
index f2babb8..469f3a8 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardSession.java
@@ -431,7 +431,7 @@
             {
                 RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
                 LOG.debug("Unknown stream {}", rstInfo);
-                rst(rstInfo, new Callback.Adapter());
+                rst(rstInfo, Callback.Adapter.INSTANCE);
             }
             else
             {
@@ -471,7 +471,7 @@
     public void onStreamException(StreamException x)
     {
         notifyOnFailure(listener, x); // TODO: notify StreamFrameListener if exists?
-        rst(new RstInfo(x.getStreamId(), x.getStreamStatus()), new Callback.Adapter());
+        rst(new RstInfo(x.getStreamId(), x.getStreamStatus()), Callback.Adapter.INSTANCE);
     }
 
     @Override
@@ -479,7 +479,7 @@
     {
         Throwable cause = x.getCause();
         notifyOnFailure(listener, cause == null ? x : cause);
-        goAway(x.getSessionStatus(), 0, TimeUnit.SECONDS, new Callback.Adapter());
+        goAway(x.getSessionStatus(), 0, TimeUnit.SECONDS, Callback.Adapter.INSTANCE);
     }
 
     private void onSyn(final SynStreamFrame frame)
@@ -570,7 +570,7 @@
             }
             RstInfo rstInfo = new RstInfo(streamId, StreamStatus.PROTOCOL_ERROR);
             LOG.debug("Duplicate stream, {}", rstInfo);
-            rst(rstInfo, new Callback.Adapter()); // We don't care (too much) if the reset fails.
+            rst(rstInfo, Callback.Adapter.INSTANCE); // We don't care (too much) if the reset fails.
             return null;
         }
         else
@@ -653,7 +653,7 @@
         {
             RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
             LOG.debug("Unknown stream {}", rstInfo);
-            rst(rstInfo, new Callback.Adapter());
+            rst(rstInfo, Callback.Adapter.INSTANCE);
         }
         else
         {
@@ -712,7 +712,7 @@
         }
         else
         {
-            control(null, frame, 0, TimeUnit.MILLISECONDS, new Callback.Adapter());
+            control(null, frame, 0, TimeUnit.MILLISECONDS, Callback.Adapter.INSTANCE);
         }
     }
 
@@ -736,7 +736,7 @@
         {
             RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
             LOG.debug("Unknown stream, {}", rstInfo);
-            rst(rstInfo, new Callback.Adapter());
+            rst(rstInfo, Callback.Adapter.INSTANCE);
         }
         else
         {
@@ -1238,7 +1238,7 @@
     {
         private CloseFrameBytes()
         {
-            super(null, new Callback.Adapter());
+            super(null, Callback.Adapter.INSTANCE);
         }
 
         @Override
diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
index dd4b6b5..778fe0b 100644
--- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
+++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/StandardStream.java
@@ -272,7 +272,7 @@
         if (!canReceive())
         {
             LOG.debug("Protocol error receiving {}, resetting", dataInfo);
-            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), new Adapter());
+            session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR), Callback.Adapter.INSTANCE);
             return;
         }
 
@@ -547,7 +547,7 @@
 
         private StreamCallback()
         {
-            this(new Adapter());
+            this(Callback.Adapter.INSTANCE);
         }
 
         private StreamCallback(Callback callback)
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java
index 57ba065..0317b2c 100644
--- a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpConnectionOverSPDY.java
@@ -63,7 +63,7 @@
         // First close then abort, to be sure that the connection cannot be reused
         // from an onFailure() handler or by blocking code waiting for completion.
         getHttpDestination().close(this);
-        session.goAway(new GoAwayInfo(), new Callback.Adapter());
+        session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
         abort(new AsynchronousCloseException());
     }
 
diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java
index 7708fd2..c136ea4 100644
--- a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java
+++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java
@@ -102,7 +102,7 @@
     public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
     {
         // SPDY push not supported
-        getHttpChannel().getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
+        getHttpChannel().getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), Callback.Adapter.INSTANCE);
         return null;
     }
 
diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/npn/npn-1.7.0_55.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_55.mod
similarity index 87%
rename from jetty-spdy/spdy-http-server/src/main/config/modules/npn/npn-1.7.0_55.mod
rename to jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_55.mod
index 06387a2..f886df8 100644
--- a/jetty-spdy/spdy-http-server/src/main/config/modules/npn/npn-1.7.0_55.mod
+++ b/jetty-spdy/spdy-http-server/src/main/config/modules/protonego-impl/npn-1.7.0_55.mod
@@ -1,9 +1,8 @@
 [name]
-npn-boot
+protonego-boot
 
 [files]
 http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.7.v20140316/npn-boot-1.1.7.v20140316.jar:lib/npn/npn-boot-1.1.7.v20140316.jar
 
-[ini-template]
---exec
+[exec]
 -Xbootclasspath/p:lib/npn/npn-boot-1.1.7.v20140316.jar
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
index 06ec2cf..18b8c5d 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/ProxyEngineSelector.java
@@ -154,7 +154,7 @@
     private void rst(Stream stream)
     {
         RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
-        stream.getSession().rst(rstInfo, new Callback.Adapter());
+        stream.getSession().rst(rstInfo, Callback.Adapter.INSTANCE);
     }
 
     public static class ProxyServerInfo
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 991c3be..2cd07d3 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
@@ -201,7 +201,7 @@
         {
             HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(HttpVersion.fromString(headers.get
                     ("version").getValue()), null, 0, 502, "SPDY reset received from upstream server", false);
-            send(info, null, true, new Callback.Adapter());
+            send(info, null, true, Callback.Adapter.INSTANCE);
         }
 
         @Override
diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
index 073ad96..5316575 100644
--- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
+++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/proxy/SPDYProxyEngine.java
@@ -189,7 +189,7 @@
                 Session existing = serverSessions.putIfAbsent(host, session);
                 if (existing != null)
                 {
-                    session.goAway(new GoAwayInfo(), new Callback.Adapter());
+                    session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
                     session = existing;
                 }
             }
@@ -222,7 +222,7 @@
     private void rst(Stream stream)
     {
         RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
-        stream.getSession().rst(rstInfo, new Callback.Adapter());
+        stream.getSession().rst(rstInfo, Callback.Adapter.INSTANCE);
     }
 
     private class ProxyPushStreamFrameListener implements StreamFrameListener
@@ -581,7 +581,7 @@
                 {
                     Session clientSession = clientStream.getSession();
                     RstInfo clientRstInfo = new RstInfo(clientStream.getId(), serverRstInfo.getStreamStatus());
-                    clientSession.rst(clientRstInfo, new Callback.Adapter());
+                    clientSession.rst(clientRstInfo, Callback.Adapter.INSTANCE);
                 }
             }
         }
diff --git a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
index d6a2a7b..7d15ab1 100644
--- a/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
+++ b/jetty-spdy/spdy-server/src/main/java/org/eclipse/jetty/spdy/server/SPDYServerConnectionFactory.java
@@ -182,7 +182,7 @@
     void closeSessions()
     {
         for (Session session : sessions)
-            session.goAway(new GoAwayInfo(), new Callback.Adapter());
+            session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE);
         sessions.clear();
     }
 
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java
index 99f8e98..bd6181f 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java
@@ -287,6 +287,7 @@
             PathFinder finder = new PathFinder();
             finder.setFileMatcher(matcher);
             finder.setBase(dir);
+            finder.setIncludeDirsInResults(true);
             Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,searchDepth,finder);
             hits.addAll(finder.getHits());
             Collections.sort(hits,new NaturalSort.Paths());
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java
index 74a9a42..05e08f9 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java
@@ -73,9 +73,18 @@
         return new File(test).toPath();
     }
 
-    public static PathMatcher getMatcher(String pattern)
+    public static PathMatcher getMatcher(final String rawpattern)
     {
         FileSystem fs = FileSystems.getDefault();
+        
+        String pattern = rawpattern;
+        
+        // Strip trailing slash (if present)
+        int lastchar = pattern.charAt(pattern.length() - 1);
+        if (lastchar == '/' || lastchar == '\\')
+        {
+            pattern = pattern.substring(0,pattern.length() - 1);
+        }
 
         // If using FileSystem.getPathMatcher() with "glob:" or "regex:"
         // use FileSystem default pattern behavior
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
index 4b2fd23..fff9e4d 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
@@ -82,7 +82,7 @@
 
         String expected[] = { "jmx", "client", "stats", "spdy", "deploy", "debug", "security", "npn", "ext", "websocket", "rewrite", "ipaccess", "xinetd",
                 "proxy", "webapp", "jndi", "lowresources", "https", "plus", "requestlog", "jsp", "monitor", "xml", "servlet", "jaas", "http", "base", "server",
-                "annotations" };
+                "annotations", "resources", "loggging" };
 
         Assert.assertThat("Module count: " + moduleNames,moduleNames.size(),is(expected.length));
     }
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java
index 78859ee..aef3da7 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java
@@ -62,6 +62,12 @@
     }
     
     @Test
+    public void testWithLogging() throws Exception
+    {
+        assertUseCase("home","base.logging","assert-logging.txt");
+    }
+
+    @Test
     public void testWithIncludeJettyDir_Logging() throws Exception
     {
         assertUseCase("home","base.with.include.jetty.dirs","assert-include-jetty-dir-logging.txt");
diff --git a/jetty-start/src/test/resources/usecases/assert-include-jetty-dir-logging.txt b/jetty-start/src/test/resources/usecases/assert-include-jetty-dir-logging.txt
index def27d7..4ecd847 100644
--- a/jetty-start/src/test/resources/usecases/assert-include-jetty-dir-logging.txt
+++ b/jetty-start/src/test/resources/usecases/assert-include-jetty-dir-logging.txt
@@ -1,5 +1,6 @@
 # The XMLs we expect (order is important)
 XML|${jetty.home}/etc/jetty-jmx.xml
+XML|${maven-test-resources}/extra-jetty-dirs/logging/etc/jetty-logging.xml
 XML|${jetty.home}/etc/jetty.xml
 XML|${jetty.home}/etc/jetty-http.xml
 
@@ -13,6 +14,11 @@
 LIB|${jetty.home}/lib/jetty-util-TEST.jar
 LIB|${jetty.home}/lib/jetty-xml-TEST.jar
 LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.home}/resources
+LIB|${maven-test-resources}/extra-jetty-dirs/logging/lib/logging/logback.jar
 
 # The Properties we expect (order is irrelevant)
 PROP|jetty.port=9090
+
+# Files
+FILE|logs/
diff --git a/jetty-start/src/test/resources/usecases/assert-logging.txt b/jetty-start/src/test/resources/usecases/assert-logging.txt
new file mode 100644
index 0000000..b566172
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/assert-logging.txt
@@ -0,0 +1,26 @@
+# The XMLs we expect (order is important)
+XML|${jetty.home}/etc/jetty-logging.xml
+XML|${jetty.home}/etc/jetty.xml
+XML|${jetty.home}/etc/jetty-http.xml
+
+# The LIBs we expect (order is irrelevant)
+LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
+LIB|${jetty.home}/lib/jetty-http-TEST.jar
+LIB|${jetty.home}/lib/jetty-io-TEST.jar
+LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
+LIB|${jetty.home}/lib/jetty-server-TEST.jar
+LIB|${jetty.home}/lib/jetty-util-TEST.jar
+LIB|${jetty.home}/lib/jetty-xml-TEST.jar
+LIB|${jetty.home}/lib/servlet-api-3.1.jar
+LIB|${jetty.base}/lib/logging/slf4j-api.jar
+LIB|${jetty.base}/lib/logging/jul-to-slf4j.jar
+LIB|${jetty.base}/lib/logging/logback-core.jar
+LIB|${jetty.base}/lib/logging/logback-classic.jar
+LIB|${jetty.base}/resources
+
+# The Properties we expect (order is irrelevant)
+PROP|jetty.port=9090
+
+# Other File References
+FILE|logs/
+FILE|resources/
\ No newline at end of file
diff --git a/jetty-start/src/test/resources/usecases/base.logging/lib/logging/jul-to-slf4j.jar b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/jul-to-slf4j.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/jul-to-slf4j.jar
diff --git a/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-classic.jar b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-classic.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-classic.jar
diff --git a/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-core.jar b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-core.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/logback-core.jar
diff --git a/jetty-start/src/test/resources/usecases/base.logging/lib/logging/slf4j-api.jar b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/slf4j-api.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/lib/logging/slf4j-api.jar
diff --git a/jetty-start/src/test/resources/usecases/base.logging/resources/logback.xml b/jetty-start/src/test/resources/usecases/base.logging/resources/logback.xml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/resources/logback.xml
diff --git a/jetty-start/src/test/resources/usecases/base.logging/start.ini b/jetty-start/src/test/resources/usecases/base.logging/start.ini
new file mode 100644
index 0000000..e18ff3d
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/base.logging/start.ini
@@ -0,0 +1,7 @@
+
+--module=server
+--module=http
+--module=logging
+--module=resources
+
+jetty.port=9090
diff --git a/jetty-start/src/test/resources/usecases/home/modules/logging.mod b/jetty-start/src/test/resources/usecases/home/modules/logging.mod
new file mode 100644
index 0000000..a39bfe4
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/logging.mod
@@ -0,0 +1,31 @@
+#
+# Jetty std err/out logging
+#
+
+[xml]
+etc/jetty-logging.xml
+
+[files]
+logs/
+
+[lib]
+lib/logging/**.jar
+resources/
+
+[ini-template]
+## Logging Configuration
+# Configure jetty logging for default internal behavior STDERR output
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+
+# Configure jetty logging for slf4j
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
+
+# Configure jetty logging for java.util.logging
+# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
+
+# STDERR / STDOUT Logging
+# Number of days to retain logs
+# jetty.log.retain=90
+# Directory for logging output
+# Either a path relative to ${jetty.base} or an absolute path
+# jetty.logs=logs
diff --git a/jetty-start/src/test/resources/usecases/home/modules/resources.mod b/jetty-start/src/test/resources/usecases/home/modules/resources.mod
new file mode 100644
index 0000000..8647d81
--- /dev/null
+++ b/jetty-start/src/test/resources/usecases/home/modules/resources.mod
@@ -0,0 +1,10 @@
+#
+# Module to add resources directory to classpath
+#
+
+[lib]
+resources/
+
+[files]
+resources/
+
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
index 2a902c2..973d941 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java
@@ -60,6 +60,12 @@
      */
     public static class Adapter implements Callback
     {
+        /**
+         * Instance of Adapter that can be used when the callback methods need an empty
+         * implementation without incurring in the cost of allocating a new Adapter object.
+         */
+        public static final Adapter INSTANCE = new Adapter();
+
         @Override
         public void succeeded()
         {
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java b/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java
index 0d86f16..6ff416e 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java
@@ -57,6 +57,7 @@
  *
  * @see java.util.List
  */
+@SuppressWarnings("serial")
 public class LazyList
     implements Cloneable, Serializable
 {
@@ -259,6 +260,38 @@
         
         return (List<E>)Collections.singletonList(list);
     }
+    
+    /**
+     * Simple utility method to test if List has at least 1 entry.
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if not-null and is not empty
+     */
+    public static boolean hasEntry(Object list)
+    {
+        if (list == null)
+            return false;
+        if (list instanceof List)
+            return !((List<?>)list).isEmpty();
+        return true;
+    }
+    
+    /**
+     * Simple utility method to test if List is empty
+     * 
+     * @param list
+     *            a LazyList, {@link List} or {@link Object}
+     * @return true if null or is empty
+     */
+    public static boolean isEmpty(Object list)
+    {
+        if (list == null)
+            return true;
+        if (list instanceof List)
+            return ((List<?>)list).isEmpty();
+        return false;
+    }
 
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
deleted file mode 100644
index 5c8ee37..0000000
--- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
+++ /dev/null
@@ -1,532 +0,0 @@
-//
-//  ========================================================================
-//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
-//  ------------------------------------------------------------------------
-//  All rights reserved. This program and the accompanying materials
-//  are made available under the terms of the Eclipse Public License v1.0
-//  and Apache License v2.0 which accompanies this distribution.
-//
-//      The Eclipse Public License is available at
-//      http://www.eclipse.org/legal/epl-v10.html
-//
-//      The Apache License v2.0 is available at
-//      http://www.opensource.org/licenses/apache2.0.php
-//
-//  You may elect to redistribute this code under either of these licenses.
-//  ========================================================================
-//
-
-package org.eclipse.jetty.webapp;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EventListener;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import javax.servlet.Servlet;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.eclipse.jetty.util.Loader;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.xml.XmlParser;
-
-/* ------------------------------------------------------------ */
-/** TagLibConfiguration.
- *
- * The class searches for TLD descriptors found in web.xml, in WEB-INF/*.tld files of the web app
- * or *.tld files within jars found in WEB-INF/lib of the webapp.   Any listeners defined in these
- * tld's are added to the context.
- *
- * &lt;bile&gt;This is total rubbish special case for JSPs! If there was a general use-case for web app
- * frameworks to register listeners directly, then a generic mechanism could have been added to the servlet
- * spec.  Instead some special purpose JSP support is required that breaks all sorts of encapsulation rules as
- * the servlet container must go searching for and then parsing the descriptors for one particular framework.
- * It only appears to be used by JSF, which is being developed by the same developer who implemented this
- * feature in the first place!
- * &lt;/bile&gt;
- *
- *
- * Note- this has been superceded by the new TldScanner in jasper which uses ServletContainerInitializer to
- * find all the listeners in tag libs and register them.
- */
-@Deprecated
-public class TagLibConfiguration extends AbstractConfiguration
-{
-    private static final Logger LOG = Log.getLogger(TagLibConfiguration.class);
-
-    public static final String TLD_RESOURCES = "org.eclipse.jetty.tlds";
-
-
-    /**
-     * TagLibListener
-     *
-     * A listener that does the job of finding .tld files that contain
-     * (other) listeners that need to be called by the servlet container.
-     *
-     * This implementation is necessitated by the fact that it is only
-     * after all the Configuration classes have run that we will
-     * parse web.xml/fragments etc and thus find tlds mentioned therein.
-     *
-     * Note: TagLibConfiguration is not used in jetty-8 as jasper (JSP engine)
-     * uses the new TldScanner class - a ServletContainerInitializer from
-     * Servlet Spec 3 - to find all listeners in taglibs and register them
-     * with the servlet container.
-     */
-    public  class TagLibListener implements ServletContextListener {
-        private List<EventListener> _tldListeners;
-        private WebAppContext _context;
-
-        public TagLibListener (WebAppContext context) {
-            _context = context;
-        }
-
-        public void contextDestroyed(ServletContextEvent sce)
-        {
-            if (_tldListeners == null)
-                return;
-
-            for (int i=_tldListeners.size()-1; i>=0; i--) {
-                EventListener l = _tldListeners.get(i);
-                if (l instanceof ServletContextListener) {
-                    ((ServletContextListener)l).contextDestroyed(sce);
-                }
-            }
-        }
-
-        public void contextInitialized(ServletContextEvent sce)
-        {
-            try
-            {
-                //For jasper 2.1:
-                //Get the system classpath tlds and tell jasper about them, if jasper is on the classpath
-                try
-                {
-
-                    ClassLoader loader = _context.getClassLoader();
-                    if (loader == null || loader.getParent() == null)
-                        loader = getClass().getClassLoader();
-                    else
-                        loader = loader.getParent();
-                    //Choose a class that should be present if tlds are in use
-                    Class<?> clazz = loader.loadClass("org.apache.jasper.compiler.TagFileProcessor");
-                    assert clazz!=null;
-                    Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
-
-                    Map<URI, List<String>> tldMap = new HashMap<URI, List<String>>();
-
-                    if (tld_resources != null)
-                    {
-                        //get the jar file names of the files
-                        for (Resource r:tld_resources)
-                        {
-                            Resource jarResource = extractJarResource(r);
-                            //jasper is happy with an empty list of tlds
-                            if (!tldMap.containsKey(jarResource.getURI()))
-                                tldMap.put(jarResource.getURI(), null);
-
-                        }
-                        //set the magic context attribute that tells jasper about the system tlds
-                        sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap);
-                    }
-                }
-                catch (ClassNotFoundException e)
-                {
-                    LOG.ignore(e);
-                }
-
-                //find the tld files and parse them to get out their
-                //listeners
-                Set<Resource> tlds = findTldResources();
-                List<TldDescriptor> descriptors = parseTlds(tlds);
-                processTlds(descriptors);
-
-                if (_tldListeners == null)
-                    return;
-
-                //call the listeners that are ServletContextListeners, put the
-                //rest into the context's list of listeners to call at the appropriate
-                //moment
-                for (EventListener l:_tldListeners) {
-                    if (l instanceof ServletContextListener) {
-                        ((ServletContextListener)l).contextInitialized(sce);
-                    } else {
-                        _context.addEventListener(l);
-                    }
-                }
-
-            }
-            catch (Exception e) {
-                LOG.warn(e);
-            }
-        }
-
-
-
-
-        private Resource extractJarResource (Resource r)
-        {
-            if (r == null)
-                return null;
-
-            try
-            {
-                String url = r.getURI().toURL().toString();
-                int idx = url.lastIndexOf("!/");
-                if (idx >= 0)
-                    url = url.substring(0, idx);
-                if (url.startsWith("jar:"))
-                    url = url.substring(4);
-                return Resource.newResource(url);
-            }
-            catch (IOException e)
-            {
-                LOG.warn(e);
-                return null;
-            }
-        }
-
-        /**
-         * Find all the locations that can harbour tld files that may contain
-         * a listener which the web container is supposed to instantiate and
-         * call.
-         *
-         * @return
-         * @throws IOException
-         */
-        private Set<Resource> findTldResources () throws IOException {
-
-            Set<Resource> tlds = new HashSet<Resource>();
-
-            // Find tld's from web.xml
-            // When web.xml was processed, it should have created aliases for all TLDs.  So search resources aliases
-            // for aliases ending in tld
-            if (_context.getResourceAliases()!=null &&
-                    _context.getBaseResource()!=null &&
-                    _context.getBaseResource().exists())
-            {
-                Iterator<String> iter=_context.getResourceAliases().values().iterator();
-                while(iter.hasNext())
-                {
-                    String location = iter.next();
-                    if (location!=null && location.toLowerCase(Locale.ENGLISH).endsWith(".tld"))
-                    {
-                        if (!location.startsWith("/"))
-                            location="/WEB-INF/"+location;
-                        Resource l=_context.getBaseResource().addPath(location);
-                        tlds.add(l);
-                    }
-                }
-            }
-
-            // Look for any tlds in WEB-INF directly.
-            Resource web_inf = _context.getWebInf();
-            if (web_inf!=null)
-            {
-                String[] contents = web_inf.list();
-                for (int i=0;contents!=null && i<contents.length;i++)
-                {
-                    if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
-                    {
-                        Resource l=web_inf.addPath(contents[i]);
-                        tlds.add(l);
-                    }
-                }
-            }
-
-            //Look for tlds in common location of WEB-INF/tlds
-            if (web_inf != null) {
-                Resource web_inf_tlds = _context.getWebInf().addPath("/tlds/");
-                if (web_inf_tlds.exists() && web_inf_tlds.isDirectory()) {
-                    String[] contents = web_inf_tlds.list();
-                    for (int i=0;contents!=null && i<contents.length;i++)
-                    {
-                        if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
-                        {
-                            Resource l=web_inf_tlds.addPath(contents[i]);
-                            tlds.add(l);
-                        }
-                    }
-                }
-            }
-
-            // Add in tlds found in META-INF of jars. The jars that will be scanned are controlled by
-            // the patterns defined in the context attributes: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern,
-            // and org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern
-            @SuppressWarnings("unchecked")
-            Collection<Resource> tld_resources=(Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
-            if (tld_resources!=null)
-                tlds.addAll(tld_resources);
-
-            return tlds;
-        }
-
-
-        /**
-         * Parse xml into in-memory tree
-         * @param tlds
-         * @return
-         */
-        private List<TldDescriptor> parseTlds (Set<Resource> tlds) {
-            List<TldDescriptor> descriptors = new ArrayList<TldDescriptor>();
-
-            Resource tld = null;
-            Iterator<Resource> iter = tlds.iterator();
-            while (iter.hasNext())
-            {
-                try
-                {
-                    tld = iter.next();
-                    if (LOG.isDebugEnabled()) LOG.debug("TLD="+tld);
-
-                    TldDescriptor d = new TldDescriptor(tld);
-                    d.parse();
-                    descriptors.add(d);
-                }
-                catch(Exception e)
-                {
-                    LOG.warn("Unable to parse TLD: " + tld,e);
-                }
-            }
-            return descriptors;
-        }
-
-
-        /**
-         * Create listeners from the parsed tld trees
-         * @param descriptors
-         * @throws Exception
-         */
-        private void processTlds (List<TldDescriptor> descriptors) throws Exception {
-
-            TldProcessor processor = new TldProcessor();
-            for (TldDescriptor d:descriptors)
-                processor.process(_context, d);
-
-            _tldListeners = new ArrayList<EventListener>(processor.getListeners());
-        }
-    }
-
-
-
-
-    /**
-     * TldDescriptor
-     *
-     *
-     */
-    public static class TldDescriptor extends Descriptor
-    {
-        protected static XmlParser __parserSingleton;
-
-        public TldDescriptor(Resource xml)
-        {
-            super(xml);
-        }
-
-        @Override
-        public void ensureParser() throws ClassNotFoundException
-        {
-           if (__parserSingleton == null)
-               __parserSingleton = newParser();
-            _parser = __parserSingleton;
-        }
-
-        @Override
-        public XmlParser newParser() throws ClassNotFoundException
-        {
-            // Create a TLD parser
-            XmlParser parser = new XmlParser(false);
-
-            URL taglib11=null;
-            URL taglib12=null;
-            URL taglib20=null;
-            URL taglib21=null;
-
-            try
-            {
-                Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
-                taglib11=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
-                taglib12=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
-                taglib20=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
-                taglib21=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
-            }
-            catch(Exception e)
-            {
-                LOG.ignore(e);
-            }
-            finally
-            {
-                if(taglib11==null)
-                    taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
-                if(taglib12==null)
-                    taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
-                if(taglib20==null)
-                    taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
-                if(taglib21==null)
-                    taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
-            }
-
-
-            if(taglib11!=null)
-            {
-                redirect(parser, "web-jsptaglib_1_1.dtd",taglib11);
-                redirect(parser, "web-jsptaglibrary_1_1.dtd",taglib11);
-            }
-            if(taglib12!=null)
-            {
-                redirect(parser, "web-jsptaglib_1_2.dtd",taglib12);
-                redirect(parser, "web-jsptaglibrary_1_2.dtd",taglib12);
-            }
-            if(taglib20!=null)
-            {
-                redirect(parser, "web-jsptaglib_2_0.xsd",taglib20);
-                redirect(parser, "web-jsptaglibrary_2_0.xsd",taglib20);
-            }
-            if(taglib21!=null)
-            {
-                redirect(parser, "web-jsptaglib_2_1.xsd",taglib21);
-                redirect(parser, "web-jsptaglibrary_2_1.xsd",taglib21);
-            }
-
-            parser.setXpath("/taglib/listener/listener-class");
-            return parser;
-        }
-
-        public void parse ()
-        throws Exception
-        {
-            ensureParser();
-            try
-            {
-                //xerces on apple appears to sometimes close the zip file instead
-                //of the inputstream, so try opening the input stream, but if
-                //that doesn't work, fallback to opening a new url
-                _root = _parser.parse(_xml.getInputStream());
-            }
-            catch (Exception e)
-            {
-                _root = _parser.parse(_xml.getURL().toString());
-            }
-
-            if (_root==null)
-            {
-                LOG.warn("No TLD root in {}",_xml);
-            }
-        }
-    }
-
-
-    /**
-     * TldProcessor
-     *
-     * Process TldDescriptors representing tag libs to find listeners.
-     */
-    public class TldProcessor extends IterativeDescriptorProcessor
-    {
-        public static final String TAGLIB_PROCESSOR = "org.eclipse.jetty.tagLibProcessor";
-        XmlParser _parser;
-        List<XmlParser.Node> _roots = new ArrayList<XmlParser.Node>();
-        List<EventListener> _listeners;
-
-
-        public TldProcessor ()
-        throws Exception
-        {
-            _listeners = new ArrayList<EventListener>();
-            registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
-        }
-
-
-        public void visitListener (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
-        {
-            String className=node.getString("listener-class",false,true);
-            if (LOG.isDebugEnabled())
-                LOG.debug("listener="+className);
-
-            try
-            {
-                Class<?> listenerClass = context.loadClass(className);
-                EventListener l = (EventListener)listenerClass.newInstance();
-                _listeners.add(l);
-            }
-            catch(Exception e)
-            {
-                LOG.warn("Could not instantiate listener "+className+": "+e);
-                LOG.debug(e);
-            }
-            catch(Error e)
-            {
-                LOG.warn("Could not instantiate listener "+className+": "+e);
-                LOG.debug(e);
-            }
-
-        }
-
-        @Override
-        public void end(WebAppContext context, Descriptor descriptor)
-        {
-        }
-
-        @Override
-        public void start(WebAppContext context, Descriptor descriptor)
-        {
-        }
-
-        public List<EventListener> getListeners() {
-            return _listeners;
-        }
-    }
-
-
-    @Override
-    public void preConfigure(WebAppContext context) throws Exception
-    {
-        try
-        {
-            Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
-        }
-        catch (Exception e)
-        {
-            //no jsp available, don't parse TLDs
-            return;
-        }
-
-        TagLibListener tagLibListener = new TagLibListener(context);
-        context.addEventListener(tagLibListener);
-    }
-
-
-    @Override
-    public void configure (WebAppContext context) throws Exception
-    {
-    }
-
-    @Override
-    public void postConfigure(WebAppContext context) throws Exception
-    {
-    }
-
-
-    @Override
-    public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
-    {
-    }
-
-
-    @Override
-    public void deconfigure(WebAppContext context) throws Exception
-    {
-    }
-}
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java
index 6ba4d22..e7722e5 100644
--- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java
@@ -34,6 +34,7 @@
 import javax.websocket.ClientEndpointConfig;
 import javax.websocket.DeploymentException;
 import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
 import javax.websocket.Extension;
 import javax.websocket.Session;
 import javax.websocket.WebSocketContainer;
@@ -199,7 +200,7 @@
         return client;
     }
 
-    public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint)
+    public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint, EndpointConfig config)
     {
         EndpointMetadata metadata = null;
 
@@ -226,7 +227,7 @@
                 // extends Endpoint
                 @SuppressWarnings("unchecked")
                 Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
-                metadata = new SimpleEndpointMetadata(eendpoint);
+                metadata = new SimpleEndpointMetadata(eendpoint,config);
             }
             else
             {
@@ -313,7 +314,7 @@
 
     public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config)
     {
-        EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
+        EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config);
         ClientEndpointConfig cec = config;
         if (config == null)
         {
diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java
index 4a5c636..84049ca 100644
--- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java
+++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/SimpleEndpointMetadata.java
@@ -19,6 +19,7 @@
 package org.eclipse.jetty.websocket.jsr356.client;
 
 import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
 
 import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
 import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
@@ -35,9 +36,20 @@
 
     public SimpleEndpointMetadata(Class<? extends Endpoint> endpointClass)
     {
+        this(endpointClass, null);
+    }
+    
+    public SimpleEndpointMetadata(Class<? extends Endpoint> endpointClass, EndpointConfig config)
+    {
         this.endpointClass = endpointClass;
         this.decoders = new DecoderMetadataSet();
         this.encoders = new EncoderMetadataSet();
+
+        if (config != null)
+        {
+            this.decoders.addAll(config.getDecoders());
+            this.encoders.addAll(config.getEncoders());
+        }
     }
 
     @Override
diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderTest.java
new file mode 100644
index 0000000..6931999
--- /dev/null
+++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/EncoderTest.java
@@ -0,0 +1,316 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.jsr356;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.ContainerProvider;
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class EncoderTest
+{
+    private static class EchoServer implements Runnable
+    {
+        private Thread thread;
+        private BlockheadServer server;
+        private ServerConnection sconnection;
+        private CountDownLatch connectLatch = new CountDownLatch(1);
+
+        public EchoServer(BlockheadServer server)
+        {
+            this.server = server;
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                sconnection = server.accept();
+                sconnection.setSoTimeout(60000);
+                sconnection.upgrade();
+                sconnection.startEcho();
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                connectLatch.countDown();
+            }
+        }
+
+        public void start()
+        {
+            this.thread = new Thread(this,"EchoServer");
+            this.thread.start();
+        }
+
+        public void stop()
+        {
+            if (this.sconnection != null)
+            {
+                this.sconnection.stopEcho();
+                try
+                {
+                    this.sconnection.close();
+                }
+                catch (IOException ignore)
+                {
+                    /* ignore */
+                }
+            }
+        }
+    }
+
+    public static class Quotes
+    {
+        private String author;
+        private List<String> quotes = new ArrayList<>();
+
+        public void addQuote(String quote)
+        {
+            quotes.add(quote);
+        }
+
+        public String getAuthor()
+        {
+            return author;
+        }
+
+        public List<String> getQuotes()
+        {
+            return quotes;
+        }
+
+        public void setAuthor(String author)
+        {
+            this.author = author;
+        }
+    }
+
+    public static class QuotesEncoder implements Encoder.Text<Quotes>
+    {
+        @Override
+        public void destroy()
+        {
+        }
+
+        @Override
+        public String encode(Quotes q) throws EncodeException
+        {
+            StringBuilder buf = new StringBuilder();
+            buf.append("Author: ").append(q.getAuthor());
+            buf.append(System.lineSeparator());
+            for (String quote : q.quotes)
+            {
+                buf.append("Quote: ").append(quote);
+                buf.append(System.lineSeparator());
+            }
+            return buf.toString();
+        }
+
+        @Override
+        public void init(EndpointConfig config)
+        {
+        }
+    }
+
+    public static class QuotesSocket extends Endpoint implements MessageHandler.Whole<String>
+    {
+        private Session session;
+        private EventQueue<String> messageQueue = new EventQueue<>();
+
+        @Override
+        public void onMessage(String message)
+        {
+            messageQueue.add(message);
+        }
+
+        @Override
+        public void onOpen(Session session, EndpointConfig config)
+        {
+            this.session = session;
+            this.session.addMessageHandler(this);
+        }
+
+        public void write(Quotes quotes) throws IOException, EncodeException
+        {
+            LOG.debug("Writing Quotes: {}",quotes);
+            this.session.getBasicRemote().sendObject(quotes);
+        }
+    }
+
+    private static final Logger LOG = Log.getLogger(EncoderTest.class);
+
+    @Rule
+    public TestTracker tt = new TestTracker();
+    private BlockheadServer server;
+
+    private WebSocketContainer client;
+
+    private void assertReceivedQuotes(String result, Quotes quotes)
+    {
+        Assert.assertThat("Quote Author",result,containsString("Author: " + quotes.getAuthor()));
+        for (String quote : quotes.quotes)
+        {
+            Assert.assertThat("Quote",result,containsString("Quote: " + quote));
+        }
+    }
+
+    private Quotes getQuotes(String filename) throws IOException
+    {
+        Quotes quotes = new Quotes();
+
+        // read file
+        File qfile = MavenTestingUtils.getTestResourceFile(filename);
+        try (FileReader reader = new FileReader(qfile); BufferedReader buf = new BufferedReader(reader))
+        {
+            String line;
+            while ((line = buf.readLine()) != null)
+            {
+                switch (line.charAt(0))
+                {
+                    case 'a':
+                        quotes.setAuthor(line.substring(2));
+                        break;
+                    case 'q':
+                        quotes.addQuote(line.substring(2));
+                        break;
+                }
+            }
+        }
+
+        return quotes;
+    }
+
+    @Before
+    public void initClient()
+    {
+        client = ContainerProvider.getWebSocketContainer();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testSingleQuotes() throws Exception
+    {
+        EchoServer eserver = new EchoServer(server);
+        try
+        {
+            eserver.start();
+
+            QuotesSocket quoter = new QuotesSocket();
+
+            ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
+            List<Class<? extends Encoder>> encoders = new ArrayList<>();
+            encoders.add(QuotesEncoder.class);
+            builder.encoders(encoders);
+            ClientEndpointConfig cec = builder.build();
+            client.connectToServer(quoter,cec,server.getWsUri());
+
+            Quotes ben = getQuotes("quotes-ben.txt");
+            quoter.write(ben);
+
+            quoter.messageQueue.awaitEventCount(1,1000,TimeUnit.MILLISECONDS);
+
+            String result = quoter.messageQueue.poll();
+            assertReceivedQuotes(result,ben);
+        }
+        finally
+        {
+            eserver.stop();
+        }
+    }
+
+    @Test
+    public void testTwoQuotes() throws Exception
+    {
+        EchoServer eserver = new EchoServer(server);
+        try
+        {
+            eserver.start();
+
+            QuotesSocket quoter = new QuotesSocket();
+            ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
+            List<Class<? extends Encoder>> encoders = new ArrayList<>();
+            encoders.add(QuotesEncoder.class);
+            builder.encoders(encoders);
+            ClientEndpointConfig cec = builder.build();
+            client.connectToServer(quoter,cec,server.getWsUri());
+
+            Quotes ben = getQuotes("quotes-ben.txt");
+            Quotes twain = getQuotes("quotes-twain.txt");
+            quoter.write(ben);
+            quoter.write(twain);
+
+            quoter.messageQueue.awaitEventCount(2,1000,TimeUnit.MILLISECONDS);
+
+            String result = quoter.messageQueue.poll();
+            assertReceivedQuotes(result,ben);
+            result = quoter.messageQueue.poll();
+            assertReceivedQuotes(result,twain);
+        }
+        finally
+        {
+            eserver.stop();
+        }
+    }
+}
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java
index 1e065fa..9b6e6f9 100644
--- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java
@@ -57,7 +57,7 @@
     
     public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path)
     {
-        EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
+        EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config);
         ServerEndpointConfig cec = config;
         if (config == null)
         {
diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java
index 9163268..ebfd399 100644
--- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/SimpleServerEndpointMetadata.java
@@ -29,13 +29,8 @@
 
     public SimpleServerEndpointMetadata(Class<? extends Endpoint> endpointClass, ServerEndpointConfig config)
     {
-        super(endpointClass);
+        super(endpointClass,config);
         this.config = config;
-        if (this.config != null)
-        {
-            getDecoders().addAll(config.getDecoders());
-            getEncoders().addAll(config.getEncoders());
-        }
     }
 
     @Override
diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
index 81059ef..1d000c6 100644
--- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
+++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/UpgradeRequest.java
@@ -259,9 +259,12 @@
     public void setCookies(List<HttpCookie> cookies)
     {
         this.cookies.clear();
-        this.cookies.addAll(cookies);
+        if (cookies != null && !cookies.isEmpty())
+        {
+            this.cookies.addAll(cookies);
+        }
     }
-
+    
     public void setExtensions(List<ExtensionConfig> configs)
     {
         this.extensions.clear();
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
index 15d2f37..4993bf5 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/ClientUpgradeRequest.java
@@ -31,6 +31,7 @@
 import java.util.concurrent.ThreadLocalRandom;
 
 import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.LazyList;
 import org.eclipse.jetty.util.MultiMap;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.UrlEncoded;
@@ -210,7 +211,19 @@
             return;
         }
 
-        setCookies(cookieStore.get(getRequestURI()));
+        List<HttpCookie> existing = getCookies();
+        List<HttpCookie> extra = cookieStore.get(getRequestURI());
+
+        List<HttpCookie> cookies = new ArrayList<>();
+        if (LazyList.hasEntry(existing))
+        {
+            cookies.addAll(existing);
+        }
+        if (LazyList.hasEntry(extra))
+        {
+            cookies.addAll(extra);
+        }
+        setCookies(cookies);
     }
 
     @Override
diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
index 7ccb4f5..433f3ce 100644
--- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
+++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java
@@ -36,7 +36,6 @@
 import org.eclipse.jetty.util.HttpCookieStore;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.component.ContainerLifeCycle;
-import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/CookieTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/CookieTest.java
new file mode 100644
index 0000000..b9af198
--- /dev/null
+++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/CookieTest.java
@@ -0,0 +1,173 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.client;
+
+import static org.hamcrest.Matchers.*;
+
+import java.net.CookieManager;
+import java.net.HttpCookie;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.toolchain.test.EventQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.WebSocketAdapter;
+import org.eclipse.jetty.websocket.api.util.QuoteUtil;
+import org.eclipse.jetty.websocket.common.frames.TextFrame;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer;
+import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CookieTest
+{
+    private static final Logger LOG = Log.getLogger(CookieTest.class);
+
+    public static class CookieTrackingSocket extends WebSocketAdapter
+    {
+        public EventQueue<String> messageQueue = new EventQueue<>();
+        public EventQueue<Throwable> errorQueue = new EventQueue<>();
+
+        @Override
+        public void onWebSocketText(String message)
+        {
+            messageQueue.add(message);
+        }
+
+        @Override
+        public void onWebSocketError(Throwable cause)
+        {
+            errorQueue.add(cause);
+        }
+    }
+
+    private WebSocketClient client;
+    private BlockheadServer server;
+
+    @Before
+    public void startClient() throws Exception
+    {
+        client = new WebSocketClient();
+        client.start();
+    }
+
+    @Before
+    public void startServer() throws Exception
+    {
+        server = new BlockheadServer();
+        server.start();
+    }
+
+    @After
+    public void stopClient() throws Exception
+    {
+        if (client.isRunning())
+        {
+            client.stop();
+        }
+    }
+
+    @After
+    public void stopServer() throws Exception
+    {
+        server.stop();
+    }
+
+    @Test
+    public void testViaCookieManager() throws Exception
+    {
+        // Setup client
+        CookieManager cookieMgr = new CookieManager();
+        client.setCookieStore(cookieMgr.getCookieStore());
+        HttpCookie cookie = new HttpCookie("hello","world");
+        cookie.setPath("/");
+        cookie.setMaxAge(100000);
+        cookieMgr.getCookieStore().add(server.getWsUri(),cookie);
+
+        // Client connects
+        CookieTrackingSocket clientSocket = new CookieTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri());
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+
+        // client confirms upgrade and receipt of frame
+        String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
+
+        Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=\"world\""));
+    }
+    
+    @Test
+    public void testViaServletUpgradeRequest() throws Exception
+    {
+        // Setup client
+        HttpCookie cookie = new HttpCookie("hello","world");
+        cookie.setPath("/");
+        cookie.setMaxAge(100000);
+        
+        ClientUpgradeRequest request = new ClientUpgradeRequest();
+        request.setCookies(Collections.singletonList(cookie));
+
+        // Client connects
+        CookieTrackingSocket clientSocket = new CookieTrackingSocket();
+        Future<Session> clientConnectFuture = client.connect(clientSocket,server.getWsUri(),request);
+
+        // Server accepts connect
+        ServerConnection serverConn = server.accept();
+
+        // client confirms upgrade and receipt of frame
+        String serverCookies = confirmClientUpgradeAndCookies(clientSocket,clientConnectFuture,serverConn);
+
+        Assert.assertThat("Cookies seen at server side",serverCookies,containsString("hello=\"world\""));
+    }
+
+    private String confirmClientUpgradeAndCookies(CookieTrackingSocket clientSocket, Future<Session> clientConnectFuture, ServerConnection serverConn)
+            throws Exception
+    {
+        // Server upgrades
+        List<String> upgradeRequestLines = serverConn.upgrade();
+        List<String> upgradeRequestCookies = serverConn.regexFind(upgradeRequestLines,"^Cookie: (.*)$");
+
+        // Server responds with cookies it knows about
+        TextFrame serverCookieFrame = new TextFrame();
+        serverCookieFrame.setFin(true);
+        serverCookieFrame.setPayload(QuoteUtil.join(upgradeRequestCookies,","));
+        serverConn.write(serverCookieFrame);
+
+        // Server closes connection
+        serverConn.close(StatusCode.NORMAL);
+
+        // Confirm client connect on future
+        clientConnectFuture.get(500,TimeUnit.MILLISECONDS);
+
+        // Wait for client receipt of cookie frame via client websocket
+        clientSocket.messageQueue.awaitEventCount(1,2,TimeUnit.SECONDS);
+
+        String cookies = clientSocket.messageQueue.poll();
+        LOG.debug("Cookies seen at server: {}",cookies);
+        return cookies;
+    }
+}
diff --git a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
index 9668b13..826f50f 100644
--- a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
+++ b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties
@@ -7,7 +7,7 @@
 # org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG
 # org.eclipse.jetty.io.AbstractConnection.LEVEL=DEBUG
 # org.eclipse.jetty.websocket.LEVEL=WARN
-# org.eclipse.jetty.websocket.LEVEL=DEBUG
+org.eclipse.jetty.websocket.LEVEL=DEBUG
 # org.eclipse.jetty.websocket.client.LEVEL=DEBUG
 # org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG
 # org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
index 076b78a..c654b2e 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Generator.java
@@ -63,8 +63,9 @@
     private final WebSocketBehavior behavior;
     private final ByteBufferPool bufferPool;
     private final boolean validating;
+    private final boolean readOnly;
 
-    /** 
+    /**
      * Are any flags in use
      * <p>
      * 
@@ -74,7 +75,7 @@
      *   0001_0000 (0x10) = rsv3
      * </pre>
      */
-    private byte flagsInUse=0x00;
+    private byte flagsInUse = 0x00;
 
     /**
      * Construct Generator with provided policy and bufferPool
@@ -86,7 +87,7 @@
      */
     public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool)
     {
-        this(policy,bufferPool,true);
+        this(policy,bufferPool,true,false);
     }
 
     /**
@@ -101,9 +102,27 @@
      */
     public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating)
     {
+        this(policy,bufferPool,validating,false);
+    }
+
+    /**
+     * Construct Generator with provided policy and bufferPool
+     * 
+     * @param policy
+     *            the policy to use
+     * @param bufferPool
+     *            the buffer pool to use
+     * @param validating
+     *            true to enable RFC frame validation
+     * @param readOnly
+     *            true if generator is to treat frames as read-only and not modify them. Useful for debugging purposes, but not generally for runtime use.
+     */
+    public Generator(WebSocketPolicy policy, ByteBufferPool bufferPool, boolean validating, boolean readOnly)
+    {
         this.behavior = policy.getBehavior();
         this.bufferPool = bufferPool;
         this.validating = validating;
+        this.readOnly = readOnly;
     }
 
     public void assertFrameValid(Frame frame)
@@ -200,22 +219,22 @@
 
     public void generateHeaderBytes(Frame frame, ByteBuffer buffer)
     {
-        int p=BufferUtil.flipToFill(buffer);
-        
+        int p = BufferUtil.flipToFill(buffer);
+
         // we need a framing header
         assertFrameValid(frame);
-        
+
         /*
          * start the generation process
          */
         byte b = 0x00;
-        
+
         // Setup fin thru opcode
         if (frame.isFin())
         {
             b |= 0x80; // 1000_0000
         }
-        
+
         // Set the flags
         if (frame.isRsv1())
         {
@@ -229,7 +248,7 @@
         {
             b |= 0x10; // 0001_0000
         }
-        
+
         // NOTE: using .getOpCode() here, not .getType().getOpCode() for testing reasons
         byte opcode = frame.getOpCode();
 
@@ -269,7 +288,7 @@
         /*
          * if payload is greater that 126 we have a 7 + 16 bit length
          */
-        else if (payloadLength >= 0x7E )
+        else if (payloadLength >= 0x7E)
         {
             b |= 0x7E;
             buffer.put(b); // indicate 2 byte length
@@ -286,7 +305,7 @@
         }
 
         // masking key
-        if (frame.isMasked())
+        if (frame.isMasked() && !readOnly)
         {
             byte[] mask = frame.getMask();
             buffer.put(mask);
@@ -306,12 +325,12 @@
                 {
                     if (remaining >= 4)
                     {
-                        payload.putInt(start, payload.getInt(start) ^ maskInt);
+                        payload.putInt(start,payload.getInt(start) ^ maskInt);
                         start += 4;
                     }
                     else
                     {
-                        payload.put(start, (byte)(payload.get(start) ^ mask[maskOffset & 3]));
+                        payload.put(start,(byte)(payload.get(start) ^ mask[maskOffset & 3]));
                         ++start;
                         ++maskOffset;
                     }
@@ -335,7 +354,14 @@
         buf.put(generateHeaderBytes(frame));
         if (frame.hasPayload())
         {
-            buf.put(frame.getPayload());
+            if (readOnly)
+            {
+                buf.put(frame.getPayload().slice());
+            }
+            else
+            {
+                buf.put(frame.getPayload());
+            }
         }
     }
 
@@ -344,19 +370,30 @@
         return bufferPool;
     }
 
-
     public void setRsv1InUse(boolean rsv1InUse)
     {
+        if (readOnly)
+        {
+            throw new RuntimeException("Not allowed to modify read-only frame");
+        }
         flagsInUse = (byte)((flagsInUse & 0xBF) | (rsv1InUse?0x40:0x00));
     }
 
     public void setRsv2InUse(boolean rsv2InUse)
     {
+        if (readOnly)
+        {
+            throw new RuntimeException("Not allowed to modify read-only frame");
+        }
         flagsInUse = (byte)((flagsInUse & 0xDF) | (rsv2InUse?0x20:0x00));
     }
 
     public void setRsv3InUse(boolean rsv3InUse)
     {
+        if (readOnly)
+        {
+            throw new RuntimeException("Not allowed to modify read-only frame");
+        }
         flagsInUse = (byte)((flagsInUse & 0xEF) | (rsv3InUse?0x10:0x00));
     }
 
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
index a249c0e..8507787 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java
@@ -176,6 +176,11 @@
 
         for (Extension ext : extensions)
         {
+            if (ext.getName().charAt(0) == '@')
+            {
+                // special, internal-only extensions, not present on negotiation level
+                continue;
+            }
             ret.add(ext.getConfig());
         }
         return ret;
@@ -276,8 +281,8 @@
     @Override
     public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
     {
-        FrameEntry entry = new FrameEntry(frame, callback, batchMode);
-        LOG.debug("Queuing {}", entry);
+        FrameEntry entry = new FrameEntry(frame,callback,batchMode);
+        LOG.debug("Queuing {}",entry);
         entries.offer(entry);
         flusher.iterate();
     }
@@ -403,7 +408,7 @@
             // this flusher into a final state that cannot be exited,
             // and the failure of a frame may not mean that the whole
             // connection is now invalid.
-            notifyCallbackFailure(current.callback, x);
+            notifyCallbackFailure(current.callback,x);
             succeeded();
         }
 
@@ -416,7 +421,7 @@
             }
             catch (Throwable x)
             {
-                LOG.debug("Exception while notifying success of callback " + callback, x);
+                LOG.debug("Exception while notifying success of callback " + callback,x);
             }
         }
 
@@ -429,7 +434,7 @@
             }
             catch (Throwable x)
             {
-                LOG.debug("Exception while notifying failure of callback " + callback, x);
+                LOG.debug("Exception while notifying failure of callback " + callback,x);
             }
         }
     }
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/FrameDebugExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/FrameDebugExtension.java
new file mode 100644
index 0000000..4d240d4
--- /dev/null
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/FrameDebugExtension.java
@@ -0,0 +1,142 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket.common.extensions;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.BatchMode;
+import org.eclipse.jetty.websocket.api.WriteCallback;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.api.extensions.Frame;
+import org.eclipse.jetty.websocket.common.Generator;
+
+public class FrameDebugExtension extends AbstractExtension
+{
+    private static final Logger LOG = Log.getLogger(FrameDebugExtension.class);
+
+    private static final int BUFSIZE = 32768;
+    private Generator generator;
+    private Path outputDir;
+    private String prefix = "frame";
+    private AtomicLong incomingId = new AtomicLong(0);
+    private AtomicLong outgoingId = new AtomicLong(0);
+
+    @Override
+    public String getName()
+    {
+        return "@frame-debug";
+    }
+
+    @Override
+    public void incomingFrame(Frame frame)
+    {
+        saveFrame(frame,false);
+        nextIncomingFrame(frame);
+    }
+
+    @Override
+    public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
+    {
+        saveFrame(frame,true);
+        nextOutgoingFrame(frame,callback,batchMode);
+    }
+
+    private void saveFrame(Frame frame, boolean outgoing)
+    {
+        if (outputDir == null || generator == null)
+        {
+            return;
+        }
+
+        StringBuilder filename = new StringBuilder();
+        filename.append(prefix);
+        if (outgoing)
+        {
+            filename.append(String.format("-outgoing-%05d",outgoingId.getAndIncrement()));
+        }
+        else
+        {
+            filename.append(String.format("-incoming-%05d",incomingId.getAndIncrement()));
+        }
+        filename.append(".dat");
+
+        Path outputFile = outputDir.resolve(filename.toString());
+        ByteBuffer buf = getBufferPool().acquire(BUFSIZE,false);
+        try (SeekableByteChannel channel = Files.newByteChannel(outputFile,StandardOpenOption.CREATE,StandardOpenOption.WRITE))
+        {
+            generator.generateHeaderBytes(frame,buf);
+            channel.write(buf);
+            if (frame.hasPayload())
+            {
+                channel.write(frame.getPayload().slice());
+            }
+            LOG.debug("Saved raw frame: {}",outputFile.toString());
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Unable to save frame: " + filename.toString(),e);
+        }
+        finally
+        {
+            getBufferPool().release(buf);
+        }
+    }
+
+    @Override
+    public void setConfig(ExtensionConfig config)
+    {
+        super.setConfig(config);
+
+        String cfgOutputDir = config.getParameter("output-dir",null);
+        if (StringUtil.isNotBlank(cfgOutputDir))
+        {
+            Path path = new File(cfgOutputDir).toPath();
+            if (Files.isDirectory(path) && Files.exists(path) && Files.isWritable(path))
+            {
+                this.outputDir = path;
+            }
+            else
+            {
+                LOG.warn("Unable to configure {}: not a valid output directory",path.toAbsolutePath().toString());
+            }
+        }
+
+        String cfgPrefix = config.getParameter("prefix","frame");
+        if (StringUtil.isNotBlank(cfgPrefix))
+        {
+            this.prefix = cfgPrefix;
+        }
+
+        if (this.outputDir != null)
+        {
+            // create a non-validating, read-only generator
+            this.generator = new Generator(getPolicy(),getBufferPool(),false,true);
+        }
+    }
+}
diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java
index cffdc06..a5f0524 100644
--- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java
+++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java
@@ -246,6 +246,7 @@
 
     private class Flusher extends IteratingCallback implements WriteCallback
     {
+        private static final int INPUT_BUFSIZE = 32 * 1024;
         private FrameEntry current;
         private ByteBuffer payload;
         private boolean finished = true;
@@ -288,7 +289,7 @@
             Frame frame = entry.frame;
             ByteBuffer data = frame.getPayload();
             int remaining = data.remaining();
-            int inputLength = Math.min(remaining, 32 * 1024);
+            int inputLength = Math.min(remaining, INPUT_BUFSIZE);
             LOG.debug("Compressing {}: {} bytes in {} bytes chunk", entry, remaining, inputLength);
 
             // Avoid to copy the bytes if the ByteBuffer
diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java
index 8db7f7d..f4c4c39 100644
--- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java
+++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java
@@ -18,6 +18,8 @@
 
 package org.eclipse.jetty.websocket.common.test;
 
+import static org.hamcrest.Matchers.*;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
@@ -52,9 +54,9 @@
 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.api.extensions.Frame.Type;
 import org.eclipse.jetty.websocket.common.AcceptHash;
 import org.eclipse.jetty.websocket.common.CloseInfo;
 import org.eclipse.jetty.websocket.common.Generator;
@@ -66,9 +68,6 @@
 import org.eclipse.jetty.websocket.common.frames.CloseFrame;
 import org.junit.Assert;
 
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-
 /**
  * A overly simplistic websocket server used during testing.
  * <p>
@@ -234,7 +233,7 @@
             {
                 try
                 {
-                    write(WebSocketFrame.copy(frame));
+                    write(WebSocketFrame.copy(frame).setMasked(false));
                 }
                 catch (IOException e)
                 {
@@ -280,20 +279,14 @@
         public List<ExtensionConfig> parseExtensions(List<String> requestLines)
         {
             List<ExtensionConfig> extensionConfigs = new ArrayList<>();
+            
+            List<String> hits = regexFind(requestLines, "^Sec-WebSocket-Extensions: (.*)$");
 
-            Pattern patExts = Pattern.compile("^Sec-WebSocket-Extensions: (.*)$",Pattern.CASE_INSENSITIVE);
-
-            Matcher mat;
-            for (String line : requestLines)
+            for (String econf : hits)
             {
-                mat = patExts.matcher(line);
-                if (mat.matches())
-                {
-                    // found extensions
-                    String econf = mat.group(1);
-                    ExtensionConfig config = ExtensionConfig.parse(econf);
-                    extensionConfigs.add(config);
-                }
+                // found extensions
+                ExtensionConfig config = ExtensionConfig.parse(econf);
+                extensionConfigs.add(config);
             }
 
             return extensionConfigs;
@@ -301,20 +294,15 @@
 
         public String parseWebSocketKey(List<String> requestLines)
         {
-            String key = null;
-
-            Pattern patKey = Pattern.compile("^Sec-WebSocket-Key: (.*)$",Pattern.CASE_INSENSITIVE);
-
-            Matcher mat;
-            for (String line : requestLines)
+            List<String> hits = regexFind(requestLines,"^Sec-WebSocket-Key: (.*)$");
+            if (hits.size() <= 0)
             {
-                mat = patKey.matcher(line);
-                if (mat.matches())
-                {
-                    key = mat.group(1);
-                }
+                return null;
             }
-
+            
+            Assert.assertThat("Number of Sec-WebSocket-Key headers", hits.size(), is(1));
+            
+            String key = hits.get(0);
             return key;
         }
 
@@ -415,6 +403,32 @@
             return lines;
         }
 
+        public List<String> regexFind(List<String> lines, String pattern)
+        {
+            List<String> hits = new ArrayList<>();
+
+            Pattern patKey = Pattern.compile(pattern,Pattern.CASE_INSENSITIVE);
+
+            Matcher mat;
+            for (String line : lines)
+            {
+                mat = patKey.matcher(line);
+                if (mat.matches())
+                {
+                    if (mat.groupCount() >= 1)
+                    {
+                        hits.add(mat.group(1));
+                    }
+                    else
+                    {
+                        hits.add(mat.group(0));
+                    }
+                }
+            }
+
+            return hits;
+        }
+
         public void respond(String rawstr) throws IOException
         {
             LOG.debug("respond(){}{}","\n",rawstr);
@@ -486,7 +500,7 @@
             echoing.set(false);
         }
 
-        public void upgrade() throws IOException
+        public List<String> upgrade() throws IOException
         {
             List<String> requestLines = readRequestLines();
             List<ExtensionConfig> extensionConfigs = parseExtensions(requestLines);
@@ -559,6 +573,7 @@
             // Write Response
             LOG.debug("Response: {}",resp.toString());
             write(resp.toString().getBytes());
+            return requestLines;
         }
 
         private void write(byte[] bytes) throws IOException
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 8ab0361..3fb66b2 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
@@ -18,11 +18,16 @@
 
 package org.eclipse.jetty.websocket.server.browser;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.handler.ResourceHandler;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
+import org.eclipse.jetty.websocket.common.extensions.FrameDebugExtension;
 import org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension;
 import org.eclipse.jetty.websocket.server.WebSocketHandler;
 import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
@@ -85,8 +90,27 @@
         String ua = req.getHeader("User-Agent");
         String rexts = req.getHeader("Sec-WebSocket-Extensions");
 
+        // manually negotiate extensions
+        List<ExtensionConfig> negotiated = new ArrayList<>();
+        // adding frame debug
+        negotiated.add(new ExtensionConfig("@frame-debug; output-dir=target"));
+        for (ExtensionConfig config : req.getExtensions())
+        {
+            if (config.getName().equals("permessage-deflate"))
+            {
+                // what we are interested in here
+                negotiated.add(config);
+                continue;
+            }
+            // skip all others
+        }
+
+        resp.setExtensions(negotiated);
+
         LOG.debug("User-Agent: {}",ua);
         LOG.debug("Sec-WebSocket-Extensions (Request) : {}",rexts);
+
+        req.getExtensions();
         return new BrowserSocket(ua,rexts);
     }
 
@@ -114,6 +138,9 @@
                 factory.getExtensionFactory().register("permessage-deflate",PerMessageDeflateExtension.class);
                 // factory.getExtensionFactory().unregister("x-webkit-deflate-frame");
 
+                // Registering Frame Debug
+                factory.getExtensionFactory().register("@frame-debug",FrameDebugExtension.class);
+
                 // Setup the desired Socket to use for all incoming upgrade requests
                 factory.setCreator(BrowserDebugTool.this);
 
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
index 0f10fe8..ee48808 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
@@ -18,12 +18,17 @@
 
 package org.eclipse.jetty.websocket.server.browser;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Locale;
 import java.util.Random;
 
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
 import org.eclipse.jetty.util.StringUtil;
 import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.log.Logger;
@@ -119,6 +124,38 @@
             LOG.info("onTextMessage({})",message);
         }
 
+        // Is multi-line?
+        if (message.contains("\n"))
+        {
+            // echo back exactly
+            writeMessage(message);
+            return;
+        }
+
+        // Is resource lookup?
+        if (message.charAt(0) == '@')
+        {
+            String name = message.substring(1);
+            URL url = Loader.getResource(BrowserSocket.class,name);
+            if (url == null)
+            {
+                writeMessage("Unable to find resource: " + name);
+                return;
+            }
+            try (InputStream in = url.openStream())
+            {
+                String data = IO.toString(in);
+                writeMessage(data);
+            }
+            catch (IOException e)
+            {
+                writeMessage("Unable to read resource: " + name);
+                LOG.warn("Unable to read resource: " + name,e);
+            }
+            return;
+        }
+
+        // Is parameterized?
         int idx = message.indexOf(':');
         if (idx > 0)
         {
@@ -194,12 +231,11 @@
                     writeMessage("key[%s] val[%s]",key,val);
                 }
             }
+            return;
         }
-        else
-        {
-            // Not parameterized, echo it back
-            writeMessage(message);
-        }
+
+        // Not parameterized, echo it back as-is
+        writeMessage(message);
     }
 
     private void writeManyAsync(int size, int count)
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
index aa31f57..4ffb6e5 100644
--- a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/index.html
@@ -17,6 +17,7 @@
       <input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
       <input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
       <input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
+      <input id="twain" class="button" type="submit" name="twain" value="twain" disabled="disabled"/>
       <input id="send10k" class="button" type="submit" name="send10k" value="send10k" disabled="disabled"/>
       <input id="send100k" class="button" type="submit" name="send100k" value="send100k" disabled="disabled"/>
       <input id="send1000k" class="button" type="submit" name="send1000k" value="send1000k" disabled="disabled"/>
@@ -31,6 +32,7 @@
     $("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
     $("hello").onclick = function(event) {wstool.write("Hello"); return false; }
     $("there").onclick = function(event) {wstool.write("There"); return false; }
+    $("twain").onclick = function(event) {wstool.write("@twain.txt"); return false; }
     $("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
             + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
             + " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
diff --git a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
index 35dd0dd..03f2896 100644
--- a/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
+++ b/jetty-websocket/websocket-server/src/test/resources/browser-debug-tool/websocket.js
@@ -85,6 +85,7 @@
         $('hello').disabled = !enabled;
         $('there').disabled = !enabled;
         $('json').disabled = !enabled;
+        $('twain').disabled = !enabled;
         $('send10k').disabled = !enabled;
         $('send100k').disabled = !enabled;
         $('send1000k').disabled = !enabled;