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:
commit
664ddef6fd
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[depend]
|
||||
alpn-impl/alpn-9
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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","|");
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -57,6 +57,11 @@
|
|||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Jetty WebSocket Browser -> 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 -> 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>
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue