Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-3167-websocket-mapping

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2019-01-15 09:44:53 +11:00
commit 664ddef6fd
17 changed files with 823 additions and 4 deletions

View File

@ -24,6 +24,8 @@ import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.server.Server; 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.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainerInitializer; import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainerInitializer;
@ -49,10 +51,12 @@ public class WebSocketJsrServer
{ {
Server server = new Server(8080); Server server = new Server(8080);
HandlerList handlers = new HandlerList();
ServletContextHandler context = new ServletContextHandler( ServletContextHandler context = new ServletContextHandler(
ServletContextHandler.SESSIONS); ServletContextHandler.SESSIONS);
context.setContextPath("/"); context.setContextPath("/");
server.setHandler(context); handlers.addHandler(context);
// Enable javax.websocket configuration for the context // Enable javax.websocket configuration for the context
ServerContainer wsContainer = JavaxWebSocketServerContainerInitializer ServerContainer wsContainer = JavaxWebSocketServerContainerInitializer
@ -61,6 +65,9 @@ public class WebSocketJsrServer
// Add your websockets to the container // Add your websockets to the container
wsContainer.addEndpoint(EchoJsrSocket.class); wsContainer.addEndpoint(EchoJsrSocket.class);
handlers.addHandler(new DefaultHandler());
server.setHandler(handlers);
server.start(); server.start();
context.dumpStdErr(); context.dumpStdErr();
server.join(); server.join();

View File

@ -0,0 +1,4 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[depend]
alpn-impl/alpn-9

View File

@ -549,7 +549,7 @@ public abstract class AbstractProxyServlet extends HttpServlet
} }
builder.append(System.lineSeparator()); builder.append(System.lineSeparator());
_log.debug("{} proxying to upstream:{}{}{}{}", _log.debug("{} proxying to upstream:{}{}{}{}{}",
getRequestId(clientRequest), getRequestId(clientRequest),
System.lineSeparator(), System.lineSeparator(),
builder, builder,

View File

@ -620,6 +620,16 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
return _remote; return _remote;
} }
@Override
public String toString() {
return String.format("%s@%x[remote=%s,local=%s,endpoint=%s]",
getClass().getSimpleName(),
hashCode(),
_remote,
_local,
_endp);
}
@Override @Override
public boolean isOpen() public boolean isOpen()
{ {

View File

@ -69,6 +69,15 @@ public interface Dumpable
return b.toString(); 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. * 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)); s = String.format("%s@%x[size=%d]",o.getClass().getComponentType(),o.hashCode(), Array.getLength(o));
else if (o instanceof Map) else if (o instanceof Map)
s = String.format("%s@%x{size=%d}",o.getClass().getName(),o.hashCode(),((Map<?,?>)o).size()); 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 else
s = String.valueOf(o).replace("\r\n","|").replace("\n","|"); s = String.valueOf(o).replace("\r\n","|").replace("\n","|");

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.common; 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.CloseStatus;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.SuspendToken; 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.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import java.io.IOException;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.time.Duration; import java.time.Duration;
import java.util.Objects; import java.util.Objects;
public class WebSocketSessionImpl implements Session public class WebSocketSessionImpl implements Session, Dumpable
{ {
private final FrameHandler.CoreSession coreSession; private final FrameHandler.CoreSession coreSession;
private final JettyWebSocketFrameHandler frameHandler; private final JettyWebSocketFrameHandler frameHandler;
@ -204,6 +206,21 @@ public class WebSocketSessionImpl implements Session
return null; 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 @Override
public String toString() public String toString()
{ {

View File

@ -57,6 +57,11 @@
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -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.
* <p>
* Provides a server, with a few simple websocket's that can be twiddled from a browser. This helps with setting up breakpoints and whatnot to help debug our
* websocket implementation from the context of a browser client.
*/
public class BrowserDebugTool
{
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<ExtensionConfig> 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);
}
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,58 @@
<html>
<head>
<title>Jetty WebSocket Browser -&gt; Server Debug Tool</title>
<script type="text/javascript" src="websocket.js"></script>
<link rel="stylesheet" type="text/css" href="main.css" media="all" >
</head>
<body>
jetty websocket/browser/javascript -&gt; server debug tool #console
<div id="console"></div>
<div id="buttons">
<input id="connect" class="button" type="submit" name="connect" value="connect"/>
<input id="close" class="button" type="submit" name="close" value="close" disabled="disabled"/>
<input id="info" class="button" type="submit" name="info" value="info" disabled="disabled"/>
<input id="time" class="button" type="submit" name="time" value="time" disabled="disabled"/>
<input id="many" class="button" type="submit" name="many" value="many" disabled="disabled"/>
<input id="manythreads" class="button" type="submit" name="many" value="manythreads" disabled="disabled"/>
<input id="hello" class="button" type="submit" name="hello" value="hello" disabled="disabled"/>
<input id="there" class="button" type="submit" name="there" value="there" disabled="disabled"/>
<input id="dump" class="button" type="submit" name="dump" value="dump" disabled="disabled"/>
<input id="json" class="button" type="submit" name="json" value="json" disabled="disabled"/>
<input id="send10k" class="button" type="submit" name="send10k" value="send10k" disabled="disabled"/>
<input id="send100k" class="button" type="submit" name="send100k" value="send100k" disabled="disabled"/>
<input id="send1000k" class="button" type="submit" name="send1000k" value="send1000k" disabled="disabled"/>
<input id="send10m" class="button" type="submit" name="send10m" value="send10m" disabled="disabled"/>
</div>
<script type="text/javascript">
$("connect").onclick = function(event) { wstool.connect(); return false; }
$("close").onclick = function(event) {wstool.close(); return false; }
$("info").onclick = function(event) {wstool.write("info:"); return false; }
$("time").onclick = function(event) {wstool.write("time:"); return false; }
$("many").onclick = function(event) {wstool.write("many:15,300"); return false; }
$("manythreads").onclick = function(event) {wstool.write("manythreads:20,25,60"); return false; }
$("hello").onclick = function(event) {wstool.write("Hello"); return false; }
$("there").onclick = function(event) {wstool.write("There"); return false; }
$("dump").onclick = function(event) {wstool.write("dump:"); return false; }
$("json").onclick = function(event) {wstool.write("[{\"channel\":\"/meta/subscribe\",\"subscription\":\"/chat/demo\",\"id\":\"2\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/meta/subscribe\",\"subscription\":\"/members/demo\",\"id\":\"3\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ " 12 Sep 2013 19:42:30 GMT\"},{\"channel\":\"/chat/demo\",\"data\":{\"user\":\"ch\",\"membership\":\"join\",\"chat\":\"ch"
+ " has joined\"},\"id\":\"4\",\"clientId\":\"81dwnxwbgs0h0bq8968b0a0gyl\",\"timestamp\":\"Thu,"
+ " 12 Sep 2013 19:42:30 GMT\"}]"); return false; }
$("send10k").onclick = function(event) {wstool.write(randomString( 10*1024)); return false;}
$("send100k").onclick = function(event) {wstool.write(randomString( 100*1024)); return false;}
$("send1000k").onclick = function(event) {wstool.write(randomString(1000*1024)); return false;}
$("send10m").onclick = function(event) {wstool.write(randomString( 10*1024*1024)); return false;}
function randomString(len, charSet) {
charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{} ":;<>,.()[]';
var randomString = '';
var charLen = charSet.length;
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charLen);
randomString += charSet.substring(randomPoz,randomPoz+1);
}
return randomString;
}
</script>
</body>
</html>

View File

@ -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;
}

