diff --git a/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
new file mode 100644
index 00000000000..cd712ed06ed
--- /dev/null
+++ b/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java
@@ -0,0 +1,202 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.browser;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.http.pathmap.ServletPathSpec;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.PathResource;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.websocket.core.ExtensionConfig;
+import org.eclipse.jetty.websocket.core.internal.FrameCaptureExtension;
+import org.eclipse.jetty.websocket.server.JettyWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * Tool to help debug websocket circumstances reported around browsers.
+ *
+ * Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
+ * websocket implementation from the context of a browser client.
+ */
+public class BrowserDebugTool
+{
+ private static final Logger LOG = Log.getLogger(BrowserDebugTool.class);
+
+ public static void main(String[] args)
+ {
+ int port = 8080;
+
+ for (int i = 0; i < args.length; i++)
+ {
+ String a = args[i];
+ if ("-p".equals(a) || "--port".equals(a))
+ {
+ port = Integer.parseInt(args[++i]);
+ }
+ }
+
+ try
+ {
+ BrowserDebugTool tool = new BrowserDebugTool();
+ tool.prepare(port);
+ tool.start();
+ }
+ catch (Throwable t)
+ {
+ LOG.warn(t);
+ }
+ }
+
+ private Server server;
+ private ServerConnector connector;
+
+ public int getPort()
+ {
+ return connector.getLocalPort();
+ }
+
+ public void prepare(int port) throws IOException, URISyntaxException {
+ server = new Server();
+ connector = new ServerConnector(server);
+ connector.setPort(port);
+ server.addConnector(connector);
+
+ ServletContextHandler context = new ServletContextHandler();
+
+ JettyWebSocketServletContainerInitializer.configure(context);
+
+ context.setContextPath("/");
+ Resource staticResourceBase = findStaticResources();
+ context.setBaseResource(staticResourceBase);
+ context.addServlet(BrowserSocketServlet.class, "/*");
+ ServletHolder defHolder = new ServletHolder("default", DefaultServlet.class);
+ context.addServlet(defHolder, "/");
+
+ HandlerList handlers = new HandlerList();
+ handlers.addHandler(context);
+ handlers.addHandler(new DefaultHandler());
+
+ server.setHandler(handlers);
+
+ LOG.info("{} setup on port {}",this.getClass().getName(),port);
+ }
+
+ private Resource findStaticResources()
+ {
+ Path path = MavenTestingUtils.getTestResourcePathDir("browser-debug-tool");
+ LOG.info("Static Resources: {}", path);
+ return new PathResource(path);
+ }
+
+ public void start() throws Exception
+ {
+ server.start();
+ LOG.info("Server available on port {}",getPort());
+ }
+
+ public void stop() throws Exception
+ {
+ server.stop();
+ }
+
+ public static class BrowserSocketServlet extends WebSocketServlet
+ {
+ @Override
+ public void configure(WebSocketServletFactory factory) {
+ LOG.debug("Configuring WebSocketServerFactory ...");
+
+ // Registering Frame Debug
+ factory.getExtensionRegistry().register("@frame-capture", FrameCaptureExtension.class);
+
+ // Setup the desired Socket to use for all incoming upgrade requests
+ factory.addMapping(new ServletPathSpec("/"), new BrowserSocketCreator());
+
+ // Set the timeout
+ factory.setDefaultIdleTimeout(Duration.ofSeconds(30));
+
+ // Set top end message size
+ factory.setDefaultMaxTextMessageSize(15 * 1024 * 1024);
+ }
+ }
+
+ public static class BrowserSocketCreator implements WebSocketCreator
+ {
+ @Override
+ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
+ {
+ LOG.debug("Creating BrowserSocket");
+
+ if (req.getSubProtocols() != null)
+ {
+ if (!req.getSubProtocols().isEmpty())
+ {
+ String subProtocol = req.getSubProtocols().get(0);
+ resp.setAcceptedSubProtocol(subProtocol);
+ }
+ }
+
+ String ua = req.getHeader("User-Agent");
+ String rexts = req.getHeader("Sec-WebSocket-Extensions");
+
+ // manually negotiate extensions
+ List negotiated = new ArrayList<>();
+ // adding frame debug
+ negotiated.add(new ExtensionConfig("@frame-capture; 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);
+ LOG.debug("Sec-WebSocket-Protocol (Request): {}",req.getHeader("Sec-WebSocket-Protocol"));
+ LOG.debug("Sec-WebSocket-Protocol (Response): {}",resp.getAcceptedSubProtocol());
+
+ req.getExtensions();
+ return new BrowserSocket(ua,rexts);
+ }
+ }
+}
diff --git a/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java b/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
new file mode 100644
index 00000000000..b7fa0006909
--- /dev/null
+++ b/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserSocket.java
@@ -0,0 +1,291 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.websocket.server.browser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Random;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+@WebSocket
+public class BrowserSocket
+{
+ private static class WriteMany implements Runnable
+ {
+ private RemoteEndpoint remote;
+ private int size;
+ private int count;
+
+ public WriteMany(RemoteEndpoint remote, int size, int count)
+ {
+ this.remote = remote;
+ this.size = size;
+ this.count = count;
+ }
+
+ @Override
+ public void run()
+ {
+ char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+ int lettersLen = letters.length;
+ char randomText[] = new char[size];
+ Random rand = new Random(42);
+ String msg;
+
+ for (int n = 0; n < count; n++)
+ {
+ // create random text
+ for (int i = 0; i < size; i++)
+ {
+ randomText[i] = letters[rand.nextInt(lettersLen)];
+ }
+ msg = String.format("ManyThreads [%s]",String.valueOf(randomText));
+ remote.sendString(msg,null);
+ }
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(BrowserSocket.class);
+
+ private Session session;
+ private final String userAgent;
+ private final String requestedExtensions;
+
+ public BrowserSocket(String ua, String reqExts)
+ {
+ this.userAgent = ua;
+ this.requestedExtensions = reqExts;
+ }
+
+ @OnWebSocketConnect
+ public void onConnect(Session session)
+ {
+ LOG.info("Connect [{}]",session);
+ this.session = session;
+ }
+
+ @OnWebSocketClose
+ public void onDisconnect(int statusCode, String reason)
+ {
+ this.session = null;
+ LOG.info("Closed [{}, {}]",statusCode,reason);
+ }
+
+ @OnWebSocketError
+ public void onError(Throwable cause)
+ {
+ this.session = null;
+ LOG.warn("Error",cause);
+ }
+
+ @OnWebSocketMessage
+ public void onTextMessage(String message)
+ {
+ if (message.length() > 300)
+ {
+ int len = message.length();
+ LOG.info("onTextMessage({} ... {}) size:{}",message.substring(0,15),message.substring(len - 15,len).replaceAll("[\r\n]*",""),len);
+ }
+ else
+ {
+ LOG.info("onTextMessage({})",message);
+ }
+
+ // Is multi-line?
+ if (message.contains("\n"))
+ {
+ // echo back exactly
+ writeMessage(message);
+ return;
+ }
+
+ // Is resource lookup?
+ if (message.charAt(0) == '@')
+ {
+ String name = message.substring(1);
+ URL url = Loader.getResource(name);
+ if (url == null)
+ {
+ writeMessage("Unable to find resource: " + name);
+ return;
+ }
+ try (InputStream in = url.openStream())
+ {
+ String data = IO.toString(in);
+ writeMessage(data);
+ }
+ catch (IOException e)
+ {
+ writeMessage("Unable to read resource: " + name);
+ LOG.warn("Unable to read resource: " + name,e);
+ }
+ return;
+ }
+
+ // Is parameterized?
+ int idx = message.indexOf(':');
+ if (idx > 0)
+ {
+ String key = message.substring(0,idx).toLowerCase(Locale.ENGLISH);
+ String val = message.substring(idx + 1);
+ switch (key)
+ {
+ case "info":
+ {
+ if (StringUtil.isBlank(userAgent))
+ {
+ writeMessage("Client has no User-Agent");
+ }
+ else
+ {
+ writeMessage("Client User-Agent: " + this.userAgent);
+ }
+
+ if (StringUtil.isBlank(requestedExtensions))
+ {
+ writeMessage("Client requested no Sec-WebSocket-Extensions");
+ }
+ else
+ {
+ writeMessage("Client requested Sec-WebSocket-Extensions: " + this.requestedExtensions);
+ writeMessage("Negotiated Sec-WebSocket-Extensions: " + session.getUpgradeResponse().getHeader("Sec-WebSocket-Extensions"));
+ }
+
+ break;
+ }
+ case "many":
+ {
+ String parts[] = val.split(",");
+ int size = Integer.parseInt(parts[0]);
+ int count = Integer.parseInt(parts[1]);
+
+ writeManyAsync(size,count);
+ break;
+ }
+ case "manythreads":
+ {
+ String parts[] = val.split(",");
+ int threadCount = Integer.parseInt(parts[0]);
+ int size = Integer.parseInt(parts[1]);
+ int count = Integer.parseInt(parts[2]);
+
+ Thread threads[] = new Thread[threadCount];
+
+ // Setup threads
+ for (int n = 0; n < threadCount; n++)
+ {
+ threads[n] = new Thread(new WriteMany(session.getRemote(),size,count),"WriteMany[" + n + "]");
+ }
+
+ // Execute threads
+ for (Thread thread : threads)
+ {
+ thread.start();
+ }
+
+ // Drop out of this thread
+ break;
+ }
+ case "time":
+ {
+ Calendar now = Calendar.getInstance();
+ DateFormat sdf = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL,SimpleDateFormat.FULL);
+ writeMessage("Server time: %s",sdf.format(now.getTime()));
+ break;
+ }
+ case "dump":
+ {
+ if(session instanceof Dumpable) {
+ System.err.println(((Dumpable) session).dump());
+ } else {
+ System.err.println(session.toString());
+ }
+ break;
+ }
+ default:
+ {
+ writeMessage("key[%s] val[%s]",key,val);
+ }
+ }
+ return;
+ }
+
+ // Not parameterized, echo it back as-is
+ writeMessage(message);
+ }
+
+ private void writeManyAsync(int size, int count)
+ {
+ char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-|{}[]():".toCharArray();
+ int lettersLen = letters.length;
+ char randomText[] = new char[size];
+ Random rand = new Random(42);
+
+ for (int n = 0; n < count; n++)
+ {
+ // create random text
+ for (int i = 0; i < size; i++)
+ {
+ randomText[i] = letters[rand.nextInt(lettersLen)];
+ }
+ writeMessage("Many [%s]",String.valueOf(randomText));
+ }
+ }
+
+ private void writeMessage(String message)
+ {
+ if (this.session == null)
+ {
+ LOG.debug("Not connected");
+ return;
+ }
+
+ if (!session.isOpen())
+ {
+ LOG.debug("Not open");
+ return;
+ }
+
+ // Async write
+ session.getRemote().sendString(message,null);
+ }
+
+ private void writeMessage(String format, Object... args)
+ {
+ writeMessage(String.format(format,args));
+ }
+}
diff --git a/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/index.html b/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/index.html
new file mode 100644
index 00000000000..b7b638d3ba1
--- /dev/null
+++ b/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/index.html
@@ -0,0 +1,58 @@
+
+
+ Jetty WebSocket Browser -> Server Debug Tool
+
+
+
+
+ jetty websocket/browser/javascript -> server debug tool #console
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/main.css b/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/main.css
new file mode 100644
index 00000000000..9eebead468d
--- /dev/null
+++ b/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/main.css
@@ -0,0 +1,29 @@
+body {
+ font-family: sans-serif;
+}
+
+div {
+ border: 0px solid black;
+}
+
+div#console {
+ clear: both;
+ width: 40em;
+ height: 20em;
+ overflow: auto;
+ background-color: #f0f0f0;
+ padding: 4px;
+ border: 1px solid black;
+}
+
+div#console .info {
+ color: black;
+}
+
+div#console .client {
+ color: blue;
+}
+
+div#console .server {
+ color: magenta;
+}
diff --git a/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/websocket.js b/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/websocket.js
new file mode 100644
index 00000000000..66a1ec0ee52
--- /dev/null
+++ b/jetty-websocket/jetty-websocket-server/src/test/resources/browser-debug-tool/websocket.js
@@ -0,0 +1,141 @@
+if (!window.WebSocket && window.MozWebSocket) {
+ window.WebSocket = window.MozWebSocket;
+}
+
+if (!window.WebSocket) {
+ alert("WebSocket not supported by this browser");
+}
+
+function $() {
+ return document.getElementById(arguments[0]);
+}
+function $F() {
+ return document.getElementById(arguments[0]).value;
+}
+
+function getKeyCode(ev) {
+ if (window.event)
+ return window.event.keyCode;
+ return ev.keyCode;
+}
+
+var wstool = {
+ connect : function() {
+ var location = document.location.toString().replace('http://', 'ws://')
+ .replace('https://', 'wss://');
+
+ wstool.info("Document URI: " + document.location);
+ wstool.info("WS URI: " + location);
+
+ this._scount = 0;
+
+ try {
+ this._ws = new WebSocket(location, "tool");
+ this._ws.onopen = this._onopen;
+ this._ws.onmessage = this._onmessage;
+ this._ws.onclose = this._onclose;
+ } catch (exception) {
+ wstool.info("Connect Error: " + exception);
+ }
+ },
+
+ close : function() {
+ this._ws.close();
+ },
+
+ _out : function(css, message) {
+ var console = $('console');
+ var spanText = document.createElement('span');
+ spanText.className = 'text ' + css;
+ spanText.innerHTML = message;
+ var lineBreak = document.createElement('br');
+ console.appendChild(spanText);
+ console.appendChild(lineBreak);
+ console.scrollTop = console.scrollHeight - console.clientHeight;
+ },
+
+ info : function(message) {
+ wstool._out("info", message);
+ },
+
+ infoc : function(message) {
+ if(message.length > 300) {
+ wstool._out("client", "[c] [big message: " + message.length + " characters]");
+ } else {
+ wstool._out("client", "[c] " + message);
+ }
+ },
+
+ infos : function(message) {
+ this._scount++;
+ if(message.length > 300) {
+ wstool._out("server", "[s" + this._scount + "] [big message: " + message.length + " characters]");
+ } else {
+ wstool._out("server", "[s" + this._scount + "] " + message);
+ }
+ },
+
+ setState : function(enabled) {
+ $('connect').disabled = enabled;
+ $('close').disabled = !enabled;
+ $('info').disabled = !enabled;
+ $('time').disabled = !enabled;
+ $('many').disabled = !enabled;
+ $('manythreads').disabled = !enabled;
+ $('hello').disabled = !enabled;
+ $('there').disabled = !enabled;
+ $('dump').disabled = !enabled;
+ $('json').disabled = !enabled;
+ $('send10k').disabled = !enabled;
+ $('send100k').disabled = !enabled;
+ $('send1000k').disabled = !enabled;
+ $('send10m').disabled = !enabled;
+ },
+
+ _onopen : function() {
+ wstool.setState(true);
+ wstool.info("Websocket Connected");
+ },
+
+ _send : function(message) {
+ if (this._ws) {
+ this._ws.send(message);
+ wstool.infoc(message);
+ }
+ },
+
+ write : function(text) {
+ wstool._send(text);
+ },
+
+ _onmessage : function(m) {
+ if (m.data) {
+ wstool.infos(m.data);
+ }
+ },
+
+ _onclose : function(closeEvent) {
+ this._ws = null;
+ wstool.setState(false);
+ wstool.info("Websocket Closed");
+ wstool.info(" .wasClean = " + closeEvent.wasClean);
+
+ var codeMap = {};
+ codeMap[1000] = "(NORMAL)";
+ codeMap[1001] = "(ENDPOINT_GOING_AWAY)";
+ codeMap[1002] = "(PROTOCOL_ERROR)";
+ codeMap[1003] = "(UNSUPPORTED_DATA)";
+ codeMap[1004] = "(UNUSED/RESERVED)";
+ codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)";
+ codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)";
+ codeMap[1007] = "(BAD_DATA)";
+ codeMap[1008] = "(POLICY_VIOLATION)";
+ codeMap[1009] = "(MESSAGE_TOO_BIG)";
+ codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)";
+ codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)";
+ codeMap[1015] = "(INTERNAL/TLS_ERROR)";
+ var codeStr = codeMap[closeEvent.code];
+ wstool.info(" .code = " + closeEvent.code + " " + codeStr);
+ wstool.info(" .reason = " + closeEvent.reason);
+ }
+};
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java
index b034d47abdc..35a71b3209f 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/AbstractExtension.java
@@ -18,16 +18,19 @@
package org.eclipse.jetty.websocket.core;
+import java.io.IOException;
+
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.internal.WebSocketChannel;
@ManagedObject("Abstract Extension")
-public abstract class AbstractExtension implements Extension
+public abstract class AbstractExtension implements Extension, Dumpable
{
private final Logger log;
private ByteBufferPool bufferPool;
@@ -41,6 +44,27 @@ public abstract class AbstractExtension implements Extension
log = Log.getLogger(this.getClass());
}
+ @Override
+ public String dump()
+ {
+ return Dumpable.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ // incoming
+ dumpWithHeading(out, indent, "incoming", this.nextIncoming);
+ dumpWithHeading(out, indent, "outgoing", this.nextOutgoing);
+ }
+
+ protected void dumpWithHeading(Appendable out, String indent, String heading, Object bean) throws IOException
+ {
+ out.append(indent).append(" +- ");
+ out.append(heading).append(" : ");
+ out.append(bean.toString());
+ }
+
@Override
public void init(ExtensionConfig config, ByteBufferPool bufferPool)
{
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java
index c99c938fd42..d4bef33ecbe 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/ExtensionStack.java
@@ -260,6 +260,12 @@ public class ExtensionStack implements IncomingFrames, OutgoingFrames, Dumpable
Dumpable.dumpObjects(out, indent, this, extensions == null?Collections.emptyList():extensions);
}
+ @Override
+ public String dumpSelf()
+ {
+ return String.format("%s@%x[size=%d,queueSize=%d]", getClass().getSimpleName(), hashCode(), extensions.size(), getQueueSize());
+ }
+
@Override
public String toString()
{
diff --git a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java
index 6de02f814d8..bccb63333b4 100644
--- a/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java
+++ b/jetty-websocket/websocket-core/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameCaptureExtension.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.websocket.core.internal;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
@@ -111,12 +112,14 @@ public class FrameCaptureExtension extends AbstractExtension
}
ByteBuffer buf = getBufferPool().acquire(BUFSIZE, false);
+ BufferUtil.flipToFill(buf);
try
{
Frame f = Frame.copy(frame);
f.setMask(null); // TODO is this needed?
generator.generateHeaderBytes(f, buf);
+ BufferUtil.flipToFlush(buf, 0);
channel.write(buf);
if (frame.hasPayload())
{
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
index 394688689f3..35d10f5916b 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java
@@ -33,6 +33,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.FrameHandler;
+import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
/**
* Abstract Servlet used to bridge the Servlet API to the WebSocket API.
@@ -173,6 +174,13 @@ public abstract class WebSocketServlet extends HttpServlet
private class CustomizedWebSocketServletFactory extends FrameHandler.ConfigurationCustomizer implements WebSocketServletFactory
{
+
+ @Override
+ public WebSocketExtensionRegistry getExtensionRegistry()
+ {
+ return mapping.getExtensionRegistry();
+ }
+
@Override
public Duration getDefaultIdleTimeout()
{
diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
index 8b839beae6b..aa1b966a049 100644
--- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
+++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java
@@ -19,11 +19,14 @@
package org.eclipse.jetty.websocket.servlet;
import org.eclipse.jetty.http.pathmap.PathSpec;
+import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import java.time.Duration;
public interface WebSocketServletFactory
{
+
+ WebSocketExtensionRegistry getExtensionRegistry();
Duration getDefaultIdleTimeout();
void setDefaultIdleTimeout(Duration duration);