diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java index 38bb2f5a1b7..5864f928389 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/WebSocketJsrServer.java @@ -24,6 +24,8 @@ import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainerInitializer; @@ -49,10 +51,12 @@ public class WebSocketJsrServer { Server server = new Server(8080); + HandlerList handlers = new HandlerList(); + ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS); context.setContextPath("/"); - server.setHandler(context); + handlers.addHandler(context); // Enable javax.websocket configuration for the context ServerContainer wsContainer = JavaxWebSocketServerContainerInitializer @@ -61,6 +65,9 @@ public class WebSocketJsrServer // Add your websockets to the container wsContainer.addEndpoint(EchoJsrSocket.class); + handlers.addHandler(new DefaultHandler()); + + server.setHandler(handlers); server.start(); context.dumpStdErr(); server.join(); diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-13.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-13.mod new file mode 100644 index 00000000000..689601a4197 --- /dev/null +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-13.mod @@ -0,0 +1,4 @@ +DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html + +[depend] +alpn-impl/alpn-9 diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java index ad44afff3e7..5322c215657 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java @@ -549,7 +549,7 @@ public abstract class AbstractProxyServlet extends HttpServlet } builder.append(System.lineSeparator()); - _log.debug("{} proxying to upstream:{}{}{}{}", + _log.debug("{} proxying to upstream:{}{}{}{}{}", getRequestId(clientRequest), System.lineSeparator(), builder, diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java index 5436006635f..49d1b3542b0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java @@ -620,6 +620,16 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory return _remote; } + @Override + public String toString() { + return String.format("%s@%x[remote=%s,local=%s,endpoint=%s]", + getClass().getSimpleName(), + hashCode(), + _remote, + _local, + _endp); + } + @Override public boolean isOpen() { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java index e71555ef7cd..5ecf8e6cbda 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java @@ -69,6 +69,15 @@ public interface Dumpable return b.toString(); } + /** + * The description of this/self found in the dump. + * Allows for alternative representation of Object other then .toString() + * where the long form output of toString() is represented in a cleaner way + * within the dump infrastructure. + * + * @return the representation of self + */ + default String dumpSelf() { return toString(); } /** * Dump just an Object (but not it's contained items) to an Appendable. @@ -89,6 +98,8 @@ public interface Dumpable s = String.format("%s@%x[size=%d]",o.getClass().getComponentType(),o.hashCode(), Array.getLength(o)); else if (o instanceof Map) s = String.format("%s@%x{size=%d}",o.getClass().getName(),o.hashCode(),((Map)o).size()); + else if (o instanceof Dumpable) + s = ((Dumpable)o).dumpSelf().replace("\r\n","|").replace("\n","|"); else s = String.valueOf(o).replace("\r\n","|").replace("\n","|"); diff --git a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionImpl.java b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionImpl.java index 0b00c4da3b1..9f0486e1f8c 100644 --- a/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionImpl.java +++ b/jetty-websocket/jetty-websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionImpl.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.common; +import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.websocket.api.CloseStatus; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.SuspendToken; @@ -26,11 +27,12 @@ import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.core.FrameHandler; +import java.io.IOException; import java.net.SocketAddress; import java.time.Duration; import java.util.Objects; -public class WebSocketSessionImpl implements Session +public class WebSocketSessionImpl implements Session, Dumpable { private final FrameHandler.CoreSession coreSession; private final JettyWebSocketFrameHandler frameHandler; @@ -204,6 +206,21 @@ public class WebSocketSessionImpl implements Session return null; } + @Override + public void dump(Appendable out, String indent) throws IOException + { + Dumpable.dumpObjects(out, indent, this, upgradeRequest, coreSession, remoteEndpoint, frameHandler); + } + + @Override + public String dumpSelf() + { + return String.format("%s@%x[behavior=%s,idleTimeout=%d]", + this.getClass().getSimpleName(), hashCode(), + getPolicy().getBehavior(), + getIdleTimeout()); + } + @Override public String toString() { diff --git a/jetty-websocket/jetty-websocket-server/pom.xml b/jetty-websocket/jetty-websocket-server/pom.xml index a63620025b1..25e78c9a424 100644 --- a/jetty-websocket/jetty-websocket-server/pom.xml +++ b/jetty-websocket/jetty-websocket-server/pom.xml @@ -57,6 +57,11 @@ javax.servlet javax.servlet-api + + org.eclipse.jetty.toolchain + jetty-test-helper + test + 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);