View File

@ -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);
}
};

View File

@ -18,16 +18,19 @@
package org.eclipse.jetty.websocket.core; package org.eclipse.jetty.websocket.core;
import java.io.IOException;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; 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.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.internal.WebSocketChannel; import org.eclipse.jetty.websocket.core.internal.WebSocketChannel;
@ManagedObject("Abstract Extension") @ManagedObject("Abstract Extension")
public abstract class AbstractExtension implements Extension public abstract class AbstractExtension implements Extension, Dumpable
{ {
private final Logger log; private final Logger log;
private ByteBufferPool bufferPool; private ByteBufferPool bufferPool;
@ -41,6 +44,27 @@ public abstract class AbstractExtension implements Extension
log = Log.getLogger(this.getClass()); 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 @Override
public void init(ExtensionConfig config, ByteBufferPool bufferPool) public void init(ExtensionConfig config, ByteBufferPool bufferPool)
{ {

View File

@ -260,6 +260,12 @@ public class ExtensionStack implements IncomingFrames, OutgoingFrames, Dumpable
Dumpable.dumpObjects(out, indent, this, extensions == null?Collections.emptyList():extensions); 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 @Override
public String toString() public String toString()
{ {

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.websocket.core.internal; package org.eclipse.jetty.websocket.core.internal;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
@ -111,12 +112,14 @@ public class FrameCaptureExtension extends AbstractExtension
} }
ByteBuffer buf = getBufferPool().acquire(BUFSIZE, false); ByteBuffer buf = getBufferPool().acquire(BUFSIZE, false);
BufferUtil.flipToFill(buf);
try try
{ {
Frame f = Frame.copy(frame); Frame f = Frame.copy(frame);
f.setMask(null); // TODO is this needed? f.setMask(null); // TODO is this needed?
generator.generateHeaderBytes(f, buf); generator.generateHeaderBytes(f, buf);
BufferUtil.flipToFlush(buf, 0);
channel.write(buf); channel.write(buf);
if (frame.hasPayload()) if (frame.hasPayload())
{ {

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.FrameHandler; 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. * 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 private class CustomizedWebSocketServletFactory extends FrameHandler.ConfigurationCustomizer implements WebSocketServletFactory
{ {
@Override
public WebSocketExtensionRegistry getExtensionRegistry()
{
return mapping.getExtensionRegistry();
}
@Override @Override
public Duration getDefaultIdleTimeout() public Duration getDefaultIdleTimeout()
{ {

View File

@ -19,11 +19,14 @@
package org.eclipse.jetty.websocket.servlet; package org.eclipse.jetty.websocket.servlet;
import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import java.time.Duration; import java.time.Duration;
public interface WebSocketServletFactory public interface WebSocketServletFactory
{ {
WebSocketExtensionRegistry getExtensionRegistry();
Duration getDefaultIdleTimeout(); Duration getDefaultIdleTimeout();
void setDefaultIdleTimeout(Duration duration); void setDefaultIdleTimeout(Duration duration);