Merge pull request #3222 from eclipse/jetty-10.0.x-3167-websocket-mapping
Jetty 10.0.x #3167 refactor websocket mapping
This commit is contained in:
commit
4f65799a7b
|
@ -24,6 +24,8 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
|||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
|
||||
|
|
|
@ -96,7 +96,19 @@ public class ServletContextHandler extends ContextHandler
|
|||
public final static int GZIP=4;
|
||||
public final static int NO_SESSIONS=0;
|
||||
public final static int NO_SECURITY=0;
|
||||
|
||||
|
||||
public static ServletContextHandler getServletContextHandler(ServletContext servletContext, String purpose)
|
||||
{
|
||||
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
|
||||
if (contextHandler == null)
|
||||
throw new IllegalStateException("No Jetty ContextHandler, " + purpose+ " unavailable");
|
||||
|
||||
if (!(contextHandler instanceof ServletContextHandler))
|
||||
throw new IllegalStateException("No Jetty ServletContextHandler, " + purpose + " unavailable");
|
||||
|
||||
return (ServletContextHandler)contextHandler;
|
||||
}
|
||||
|
||||
public interface ServletContainerInitializerCaller extends LifeCycle {};
|
||||
|
||||
protected final DecoratedObjectFactory _objFactory;
|
||||
|
|
|
@ -116,14 +116,13 @@ public interface LifeCycle
|
|||
*/
|
||||
public interface Listener extends EventListener
|
||||
{
|
||||
public void lifeCycleStarting(LifeCycle event);
|
||||
public void lifeCycleStarted(LifeCycle event);
|
||||
public void lifeCycleFailure(LifeCycle event,Throwable cause);
|
||||
public void lifeCycleStopping(LifeCycle event);
|
||||
public void lifeCycleStopped(LifeCycle event);
|
||||
default void lifeCycleStarting(LifeCycle event) {}
|
||||
default void lifeCycleStarted(LifeCycle event) {}
|
||||
default void lifeCycleFailure(LifeCycle event,Throwable cause) {}
|
||||
default void lifeCycleStopping(LifeCycle event) {}
|
||||
default void lifeCycleStopped(LifeCycle event) {}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility to start an object if it is a LifeCycle and to convert
|
||||
* any exception thrown to a {@link RuntimeException}
|
||||
|
|
|
@ -18,6 +18,22 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.javax.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.websocket.ClientEndpoint;
|
||||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||
|
@ -30,20 +46,6 @@ import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
|
|||
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
|
||||
|
||||
import javax.websocket.ClientEndpoint;
|
||||
import javax.websocket.ClientEndpointConfig;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.Session;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Container for Client use of the javax.websocket API.
|
||||
* <p>
|
||||
|
@ -52,33 +54,33 @@ import java.util.concurrent.Future;
|
|||
@ManagedObject("JSR356 Client Container")
|
||||
public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer implements javax.websocket.WebSocketContainer
|
||||
{
|
||||
protected final WebSocketCoreClient coreClient;
|
||||
protected WebSocketCoreClient coreClient;
|
||||
protected Supplier<WebSocketCoreClient> coreClientFactory;
|
||||
private final JavaxWebSocketClientFrameHandlerFactory frameHandlerFactory;
|
||||
private ClassLoader contextClassLoader;
|
||||
private DecoratedObjectFactory objectFactory;
|
||||
private WebSocketExtensionRegistry extensionRegistry;
|
||||
|
||||
public JavaxWebSocketClientContainer()
|
||||
{
|
||||
this(new WebSocketCoreClient());
|
||||
this.coreClient.getHttpClient().setName("Javax-WebSocketClient@" + Integer.toHexString(this.coreClient.getHttpClient().hashCode()));
|
||||
// We created WebSocketCoreClient, let lifecycle be managed by us
|
||||
addManaged(coreClient);
|
||||
this(() -> {
|
||||
WebSocketCoreClient coreClient = new WebSocketCoreClient();
|
||||
coreClient.getHttpClient().setName("Javax-WebSocketClient@" + Integer.toHexString(coreClient.getHttpClient().hashCode()));
|
||||
return coreClient;
|
||||
});
|
||||
}
|
||||
|
||||
public JavaxWebSocketClientContainer(HttpClient httpClient)
|
||||
public JavaxWebSocketClientContainer(Supplier<WebSocketCoreClient> coreClientFactory)
|
||||
{
|
||||
this(new WebSocketCoreClient(httpClient));
|
||||
// We created WebSocketCoreClient, let lifecycle be managed by us
|
||||
addManaged(coreClient);
|
||||
this((WebSocketCoreClient)null);
|
||||
this.coreClientFactory = coreClientFactory;
|
||||
this.addBean(coreClientFactory);
|
||||
}
|
||||
|
||||
public JavaxWebSocketClientContainer(WebSocketCoreClient coreClient)
|
||||
{
|
||||
super();
|
||||
this.coreClient = coreClient;
|
||||
this.addBean(this.coreClient);
|
||||
this.contextClassLoader = this.getClass().getClassLoader();
|
||||
this.addBean(coreClient);
|
||||
this.objectFactory = new DecoratedObjectFactory();
|
||||
this.extensionRegistry = new WebSocketExtensionRegistry();
|
||||
this.frameHandlerFactory = new JavaxWebSocketClientFrameHandlerFactory(this);
|
||||
|
@ -98,11 +100,20 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
|
|||
|
||||
protected HttpClient getHttpClient()
|
||||
{
|
||||
return coreClient.getHttpClient();
|
||||
return getWebSocketCoreClient().getHttpClient();
|
||||
}
|
||||
|
||||
protected WebSocketCoreClient getWebSocketCoreClient() throws Exception
|
||||
protected WebSocketCoreClient getWebSocketCoreClient()
|
||||
{
|
||||
if (coreClient == null)
|
||||
{
|
||||
coreClient = coreClientFactory.get();
|
||||
if (coreClient.isRunning())
|
||||
addBean(coreClient,false);
|
||||
else
|
||||
addManaged(coreClient);
|
||||
}
|
||||
|
||||
return coreClient;
|
||||
}
|
||||
|
||||
|
@ -132,7 +143,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
|
|||
Objects.requireNonNull(configuredEndpoint, "WebSocket configured endpoint cannot be null");
|
||||
Objects.requireNonNull(destURI, "Destination URI cannot be null");
|
||||
|
||||
ClientUpgradeRequestImpl upgradeRequest = new ClientUpgradeRequestImpl(this, coreClient, destURI, configuredEndpoint);
|
||||
ClientUpgradeRequestImpl upgradeRequest = new ClientUpgradeRequestImpl(this, getWebSocketCoreClient(), destURI, configuredEndpoint);
|
||||
|
||||
EndpointConfig config = configuredEndpoint.getConfig();
|
||||
if (config != null && config instanceof ClientEndpointConfig)
|
||||
|
|
|
@ -18,6 +18,25 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.javax.common;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.PongMessage;
|
||||
import javax.websocket.Session;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -39,24 +58,6 @@ import org.eclipse.jetty.websocket.javax.common.messages.PartialByteBufferMessag
|
|||
import org.eclipse.jetty.websocket.javax.common.messages.PartialStringMessageSink;
|
||||
import org.eclipse.jetty.websocket.javax.common.util.InvokerUtils;
|
||||
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.MessageHandler;
|
||||
import javax.websocket.PongMessage;
|
||||
import javax.websocket.Session;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class JavaxWebSocketFrameHandler implements FrameHandler
|
||||
{
|
||||
private final Logger LOG;
|
||||
|
@ -217,7 +218,7 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
|
|||
|
||||
if (errorHandle == null)
|
||||
{
|
||||
LOG.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " missing onError handler", cause);
|
||||
LOG.warn("Unhandled Error: " + endpointInstance, cause);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,12 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
|
@ -36,19 +39,23 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
|||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
|
||||
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
|
||||
import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainer;
|
||||
import org.eclipse.jetty.websocket.javax.common.InvalidWebSocketException;
|
||||
import org.eclipse.jetty.websocket.javax.server.internal.AnnotatedServerEndpointConfig;
|
||||
import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketCreator;
|
||||
import org.eclipse.jetty.websocket.javax.common.InvalidWebSocketException;
|
||||
import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainer;
|
||||
import org.eclipse.jetty.websocket.javax.server.internal.UndefinedServerEndpointConfig;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
|
||||
@ManagedObject("JSR356 Server Container")
|
||||
public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer implements javax.websocket.server.ServerContainer
|
||||
public class JavaxWebSocketServerContainer
|
||||
extends JavaxWebSocketClientContainer
|
||||
implements javax.websocket.server.ServerContainer, LifeCycle.Listener
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainer.class);
|
||||
|
||||
|
@ -74,31 +81,84 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
return (javax.websocket.WebSocketContainer)handler.getServletContext().getAttribute("javax.websocket.server.ServerContainer");
|
||||
}
|
||||
|
||||
private final WebSocketCreatorMapping _webSocketCreatorMapping;
|
||||
public static JavaxWebSocketServerContainer ensureContainer(ServletContext servletContext) throws ServletException
|
||||
{
|
||||
ContextHandler contextHandler = ServletContextHandler.getServletContextHandler(servletContext, "Javax Websocket");
|
||||
|
||||
JavaxWebSocketServerContainer container = contextHandler.getBean(JavaxWebSocketServerContainer.class);
|
||||
if (container==null)
|
||||
{
|
||||
// Find Pre-Existing (Shared?) HttpClient and/or executor
|
||||
HttpClient httpClient = (HttpClient)servletContext.getAttribute(JavaxWebSocketServerContainerInitializer.HTTPCLIENT_ATTRIBUTE);
|
||||
if (httpClient == null)
|
||||
httpClient = (HttpClient)contextHandler.getServer()
|
||||
.getAttribute(JavaxWebSocketServerContainerInitializer.HTTPCLIENT_ATTRIBUTE);
|
||||
|
||||
Executor executor = httpClient == null?null:httpClient.getExecutor();
|
||||
if (executor == null)
|
||||
executor = (Executor)servletContext
|
||||
.getAttribute("org.eclipse.jetty.server.Executor");
|
||||
if (executor == null)
|
||||
executor = contextHandler.getServer().getThreadPool();
|
||||
|
||||
if (httpClient != null && httpClient.getExecutor() == null)
|
||||
httpClient.setExecutor(executor);
|
||||
|
||||
// Create the Jetty ServerContainer implementation
|
||||
container = new JavaxWebSocketServerContainer(
|
||||
WebSocketMapping.ensureMapping(servletContext), httpClient, executor);
|
||||
contextHandler.addManaged(container);
|
||||
contextHandler.addLifeCycleListener(container);
|
||||
}
|
||||
// Store a reference to the ServerContainer per - javax.websocket spec 1.0 final - section 6.4: Programmatic Server Deployment
|
||||
servletContext.setAttribute(ServerContainer.class.getName(), container);
|
||||
return container;
|
||||
}
|
||||
|
||||
private final WebSocketMapping webSocketMapping;
|
||||
private final JavaxWebSocketServerFrameHandlerFactory frameHandlerFactory;
|
||||
private final Executor executor;
|
||||
private final FrameHandler.ConfigurationCustomizer customizer = new FrameHandler.ConfigurationCustomizer();
|
||||
private long asyncSendTimeout = -1;
|
||||
private List<Class<?>> deferredEndpointClasses;
|
||||
private List<ServerEndpointConfig> deferredEndpointConfigs;
|
||||
|
||||
/**
|
||||
* Main entry point for {@link JavaxWebSocketServerContainerInitializer}.
|
||||
*
|
||||
* @param webSocketCreatorMapping the {@link WebSocketCreatorMapping} that this container belongs to
|
||||
* @param webSocketMapping the {@link WebSocketMapping} that this container belongs to
|
||||
* @param httpClient the {@link HttpClient} instance to use
|
||||
*/
|
||||
public JavaxWebSocketServerContainer(WebSocketCreatorMapping webSocketCreatorMapping, HttpClient httpClient, Executor executor)
|
||||
public JavaxWebSocketServerContainer(WebSocketMapping webSocketMapping, HttpClient httpClient, Executor executor)
|
||||
{
|
||||
super(new WebSocketCoreClient(httpClient));
|
||||
this._webSocketCreatorMapping = webSocketCreatorMapping;
|
||||
super(() ->
|
||||
{
|
||||
WebSocketCoreClient client = new WebSocketCoreClient(httpClient);
|
||||
if (executor != null && httpClient == null)
|
||||
client.getHttpClient().setExecutor(executor);
|
||||
return client;
|
||||
});
|
||||
this.webSocketMapping = webSocketMapping;
|
||||
this.executor = executor;
|
||||
this.frameHandlerFactory = new JavaxWebSocketServerFrameHandlerFactory(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lifeCycleStopping(LifeCycle context)
|
||||
{
|
||||
ContextHandler contextHandler = (ContextHandler) context;
|
||||
JavaxWebSocketServerContainer container = contextHandler.getBean(JavaxWebSocketServerContainer.class);
|
||||
if (container==this)
|
||||
{
|
||||
contextHandler.removeBean(container);
|
||||
LifeCycle.stop(container);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ByteBufferPool getBufferPool()
|
||||
{
|
||||
return this._webSocketCreatorMapping.getBufferPool();
|
||||
return this.webSocketMapping.getBufferPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,7 +170,7 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
@Override
|
||||
public WebSocketExtensionRegistry getExtensionRegistry()
|
||||
{
|
||||
return this._webSocketCreatorMapping.getExtensionRegistry();
|
||||
return this.webSocketMapping.getExtensionRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -122,25 +182,7 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
@Override
|
||||
public DecoratedObjectFactory getObjectFactory()
|
||||
{
|
||||
return this._webSocketCreatorMapping.getObjectFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebSocketCoreClient getWebSocketCoreClient() throws Exception
|
||||
{
|
||||
// Lazy Start Http Client
|
||||
if (!coreClient.getHttpClient().isStarted())
|
||||
{
|
||||
coreClient.getHttpClient().start();
|
||||
}
|
||||
|
||||
// Lazy Start WebSocket Client
|
||||
if (!coreClient.isStarted())
|
||||
{
|
||||
coreClient.start();
|
||||
}
|
||||
|
||||
return coreClient;
|
||||
return this.webSocketMapping.getObjectFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -161,14 +203,6 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a @{@link ServerEndpoint} annotated endpoint class to
|
||||
* the server
|
||||
*
|
||||
* @param endpointClass the annotated endpoint class to add to the server
|
||||
* @throws DeploymentException if unable to deploy that endpoint class
|
||||
* @see javax.websocket.server.ServerContainer#addEndpoint(Class)
|
||||
*/
|
||||
@Override
|
||||
public void addEndpoint(Class<?> endpointClass) throws DeploymentException
|
||||
{
|
||||
|
@ -199,20 +233,11 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
else
|
||||
{
|
||||
if (deferredEndpointClasses == null)
|
||||
{
|
||||
deferredEndpointClasses = new ArrayList<>();
|
||||
}
|
||||
deferredEndpointClasses.add(endpointClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a ServerEndpointConfig to the server
|
||||
*
|
||||
* @param config the endpoint config to add
|
||||
* @throws DeploymentException if unable to deploy that endpoint class
|
||||
* @see javax.websocket.server.ServerContainer#addEndpoint(ServerEndpointConfig)
|
||||
*/
|
||||
@Override
|
||||
public void addEndpoint(ServerEndpointConfig config) throws DeploymentException
|
||||
{
|
||||
|
@ -243,8 +268,11 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
{
|
||||
frameHandlerFactory.getMetadata(config.getEndpointClass(), config);
|
||||
|
||||
JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, this._webSocketCreatorMapping.getExtensionRegistry());
|
||||
this._webSocketCreatorMapping.addMapping(new UriTemplatePathSpec(config.getPath()), creator);
|
||||
JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, this.webSocketMapping
|
||||
.getExtensionRegistry());
|
||||
|
||||
this.webSocketMapping
|
||||
.addMapping(new UriTemplatePathSpec(config.getPath()), creator, frameHandlerFactory, customizer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -283,20 +311,21 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
public int getDefaultMaxBinaryMessageBufferSize()
|
||||
{
|
||||
// TODO: warn on long -> int conversion issue
|
||||
return (int)this._webSocketCreatorMapping.getDefaultMaxBinaryMessageSize();
|
||||
// TODO: Should this be Filter?
|
||||
return (int)customizer.getMaxBinaryMessageSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDefaultMaxSessionIdleTimeout()
|
||||
{
|
||||
return this._webSocketCreatorMapping.getDefaultIdleTimeout().toMillis();
|
||||
return customizer.getIdleTimeout().toMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultMaxTextMessageBufferSize()
|
||||
{
|
||||
// TODO: warn on long -> int conversion issue
|
||||
return (int)this._webSocketCreatorMapping.getDefaultMaxTextMessageSize();
|
||||
return (int)customizer.getMaxTextMessageSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -308,18 +337,18 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
|
|||
@Override
|
||||
public void setDefaultMaxBinaryMessageBufferSize(int max)
|
||||
{
|
||||
this._webSocketCreatorMapping.setDefaultMaxBinaryMessageSize(max);
|
||||
customizer.setMaxBinaryMessageSize(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultMaxSessionIdleTimeout(long ms)
|
||||
{
|
||||
this._webSocketCreatorMapping.setDefaultIdleTimeout(Duration.ofMillis(ms));
|
||||
customizer.setIdleTimeout(Duration.ofMillis(ms));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultMaxTextMessageBufferSize(int max)
|
||||
{
|
||||
this._webSocketCreatorMapping.setDefaultMaxTextMessageSize(max);
|
||||
customizer.setMaxTextMessageSize(max);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,9 @@ package org.eclipse.jetty.websocket.javax.server;
|
|||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.HandlesTypes;
|
||||
import javax.websocket.DeploymentException;
|
||||
|
@ -34,14 +31,13 @@ import javax.websocket.server.ServerApplicationConfig;
|
|||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.ThreadClassLoaderScope;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
|
||||
|
||||
@HandlesTypes({ ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class })
|
||||
|
@ -49,36 +45,8 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
|
|||
{
|
||||
public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.javax";
|
||||
public static final String DEPRECATED_ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
|
||||
private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainerInitializer.class);
|
||||
public static final String HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.javax.HttpClient";
|
||||
|
||||
/**
|
||||
* DestroyListener
|
||||
*/
|
||||
public static class ContextDestroyListener implements ServletContextListener
|
||||
{
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce)
|
||||
{
|
||||
//noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce)
|
||||
{
|
||||
//remove any ServerContainer beans
|
||||
if (sce.getServletContext() instanceof ContextHandler.Context)
|
||||
{
|
||||
ContextHandler handler = ((ContextHandler.Context)sce.getServletContext()).getContextHandler();
|
||||
JavaxWebSocketServerContainer bean = handler.getBean(JavaxWebSocketServerContainer.class);
|
||||
if (bean != null)
|
||||
handler.removeBean(bean);
|
||||
}
|
||||
|
||||
//remove reference in attributes
|
||||
sce.getServletContext().removeAttribute(javax.websocket.server.ServerContainer.class.getName());
|
||||
}
|
||||
}
|
||||
private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainerInitializer.class);
|
||||
|
||||
/**
|
||||
* Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for
|
||||
|
@ -128,72 +96,15 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
|
|||
return defValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jetty Native approach.
|
||||
* <p>
|
||||
* Note: this will add the Upgrade filter to the existing list, with no regard for order. It will just be tacked onto the end of the list.
|
||||
*
|
||||
* @param context the servlet context handler
|
||||
* @return the created websocket server container
|
||||
* @throws ServletException if unable to create the websocket server container
|
||||
*/
|
||||
public static JavaxWebSocketServerContainer configureContext(ServletContextHandler context) throws ServletException
|
||||
public static JavaxWebSocketServerContainer configureContext(ServletContextHandler context)
|
||||
throws ServletException
|
||||
{
|
||||
WebSocketUpgradeFilter.configureContext(context);
|
||||
WebSocketCreatorMapping webSocketCreatorMapping = (WebSocketCreatorMapping)context.getAttribute(WebSocketCreatorMapping.class.getName());
|
||||
|
||||
// Find Pre-Existing (Shared?) HttpClient and/or executor
|
||||
HttpClient httpClient = (HttpClient)context.getServletContext().getAttribute(HTTPCLIENT_ATTRIBUTE);
|
||||
if ((httpClient == null) && (context.getServer() != null))
|
||||
{
|
||||
httpClient = (HttpClient) context.getServer().getAttribute(HTTPCLIENT_ATTRIBUTE);
|
||||
}
|
||||
|
||||
Executor executor = httpClient == null?null:httpClient.getExecutor();
|
||||
if (executor == null)
|
||||
executor = (Executor)context.getAttribute("org.eclipse.jetty.server.Executor");
|
||||
if (executor == null)
|
||||
executor = context.getServer().getThreadPool();
|
||||
|
||||
// Do we need to make a client?
|
||||
if (httpClient == null)
|
||||
{
|
||||
// TODO Do we always need a HttpClient?
|
||||
// TODO Can the client share the websocket or container buffer pool
|
||||
httpClient = new HttpClient();
|
||||
httpClient.setName("Javax-WebSocketServer@" + Integer.toHexString(httpClient.hashCode()));
|
||||
httpClient.setExecutor(executor);
|
||||
context.addBean(httpClient, true);
|
||||
}
|
||||
else if (httpClient.getExecutor() == null)
|
||||
{
|
||||
httpClient.setExecutor(executor);
|
||||
}
|
||||
|
||||
// Create the Jetty ServerContainer implementation
|
||||
JavaxWebSocketServerContainer jettyContainer = new JavaxWebSocketServerContainer(webSocketCreatorMapping, httpClient, executor);
|
||||
context.addBean(jettyContainer);
|
||||
|
||||
// Add WebSocketServletFrameHandlerFactory to servlet container for this JSR container
|
||||
webSocketCreatorMapping.addFrameHandlerFactory(jettyContainer.getFrameHandlerFactory());
|
||||
|
||||
// Store a reference to the ServerContainer per - javax.websocket spec 1.0 final - section 6.4: Programmatic Server Deployment
|
||||
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(), jettyContainer);
|
||||
|
||||
return jettyContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context not used
|
||||
* @param jettyContext the {@link ServletContextHandler} to use
|
||||
* @return a configured {@link JavaxWebSocketServerContainer} instance
|
||||
* @throws ServletException if the {@link WebSocketUpgradeFilter} cannot be configured
|
||||
* @deprecated use {@link #configureContext(ServletContextHandler)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static JavaxWebSocketServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
|
||||
{
|
||||
return configureContext(jettyContext);
|
||||
WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext());
|
||||
FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(context.getServletContext());
|
||||
JavaxWebSocketServerContainer container = JavaxWebSocketServerContainer.ensureContainer(context.getServletContext());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("configureContext {} {} {}",mapping,upgradeFilter,container);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -211,30 +122,13 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
|
|||
return;
|
||||
}
|
||||
|
||||
ContextHandler handler = ContextHandler.getContextHandler(context);
|
||||
|
||||
if (handler == null)
|
||||
{
|
||||
throw new ServletException("Not running on Jetty, Javax Websocket support unavailable");
|
||||
}
|
||||
|
||||
if (!(handler instanceof ServletContextHandler))
|
||||
{
|
||||
throw new ServletException("Not running in Jetty ServletContextHandler, Javax Websocket support unavailable");
|
||||
}
|
||||
|
||||
ServletContextHandler jettyContext = (ServletContextHandler)handler;
|
||||
JavaxWebSocketServerContainer container = configureContext(ServletContextHandler.getServletContextHandler(context,"Javax WebSocket SCI"));
|
||||
|
||||
try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader()))
|
||||
{
|
||||
// Create the Jetty ServerContainer implementation
|
||||
JavaxWebSocketServerContainer jettyContainer = configureContext(jettyContext);
|
||||
context.addListener(new ContextDestroyListener()); // make sure context is cleaned up when the context stops
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Found {} classes", c.size());
|
||||
}
|
||||
|
||||
// Now process the incoming classes
|
||||
Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
|
||||
|
@ -258,9 +152,8 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
|
|||
for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Found ServerApplicationConfig: {}", clazz);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ServerApplicationConfig config = clazz.getDeclaredConstructor().newInstance();
|
||||
|
@ -302,7 +195,7 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
|
|||
{
|
||||
try
|
||||
{
|
||||
jettyContainer.addEndpoint(config);
|
||||
container.addEndpoint(config);
|
||||
}
|
||||
catch (DeploymentException e)
|
||||
{
|
||||
|
@ -318,7 +211,7 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
|
|||
{
|
||||
try
|
||||
{
|
||||
jettyContainer.addEndpoint(annotatedClass);
|
||||
container.addEndpoint(annotatedClass);
|
||||
}
|
||||
catch (DeploymentException e)
|
||||
{
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactor
|
|||
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
|
||||
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.EndpointConfig;
|
||||
|
@ -36,7 +36,7 @@ import javax.websocket.Session;
|
|||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory implements WebSocketServletFrameHandlerFactory
|
||||
public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory implements FrameHandlerFactory
|
||||
{
|
||||
public JavaxWebSocketServerFrameHandlerFactory(JavaxWebSocketContainer container)
|
||||
{
|
||||
|
|
|
@ -20,13 +20,13 @@ package org.eclipse.jetty.websocket.javax.server;
|
|||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
|
||||
public class DummyServerContainer extends JavaxWebSocketServerContainer
|
||||
{
|
||||
public DummyServerContainer()
|
||||
{
|
||||
super(new WebSocketCreatorMapping(), new HttpClient(), new QueuedThreadPool());
|
||||
super(new WebSocketMapping(), new HttpClient(), new QueuedThreadPool());
|
||||
addBean(getHttpClient(), true);
|
||||
addBean(getExecutor(), true);
|
||||
}
|
||||
|
|
|
@ -38,9 +38,11 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.internal.Parser;
|
||||
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainerInitializer;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
|||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.websocket.ClientEndpoint;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.DecodeException;
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.eclipse.jetty.websocket.javax.client.EmptyClientEndpointConfig;
|
|||
import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders;
|
||||
import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders;
|
||||
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainer;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
|
@ -47,7 +47,7 @@ public abstract class AbstractJavaxWebSocketServerFrameHandlerTest
|
|||
context = new ServletContextHandler();
|
||||
server.setHandler(context);
|
||||
|
||||
WebSocketCreatorMapping factory = new WebSocketCreatorMapping();
|
||||
WebSocketMapping factory = new WebSocketMapping();
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
||||
container = new JavaxWebSocketServerContainer(factory, httpClient, server.getThreadPool());
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenCloseRe
|
|||
import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenIntSocket;
|
||||
import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenSessionIntSocket;
|
||||
import org.eclipse.jetty.websocket.javax.common.util.InvalidSignatureException;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
@ -99,7 +99,7 @@ public class DeploymentExceptionTest
|
|||
{
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
|
||||
WebSocketCreatorMapping factory = new WebSocketCreatorMapping();
|
||||
WebSocketMapping factory = new WebSocketMapping();
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
||||
JavaxWebSocketServerContainer container = new JavaxWebSocketServerContainer(factory, httpClient, server.getThreadPool());
|
||||
|
|
|
@ -18,11 +18,14 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.javax.tests.server.sockets;
|
||||
|
||||
import javax.websocket.OnError;
|
||||
import javax.websocket.OnMessage;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
|
||||
import org.eclipse.jetty.websocket.core.WebSocketTimeoutException;
|
||||
|
||||
@ServerEndpoint(value = "/idle-onopen-socket")
|
||||
public class IdleTimeoutOnOpenSocket
|
||||
{
|
||||
|
@ -37,4 +40,11 @@ public class IdleTimeoutOnOpenSocket
|
|||
{
|
||||
return msg;
|
||||
}
|
||||
|
||||
@OnError
|
||||
public void onError(Throwable cause)
|
||||
{
|
||||
if (!(cause instanceof WebSocketTimeoutException))
|
||||
throw new RuntimeException(cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,13 +67,11 @@ public class JettyWebSocketFrameHandler implements FrameHandler
|
|||
*/
|
||||
private final UpgradeResponse upgradeResponse;
|
||||
private final CompletableFuture<Session> futureSession;
|
||||
private final CoreCustomizer customizer;
|
||||
private final Customizer customizer;
|
||||
private MessageSink textSink;
|
||||
private MessageSink binarySink;
|
||||
private MessageSink activeMessageSink;
|
||||
private WebSocketSessionImpl session;
|
||||
private long maxBinaryMessageSize = -1;
|
||||
private long maxTextMessageSize = -1;
|
||||
|
||||
public JettyWebSocketFrameHandler(Executor executor,
|
||||
Object endpointInstance,
|
||||
|
@ -85,7 +83,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler
|
|||
MethodHandle frameHandle,
|
||||
MethodHandle pingHandle, MethodHandle pongHandle,
|
||||
CompletableFuture<Session> futureSession,
|
||||
CoreCustomizer customizer)
|
||||
Customizer customizer)
|
||||
{
|
||||
this.log = Log.getLogger(endpointInstance.getClass());
|
||||
|
||||
|
@ -130,7 +128,9 @@ public class JettyWebSocketFrameHandler implements FrameHandler
|
|||
|
||||
if (errorHandle == null)
|
||||
{
|
||||
log.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " missing onError handler", cause);
|
||||
log.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " : " + cause);
|
||||
if (log.isDebugEnabled())
|
||||
log.debug("unhandled", cause);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -228,14 +228,10 @@ public class JettyWebSocketFrameHandler implements FrameHandler
|
|||
pongHandle = JettyWebSocketFrameHandlerFactory.bindTo(pongHandle, session);
|
||||
|
||||
if (textHandle != null)
|
||||
{
|
||||
textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, getMaxTextMessageSize());
|
||||
}
|
||||
textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, coreSession.getMaxTextMessageSize());
|
||||
|
||||
if (binaryHandle != null)
|
||||
{
|
||||
binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, getMaxBinaryMessageSize());
|
||||
}
|
||||
binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, coreSession.getMaxBinaryMessageSize());
|
||||
|
||||
if (openHandle != null)
|
||||
{
|
||||
|
@ -252,26 +248,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler
|
|||
futureSession.complete(session);
|
||||
}
|
||||
|
||||
public long getMaxBinaryMessageSize()
|
||||
{
|
||||
return maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
public void setMaxBinaryMessageSize(long maxSize)
|
||||
{
|
||||
this.maxBinaryMessageSize = maxSize;
|
||||
}
|
||||
|
||||
public long getMaxTextMessageSize()
|
||||
{
|
||||
return maxTextMessageSize;
|
||||
}
|
||||
|
||||
public void setMaxTextMessageSize(long maxSize)
|
||||
{
|
||||
this.maxTextMessageSize = maxSize;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s]", this.getClass().getSimpleName(), this.hashCode(), endpointInstance.getClass().getName());
|
||||
|
|
|
@ -18,6 +18,24 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
|
@ -44,28 +62,12 @@ import org.eclipse.jetty.websocket.common.message.ReaderMessageSink;
|
|||
import org.eclipse.jetty.websocket.common.message.StringMessageSink;
|
||||
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Factory for websocket-core {@link FrameHandler} implementations suitable for
|
||||
* Factory to create {@link JettyWebSocketFrameHandler} instances suitable for
|
||||
* use with jetty-native websocket API.
|
||||
* <p>
|
||||
* Will create a {@link FrameHandler} suitable for use with classes/objects that:
|
||||
* Will create a {@link org.eclipse.jetty.websocket.core.FrameHandler} suitable for use with classes/objects that:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Is @{@link org.eclipse.jetty.websocket.api.annotations.WebSocket} annotated</li>
|
||||
|
@ -77,7 +79,7 @@ import java.util.concurrent.Executor;
|
|||
* <li>Implements {@link org.eclipse.jetty.websocket.api.WebSocketFrameListener}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class JettyWebSocketFrameHandlerFactory
|
||||
public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
|
||||
{
|
||||
private final Executor executor;
|
||||
private Map<Class<?>, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>();
|
||||
|
@ -85,6 +87,7 @@ public class JettyWebSocketFrameHandlerFactory
|
|||
public JettyWebSocketFrameHandlerFactory(Executor executor)
|
||||
{
|
||||
this.executor = executor;
|
||||
addBean(executor);
|
||||
}
|
||||
|
||||
public JettyWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass)
|
||||
|
@ -156,13 +159,6 @@ public class JettyWebSocketFrameHandlerFactory
|
|||
future,
|
||||
metadata);
|
||||
|
||||
// TODO these are not attributes on the CoreSession, so we need another path to route them to the sinks that enforce them:
|
||||
if (metadata.getMaxBinaryMessageSize() >= -1)
|
||||
frameHandler.setMaxBinaryMessageSize(metadata.getMaxBinaryMessageSize());
|
||||
|
||||
if (metadata.getMaxTextMessageSize() >= -1)
|
||||
frameHandler.setMaxTextMessageSize(metadata.getMaxTextMessageSize());
|
||||
|
||||
return frameHandler;
|
||||
}
|
||||
|
||||
|
@ -298,10 +294,14 @@ public class JettyWebSocketFrameHandlerFactory
|
|||
{
|
||||
JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata();
|
||||
|
||||
metadata.setInputBufferSize(anno.inputBufferSize());
|
||||
metadata.setMaxBinaryMessageSize(anno.maxBinaryMessageSize());
|
||||
if (anno.inputBufferSize()>=0)
|
||||
metadata.setInputBufferSize(anno.inputBufferSize());
|
||||
if (anno.maxBinaryMessageSize()>=0)
|
||||
metadata.setMaxBinaryMessageSize(anno.maxBinaryMessageSize());
|
||||
if (anno.maxTextMessageSize()>=0)
|
||||
metadata.setMaxTextMessageSize(anno.maxTextMessageSize());
|
||||
metadata.setIdleTimeout(anno.maxIdleTime());
|
||||
if (anno.maxIdleTime()>=0)
|
||||
metadata.setIdleTimeout(Duration.ofMillis(anno.maxIdleTime()));
|
||||
metadata.setBatchMode(anno.batchMode());
|
||||
|
||||
Method onmethod;
|
||||
|
@ -486,4 +486,9 @@ public class JettyWebSocketFrameHandlerFactory
|
|||
throw new InvalidSignatureException(err.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
dumpObjects(out, indent, metadataMap);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.eclipse.jetty.websocket.core.FrameHandler;
|
|||
import java.lang.invoke.MethodHandle;
|
||||
import java.time.Duration;
|
||||
|
||||
public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCustomizer
|
||||
public class JettyWebSocketFrameHandlerMetadata extends FrameHandler.ConfigurationCustomizer
|
||||
{
|
||||
private MethodHandle openHandle;
|
||||
private MethodHandle closeHandle;
|
||||
|
@ -41,12 +41,6 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
|
|||
private MethodHandle pingHandle;
|
||||
private MethodHandle pongHandle;
|
||||
|
||||
// Policy Configuration
|
||||
private int idleTimeout = -1;
|
||||
private int inputBufferSize = -1;
|
||||
private int maxBinaryMessageSize = -1;
|
||||
private int maxTextMessageSize = -1;
|
||||
|
||||
// Batch Configuration
|
||||
// TODO remove?
|
||||
private BatchMode batchMode = BatchMode.OFF;
|
||||
|
@ -111,46 +105,6 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
|
|||
return frameHandle;
|
||||
}
|
||||
|
||||
public void setIdleTimeout(int idleTimeout)
|
||||
{
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public int getIdleTimeout()
|
||||
{
|
||||
return idleTimeout;
|
||||
}
|
||||
|
||||
public void setInputBufferSize(int inputBufferSize)
|
||||
{
|
||||
this.inputBufferSize = inputBufferSize;
|
||||
}
|
||||
|
||||
public int getInputBufferSize()
|
||||
{
|
||||
return inputBufferSize;
|
||||
}
|
||||
|
||||
public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
|
||||
{
|
||||
this.maxBinaryMessageSize = maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
public int getMaxBinaryMessageSize()
|
||||
{
|
||||
return maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
public void setMaxTextMessageSize(int maxTextMessageSize)
|
||||
{
|
||||
this.maxTextMessageSize = maxTextMessageSize;
|
||||
}
|
||||
|
||||
public int getMaxTextMessageSize()
|
||||
{
|
||||
return maxTextMessageSize;
|
||||
}
|
||||
|
||||
public void setOpenHandler(MethodHandle open, Object origin)
|
||||
{
|
||||
assertNotSet(this.openHandle, "OPEN Handler", origin);
|
||||
|
@ -226,16 +180,4 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
|
|||
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(FrameHandler.CoreSession session)
|
||||
{
|
||||
// Update passed in (unique) policy for this Frame Handler instance
|
||||
if (getIdleTimeout() > 0)
|
||||
session.setIdleTimeout(Duration.ofMillis(getIdleTimeout()));
|
||||
|
||||
if (getInputBufferSize() > 0)
|
||||
session.setInputBufferSize(getInputBufferSize());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -104,13 +106,13 @@ public class WebSocketSessionImpl implements Session
|
|||
@Override
|
||||
public long getMaxBinaryMessageSize()
|
||||
{
|
||||
return frameHandler.getMaxBinaryMessageSize();
|
||||
return coreSession.getMaxBinaryMessageSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxTextMessageSize()
|
||||
{
|
||||
return frameHandler.getMaxTextMessageSize();
|
||||
return coreSession.getMaxTextMessageSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -134,13 +136,13 @@ public class WebSocketSessionImpl implements Session
|
|||
@Override
|
||||
public void setMaxBinaryMessageSize(long size)
|
||||
{
|
||||
frameHandler.setMaxBinaryMessageSize(size);
|
||||
coreSession.setMaxBinaryMessageSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxTextMessageSize(long size)
|
||||
{
|
||||
frameHandler.setMaxTextMessageSize(size);
|
||||
coreSession.setMaxTextMessageSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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=%dms]",
|
||||
this.getClass().getSimpleName(), hashCode(),
|
||||
getPolicy().getBehavior(),
|
||||
getIdleTimeout().toMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -104,6 +104,10 @@ public class InvokerUtils
|
|||
* Might need to drop calling args and/or reorder the calling args to fit
|
||||
* the actual method being called.
|
||||
* </p>
|
||||
* @param targetClass the target class for invocations of the resulting MethodHandle (also known as parameter 0)
|
||||
* @param method the method to invoke
|
||||
* @param callingArgs the calling arguments
|
||||
* @return the MethodHandle for this set of CallingArgs
|
||||
*/
|
||||
public static MethodHandle mutatedInvoker(Class<?> targetClass, Method method, Arg... callingArgs)
|
||||
{
|
||||
|
|
|
@ -32,6 +32,12 @@
|
|||
<artifactId>jetty-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jmx</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>jetty-websocket-api</artifactId>
|
||||
|
@ -51,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>
|
||||
|
|
|
@ -28,6 +28,9 @@ module org.eclipse.jetty.websocket.jetty.server
|
|||
|
||||
requires javax.servlet.api;
|
||||
requires org.eclipse.jetty.util;
|
||||
requires org.eclipse.jetty.http;
|
||||
requires org.eclipse.jetty.server;
|
||||
requires static org.eclipse.jetty.jmx;
|
||||
requires org.eclipse.jetty.servlet;
|
||||
requires org.eclipse.jetty.webapp;
|
||||
requires org.eclipse.jetty.websocket.jetty.api;
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.server.internal.UpgradeRequestAdapter;
|
||||
import org.eclipse.jetty.websocket.server.internal.UpgradeResponseAdapter;
|
||||
import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||
|
||||
public class JettyServerFrameHandlerFactory
|
||||
extends JettyWebSocketFrameHandlerFactory
|
||||
implements FrameHandlerFactory, LifeCycle.Listener
|
||||
{
|
||||
public static JettyServerFrameHandlerFactory ensureFactory(ServletContext servletContext)
|
||||
throws ServletException
|
||||
{
|
||||
ContextHandler contextHandler = ServletContextHandler.getServletContextHandler(servletContext, "Jetty Websocket");
|
||||
|
||||
JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class);
|
||||
if (factory == null)
|
||||
{
|
||||
Executor executor = (Executor)servletContext
|
||||
.getAttribute("org.eclipse.jetty.server.Executor");
|
||||
if (executor == null)
|
||||
executor = contextHandler.getServer().getThreadPool();
|
||||
|
||||
factory = new JettyServerFrameHandlerFactory(executor);
|
||||
contextHandler.addManaged(factory);
|
||||
contextHandler.addLifeCycleListener(factory);
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
public JettyServerFrameHandlerFactory(Executor executor)
|
||||
{
|
||||
super(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse)
|
||||
{
|
||||
return super.newJettyFrameHandler(websocketPojo, new UpgradeRequestAdapter(upgradeRequest), new UpgradeResponseAdapter(upgradeResponse),
|
||||
new CompletableFuture<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lifeCycleStopping(LifeCycle context)
|
||||
{
|
||||
ContextHandler contextHandler = (ContextHandler) context;
|
||||
JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class);
|
||||
if (factory != null)
|
||||
{
|
||||
contextHandler.removeBean(factory);
|
||||
LifeCycle.stop(factory);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,10 +18,16 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
|
||||
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
|
@ -37,6 +43,8 @@ import java.util.concurrent.Executor;
|
|||
*/
|
||||
public class JettyWebSocketServletContainerInitializer implements ServletContainerInitializer
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(JettyWebSocketServletContainerInitializer.class);
|
||||
|
||||
public static class JettyWebSocketEmbeddedStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller
|
||||
{
|
||||
private ServletContainerInitializer sci;
|
||||
|
@ -70,33 +78,13 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
|
||||
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException
|
||||
{
|
||||
// TODO why doesn't the javax side share this approach?
|
||||
List<WebSocketServletFrameHandlerFactory> factories = (List<WebSocketServletFrameHandlerFactory>)ctx
|
||||
.getAttribute(WebSocketServletFrameHandlerFactory.ATTR_HANDLERS);
|
||||
if (factories == null)
|
||||
{
|
||||
factories = new ArrayList<>();
|
||||
ctx.setAttribute(WebSocketServletFrameHandlerFactory.ATTR_HANDLERS, factories);
|
||||
}
|
||||
WebSocketMapping mapping = WebSocketMapping.ensureMapping(servletContext);
|
||||
FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(servletContext);
|
||||
JettyServerFrameHandlerFactory factory = JettyServerFrameHandlerFactory.ensureFactory(servletContext);
|
||||
|
||||
Executor executor = (Executor)ctx.getAttribute("org.eclipse.jetty.server.Executor");
|
||||
if (executor == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||
threadPool.setName("Jetty-WebSocketServer");
|
||||
threadPool.start();
|
||||
executor = threadPool;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ServletException("Unable to start internal Executor", e);
|
||||
}
|
||||
}
|
||||
|
||||
factories.add(new JettyWebSocketServletFrameHandlerFactory(executor));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onStartup {} {} {}",mapping, upgradeFilter, factory);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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;
|
||||
|
||||
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.server.internal.UpgradeRequestAdapter;
|
||||
import org.eclipse.jetty.websocket.server.internal.UpgradeResponseAdapter;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFrameHandlerFactory;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class JettyWebSocketServletFrameHandlerFactory extends JettyWebSocketFrameHandlerFactory implements WebSocketServletFrameHandlerFactory
|
||||
{
|
||||
public JettyWebSocketServletFrameHandlerFactory(Executor executor)
|
||||
{
|
||||
super(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse)
|
||||
{
|
||||
return super.newJettyFrameHandler(websocketPojo, new UpgradeRequestAdapter(upgradeRequest), new UpgradeResponseAdapter(upgradeResponse),
|
||||
new CompletableFuture<>());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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 javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
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.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 ...");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
request.getServletContext().getNamedDispatcher("default").forward(request,response);
|
||||
}
|
||||
}
|
||||
|
||||
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,307 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
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.OnWebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
|
||||
@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 "ping":
|
||||
{
|
||||
try
|
||||
{
|
||||
LOG.info("PING!");
|
||||
this.session.getRemote().sendPing(BufferUtil.toBuffer("ping from server"));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
}
|
||||
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":
|
||||
{
|
||||
String dump = (session instanceof Dumpable) ? ((Dumpable) session).dump() : session.toString();
|
||||
System.err.println(dump);
|
||||
Arrays.stream(dump.split("\n")).forEach(d->writeMessage("Dump: %s", d));
|
||||
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,60 @@
|
|||
<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="ping" class="button" type="submit" name="ping" value="ping" 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; }
|
||||
$("ping").onclick = function(event) {wstool.write("ping:"); 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,142 @@
|
|||
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 && !message.startsWith("Dump:")) {
|
||||
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;
|
||||
$('ping').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);
|
||||
}
|
||||
};
|
|
@ -30,7 +30,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
import org.eclipse.jetty.websocket.core.internal.WebSocketChannel;
|
||||
|
||||
@ManagedObject("Abstract Extension")
|
||||
public abstract class AbstractExtension implements Extension, Dumpable
|
||||
public abstract class AbstractExtension implements Extension
|
||||
{
|
||||
private final Logger log;
|
||||
private ByteBufferPool bufferPool;
|
||||
|
@ -44,27 +44,6 @@ public abstract class AbstractExtension implements Extension, Dumpable
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -125,10 +125,53 @@ public interface FrameHandler extends IncomingFrames
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
interface Configuration
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the Idle Timeout
|
||||
*
|
||||
* @return the idle timeout
|
||||
*/
|
||||
Duration getIdleTimeout();
|
||||
|
||||
/**
|
||||
* Set the Idle Timeout.
|
||||
*
|
||||
* @param timeout the timeout duration
|
||||
*/
|
||||
void setIdleTimeout(Duration timeout);
|
||||
|
||||
boolean isAutoFragment();
|
||||
|
||||
void setAutoFragment(boolean autoFragment);
|
||||
|
||||
long getMaxFrameSize();
|
||||
|
||||
void setMaxFrameSize(long maxFrameSize);
|
||||
|
||||
int getOutputBufferSize();
|
||||
|
||||
void setOutputBufferSize(int outputBufferSize);
|
||||
|
||||
int getInputBufferSize();
|
||||
|
||||
void setInputBufferSize(int inputBufferSize);
|
||||
|
||||
long getMaxBinaryMessageSize();
|
||||
|
||||
void setMaxBinaryMessageSize(long maxSize);
|
||||
|
||||
long getMaxTextMessageSize();
|
||||
|
||||
void setMaxTextMessageSize(long maxSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the outgoing Frames.
|
||||
*/
|
||||
interface CoreSession extends OutgoingFrames
|
||||
interface CoreSession extends OutgoingFrames, Configuration
|
||||
{
|
||||
/**
|
||||
* The negotiated WebSocket Sub-Protocol for this channel.
|
||||
|
@ -229,20 +272,6 @@ public interface FrameHandler extends IncomingFrames
|
|||
*/
|
||||
boolean isOpen();
|
||||
|
||||
/**
|
||||
* Get the Idle Timeout
|
||||
*
|
||||
* @return the idle timeout
|
||||
*/
|
||||
Duration getIdleTimeout();
|
||||
|
||||
/**
|
||||
* Set the Idle Timeout.
|
||||
*
|
||||
* @param timeout the timeout duration
|
||||
*/
|
||||
void setIdleTimeout(Duration timeout);
|
||||
|
||||
/**
|
||||
* If using BatchMode.ON or BatchMode.AUTO, trigger a flush of enqueued / batched frames.
|
||||
*
|
||||
|
@ -277,22 +306,6 @@ public interface FrameHandler extends IncomingFrames
|
|||
*/
|
||||
void demand(long n);
|
||||
|
||||
boolean isAutoFragment();
|
||||
|
||||
void setAutoFragment(boolean autoFragment);
|
||||
|
||||
long getMaxFrameSize();
|
||||
|
||||
void setMaxFrameSize(long maxFrameSize);
|
||||
|
||||
int getOutputBufferSize();
|
||||
|
||||
void setOutputBufferSize(int outputBufferSize);
|
||||
|
||||
int getInputBufferSize();
|
||||
|
||||
void setInputBufferSize(int inputBufferSize);
|
||||
|
||||
class Empty implements CoreSession
|
||||
{
|
||||
@Override
|
||||
|
@ -375,31 +388,26 @@ public interface FrameHandler extends IncomingFrames
|
|||
@Override
|
||||
public void setIdleTimeout(Duration timeout)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush(Callback callback)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(Callback callback)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(int statusCode, String reason, Callback callback)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void demand(long n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -411,7 +419,6 @@ public interface FrameHandler extends IncomingFrames
|
|||
@Override
|
||||
public void setAutoFragment(boolean autoFragment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -423,7 +430,6 @@ public interface FrameHandler extends IncomingFrames
|
|||
@Override
|
||||
public void setMaxFrameSize(long maxFrameSize)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -435,7 +441,6 @@ public interface FrameHandler extends IncomingFrames
|
|||
@Override
|
||||
public void setOutputBufferSize(int outputBufferSize)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -447,19 +452,154 @@ public interface FrameHandler extends IncomingFrames
|
|||
@Override
|
||||
public void setInputBufferSize(int inputBufferSize)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFrame(Frame frame, Callback callback, boolean batch)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxBinaryMessageSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxBinaryMessageSize(long maxSize)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxTextMessageSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxTextMessageSize(long maxSize)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CoreCustomizer
|
||||
interface Customizer
|
||||
{
|
||||
void customize(CoreSession session);
|
||||
}
|
||||
|
||||
class ConfigurationCustomizer implements Customizer, Configuration
|
||||
{
|
||||
private Duration timeout;
|
||||
private Boolean autoFragment;
|
||||
private Long maxFrameSize;
|
||||
private Integer outputBufferSize;
|
||||
private Integer inputBufferSize;
|
||||
private Long maxBinaryMessageSize;
|
||||
private Long maxTextMessageSize;
|
||||
|
||||
@Override
|
||||
public Duration getIdleTimeout()
|
||||
{
|
||||
return timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdleTimeout(Duration timeout)
|
||||
{
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoFragment()
|
||||
{
|
||||
return autoFragment==null?WebSocketConstants.DEFAULT_AUTO_FRAGMENT:autoFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutoFragment(boolean autoFragment)
|
||||
{
|
||||
this.autoFragment = autoFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxFrameSize()
|
||||
{
|
||||
return maxFrameSize==null?WebSocketConstants.DEFAULT_MAX_FRAME_SIZE:maxFrameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxFrameSize(long maxFrameSize)
|
||||
{
|
||||
this.maxFrameSize = maxFrameSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutputBufferSize()
|
||||
{
|
||||
return outputBufferSize==null?WebSocketConstants.DEFAULT_OUTPUT_BUFFER_SIZE:outputBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputBufferSize(int outputBufferSize)
|
||||
{
|
||||
this.outputBufferSize = outputBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputBufferSize()
|
||||
{
|
||||
return inputBufferSize==null?WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE:inputBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputBufferSize(int inputBufferSize)
|
||||
{
|
||||
this.inputBufferSize = inputBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxBinaryMessageSize()
|
||||
{
|
||||
return maxBinaryMessageSize==null?WebSocketConstants.DEFAULT_MAX_BINARY_MESSAGE_SIZE:maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxBinaryMessageSize(long maxBinaryMessageSize)
|
||||
{
|
||||
this.maxBinaryMessageSize = maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxTextMessageSize()
|
||||
{
|
||||
return maxTextMessageSize==null?WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE:maxTextMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxTextMessageSize(long maxTextMessageSize)
|
||||
{
|
||||
this.maxTextMessageSize = maxTextMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(CoreSession session)
|
||||
{
|
||||
if (timeout!=null)
|
||||
session.setIdleTimeout(timeout);
|
||||
if (autoFragment!=null)
|
||||
session.setAutoFragment(autoFragment);
|
||||
if (maxFrameSize!=null)
|
||||
session.setMaxFrameSize(maxFrameSize);
|
||||
if (inputBufferSize!=null)
|
||||
session.setInputBufferSize(inputBufferSize);
|
||||
if (outputBufferSize!=null)
|
||||
session.setOutputBufferSize(outputBufferSize);
|
||||
if (maxBinaryMessageSize!=null)
|
||||
session.setMaxBinaryMessageSize(maxBinaryMessageSize);
|
||||
if (maxTextMessageSize!=null)
|
||||
session.setMaxTextMessageSize(maxTextMessageSize);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.core.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
|
@ -29,18 +33,14 @@ import org.eclipse.jetty.websocket.core.ExtensionConfig;
|
|||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHandler.CoreCustomizer
|
||||
public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHandler.Customizer
|
||||
{
|
||||
|
||||
private static final Logger LOG = Log.getLogger(WebSocketCoreClient.class);
|
||||
private final HttpClient httpClient;
|
||||
private WebSocketExtensionRegistry extensionRegistry;
|
||||
private DecoratedObjectFactory objectFactory;
|
||||
private final FrameHandler.CoreCustomizer customizer;
|
||||
private final FrameHandler.Customizer customizer;
|
||||
|
||||
// TODO: Things to consider for inclusion in this class (or removal if they can be set elsewhere, like HttpClient)
|
||||
// - AsyncWrite Idle Timeout
|
||||
|
@ -51,12 +51,7 @@ public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHand
|
|||
|
||||
public WebSocketCoreClient()
|
||||
{
|
||||
this(new HttpClient(new SslContextFactory()));
|
||||
// TODO is there more HttpClient configuration we should do by default?
|
||||
httpClient.getSslContextFactory().setEndpointIdentificationAlgorithm("HTTPS");
|
||||
httpClient.setName("WSCoreClient");
|
||||
// Internally created, let websocket client's lifecycle manage it.
|
||||
addManaged(httpClient);
|
||||
this(null,null);
|
||||
}
|
||||
|
||||
public WebSocketCoreClient(HttpClient httpClient)
|
||||
|
@ -64,9 +59,15 @@ public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHand
|
|||
this(httpClient, null);
|
||||
}
|
||||
|
||||
public WebSocketCoreClient(HttpClient httpClient, FrameHandler.CoreCustomizer customizer)
|
||||
public WebSocketCoreClient(HttpClient httpClient, FrameHandler.Customizer customizer)
|
||||
{
|
||||
this.httpClient = httpClient == null?new HttpClient():httpClient;
|
||||
if (httpClient==null)
|
||||
{
|
||||
httpClient = new HttpClient(new SslContextFactory());
|
||||
httpClient.getSslContextFactory().setEndpointIdentificationAlgorithm("HTTPS");
|
||||
httpClient.setName(String.format("%s@%x",getClass().getSimpleName(),hashCode()));
|
||||
}
|
||||
this.httpClient = httpClient;
|
||||
this.extensionRegistry = new WebSocketExtensionRegistry();
|
||||
this.objectFactory = new DecoratedObjectFactory();
|
||||
this.customizer = customizer;
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -70,6 +70,8 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
|
|||
private long maxFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE;
|
||||
private int inputBufferSize = WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE;
|
||||
private int outputBufferSize = WebSocketConstants.DEFAULT_OUTPUT_BUFFER_SIZE;
|
||||
private long maxBinaryMessageSize = WebSocketConstants.DEFAULT_MAX_BINARY_MESSAGE_SIZE;
|
||||
private long maxTextMessageSize = WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE;
|
||||
|
||||
public WebSocketChannel(FrameHandler handler,
|
||||
Behavior behavior,
|
||||
|
@ -576,6 +578,30 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
|
|||
this.inputBufferSize = inputBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxBinaryMessageSize()
|
||||
{
|
||||
return maxBinaryMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxBinaryMessageSize(long maxSize)
|
||||
{
|
||||
maxBinaryMessageSize = maxSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxTextMessageSize()
|
||||
{
|
||||
return maxTextMessageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxTextMessageSize(long maxSize)
|
||||
{
|
||||
maxTextMessageSize = maxSize;
|
||||
}
|
||||
|
||||
private class IncomingState extends FrameSequence implements IncomingFrames
|
||||
{
|
||||
@Override
|
||||
|
@ -672,7 +698,7 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
|
|||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
Dumpable.dumpObjects(out, indent, this,
|
||||
negotiated.getSubProtocol(),
|
||||
"subprotocol="+negotiated.getSubProtocol(),
|
||||
negotiated.getExtensions(),
|
||||
handler);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.core.server;
|
||||
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.server.internal.RFC6455Handshaker;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -31,5 +32,10 @@ public interface Handshaker
|
|||
return new RFC6455Handshaker();
|
||||
}
|
||||
|
||||
boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request, HttpServletResponse response) throws IOException;
|
||||
boolean upgradeRequest(
|
||||
WebSocketNegotiator negotiator,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FrameHandler.Customizer defaultCustomizer)
|
||||
throws IOException;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
|
|||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
|
||||
public interface WebSocketNegotiator extends FrameHandler.Customizer
|
||||
{
|
||||
FrameHandler negotiate(Negotiation negotiation) throws IOException;
|
||||
|
||||
|
@ -48,7 +48,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
|
|||
};
|
||||
}
|
||||
|
||||
static WebSocketNegotiator from(Function<Negotiation, FrameHandler> negotiate, FrameHandler.CoreCustomizer customizer)
|
||||
static WebSocketNegotiator from(Function<Negotiation, FrameHandler> negotiate, FrameHandler.Customizer customizer)
|
||||
{
|
||||
return new AbstractNegotiator(null, null, null, customizer)
|
||||
{
|
||||
|
@ -65,7 +65,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
|
|||
WebSocketExtensionRegistry extensionRegistry,
|
||||
DecoratedObjectFactory objectFactory,
|
||||
ByteBufferPool bufferPool,
|
||||
FrameHandler.CoreCustomizer customizer)
|
||||
FrameHandler.Customizer customizer)
|
||||
{
|
||||
return new AbstractNegotiator(extensionRegistry, objectFactory, bufferPool, customizer)
|
||||
{
|
||||
|
@ -82,7 +82,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
|
|||
final WebSocketExtensionRegistry extensionRegistry;
|
||||
final DecoratedObjectFactory objectFactory;
|
||||
final ByteBufferPool bufferPool;
|
||||
final FrameHandler.CoreCustomizer customizer;
|
||||
final FrameHandler.Customizer customizer;
|
||||
|
||||
public AbstractNegotiator()
|
||||
{
|
||||
|
@ -93,7 +93,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
|
|||
WebSocketExtensionRegistry extensionRegistry,
|
||||
DecoratedObjectFactory objectFactory,
|
||||
ByteBufferPool bufferPool,
|
||||
FrameHandler.CoreCustomizer customizer)
|
||||
FrameHandler.Customizer customizer)
|
||||
{
|
||||
this.extensionRegistry = extensionRegistry == null?new WebSocketExtensionRegistry():extensionRegistry;
|
||||
this.objectFactory = objectFactory == null?new DecoratedObjectFactory():objectFactory;
|
||||
|
@ -125,5 +125,10 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
|
|||
{
|
||||
return bufferPool;
|
||||
}
|
||||
|
||||
public FrameHandler.Customizer getCustomizer()
|
||||
{
|
||||
return customizer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ public class WebSocketUpgradeHandler extends HandlerWrapper
|
|||
return;
|
||||
}
|
||||
|
||||
if (handshaker.upgradeRequest(negotiator, request, response))
|
||||
if (handshaker.upgradeRequest(negotiator, request, response, null))
|
||||
return;
|
||||
|
||||
if (!baseRequest.isHandled())
|
||||
|
|
|
@ -58,7 +58,9 @@ public final class RFC6455Handshaker implements Handshaker
|
|||
private static final HttpField CONNECTION_UPGRADE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeader.UPGRADE.asString());
|
||||
private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION);
|
||||
|
||||
public boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
public boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FrameHandler.Customizer defaultCustomizer) throws IOException
|
||||
{
|
||||
Request baseRequest = Request.getBaseRequest(request);
|
||||
HttpChannel httpChannel = baseRequest.getHttpChannel();
|
||||
|
@ -192,7 +194,8 @@ public final class RFC6455Handshaker implements Handshaker
|
|||
}
|
||||
|
||||
channel.setWebSocketConnection(connection);
|
||||
|
||||
if (defaultCustomizer!=null)
|
||||
defaultCustomizer.customize(channel);
|
||||
negotiator.customize(channel);
|
||||
|
||||
// send upgrade response
|
||||
|
|
|
@ -23,6 +23,7 @@ module org.eclipse.jetty.websocket.servlet
|
|||
requires javax.servlet.api;
|
||||
requires org.eclipse.jetty.util;
|
||||
requires org.eclipse.jetty.http;
|
||||
requires org.eclipse.jetty.server;
|
||||
requires org.eclipse.jetty.io;
|
||||
requires org.eclipse.jetty.servlet;
|
||||
requires org.eclipse.jetty.websocket.core;
|
||||
|
|
|
@ -21,23 +21,19 @@ package org.eclipse.jetty.websocket.servlet;
|
|||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
|
||||
/**
|
||||
* WebSocket Core API - Factory for Servlet based API's to use for creating API specific FrameHandler instances that
|
||||
* websocket-core will eventually utilize.
|
||||
* <p>
|
||||
* This is used by Servlet based APIs only.
|
||||
* </p>
|
||||
* Factory for FrameHandler instances
|
||||
*/
|
||||
public interface WebSocketServletFrameHandlerFactory
|
||||
public interface FrameHandlerFactory
|
||||
{
|
||||
String ATTR_HANDLERS = "org.eclipse.jetty.websocket.servlet.FrameHandlerFactories";
|
||||
|
||||
/**
|
||||
* Attempt to create a FrameHandler from the provided websocketPojo.
|
||||
* Create a FrameHandler from the provided websocketPojo.
|
||||
*
|
||||
* @param websocketPojo the websocket pojo to work with
|
||||
* @param upgradeRequest the Upgrade Handshake Request used to create the FrameHandler
|
||||
* @param upgradeResponse the Upgrade Handshake Response used to create the FrameHandler
|
||||
* @return the API specific FrameHandler, or null if this implementation is unable to create the FrameHandler (allowing another {@link WebSocketServletFrameHandlerFactory} to try)
|
||||
* @return the API specific FrameHandler, or null if this implementation is unable to create
|
||||
* the FrameHandler (allowing another {@link FrameHandlerFactory} to try)
|
||||
*/
|
||||
FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse);
|
||||
FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest,
|
||||
ServletUpgradeResponse upgradeResponse);
|
||||
}
|
|
@ -18,15 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.servlet;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.websocket.core.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketConstants;
|
||||
import org.eclipse.jetty.websocket.core.server.Negotiation;
|
||||
import org.eclipse.jetty.websocket.servlet.internal.UpgradeHttpServletRequest;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
@ -42,8 +33,19 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.websocket.core.ExtensionConfig;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketConstants;
|
||||
import org.eclipse.jetty.websocket.core.server.Negotiation;
|
||||
import org.eclipse.jetty.websocket.servlet.internal.UpgradeHttpServletRequest;
|
||||
|
||||
/**
|
||||
* Servlet specific Upgrade Request implementation.
|
||||
* Holder of request data for a WebSocket upgrade request.
|
||||
*/
|
||||
public class ServletUpgradeRequest
|
||||
{
|
||||
|
@ -62,7 +64,9 @@ public class ServletUpgradeRequest
|
|||
this.queryString = httpRequest.getQueryString();
|
||||
this.secure = httpRequest.isSecure();
|
||||
|
||||
// TODO why is this URL and not URI?
|
||||
StringBuffer uri = httpRequest.getRequestURL();
|
||||
// WHY?
|
||||
if (this.queryString != null)
|
||||
uri.append("?").append(this.queryString);
|
||||
uri.replace(0, uri.indexOf(":"), secure?"wss":"ws");
|
||||
|
@ -70,11 +74,18 @@ public class ServletUpgradeRequest
|
|||
this.request = new UpgradeHttpServletRequest(httpRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link X509Certificate} instance at request attribute "javax.servlet.request.X509Certificate" or null.
|
||||
*/
|
||||
public X509Certificate[] getCertificates()
|
||||
{
|
||||
return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HttpServletRequest#getCookies()
|
||||
* @return Request cookies
|
||||
*/
|
||||
public List<HttpCookie> getCookies()
|
||||
{
|
||||
if (cookies == null)
|
||||
|
@ -95,16 +106,30 @@ public class ServletUpgradeRequest
|
|||
return cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The extensions offered
|
||||
* @see Negotiation#getOfferedExtensions()
|
||||
*/
|
||||
public List<ExtensionConfig> getExtensions()
|
||||
{
|
||||
return negotiation.getOfferedExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name Header name
|
||||
* @return Header value or null
|
||||
* @see HttpServletRequest#getHeader(String)
|
||||
*/
|
||||
public String getHeader(String name)
|
||||
{
|
||||
return request.getHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name Header name
|
||||
* @return Header value as integer or -1
|
||||
* @see HttpServletRequest#getHeader(String)
|
||||
*/
|
||||
public int getHeaderInt(String name)
|
||||
{
|
||||
String val = request.getHeader(name);
|
||||
|
@ -115,43 +140,55 @@ public class ServletUpgradeRequest
|
|||
return Integer.parseInt(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Map of headers
|
||||
* @see UpgradeHttpServletRequest#getHeaders()
|
||||
*/
|
||||
public Map<String, List<String>> getHeadersMap()
|
||||
{
|
||||
return request.getHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name Header name
|
||||
* @return List of header values or null
|
||||
* @see UpgradeHttpServletRequest#getHeaders()
|
||||
*/
|
||||
public List<String> getHeaders(String name)
|
||||
{
|
||||
return request.getHeaders().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The requested host
|
||||
* @see HttpServletRequest#getRequestURL()
|
||||
*/
|
||||
public String getHost()
|
||||
{
|
||||
// TODO why is this not HttpServletRequest#getHost ?
|
||||
return requestURI.getHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying HttpServletRequest that existed at Upgrade time.
|
||||
* <p>
|
||||
* Note: many features of the HttpServletRequest are invalid when upgraded,
|
||||
* especially ones that deal with body content, streams, readers, and responses.
|
||||
*
|
||||
* @return a limited version of the underlying HttpServletRequest
|
||||
* @return Immutable version of {@link HttpServletRequest}
|
||||
*/
|
||||
public HttpServletRequest getHttpServletRequest()
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The HTTP protocol version
|
||||
* @see HttpServletRequest#getProtocol()
|
||||
*/
|
||||
public String getHttpVersion()
|
||||
{
|
||||
return request.getProtocol();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocale()}
|
||||
*
|
||||
* @return the preferred <code>Locale</code> for the client
|
||||
* @return The requested Locale
|
||||
* @see HttpServletRequest#getLocale()
|
||||
*/
|
||||
public Locale getLocale()
|
||||
{
|
||||
|
@ -159,9 +196,8 @@ public class ServletUpgradeRequest
|
|||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getLocales()}
|
||||
*
|
||||
* @return an Enumeration of preferred Locale objects
|
||||
* @return The requested Locales
|
||||
* @see HttpServletRequest#getLocales()
|
||||
*/
|
||||
public Enumeration<Locale> getLocales()
|
||||
{
|
||||
|
@ -169,11 +205,9 @@ public class ServletUpgradeRequest
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a {@link java.net.SocketAddress} for the local socket.
|
||||
* <p>
|
||||
* Warning: this can cause a DNS lookup
|
||||
*
|
||||
* @return the local socket address
|
||||
* @return The local requested address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress}
|
||||
* @see ServletRequest#getLocalAddr()
|
||||
* @see ServletRequest#getLocalPort()
|
||||
*/
|
||||
public SocketAddress getLocalSocketAddress()
|
||||
{
|
||||
|
@ -181,16 +215,27 @@ public class ServletUpgradeRequest
|
|||
return new InetSocketAddress(request.getLocalAddr(), request.getLocalPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The requested method
|
||||
* @see HttpServletRequest#getMethod()
|
||||
*/
|
||||
public String getMethod()
|
||||
{
|
||||
return request.getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The origin header value
|
||||
*/
|
||||
public String getOrigin()
|
||||
{
|
||||
return getHeader("Origin");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The request parameter map
|
||||
* @see ServletRequest#getParameterMap()
|
||||
*/
|
||||
public Map<String, List<String>> getParameterMap()
|
||||
{
|
||||
if (parameterMap == null)
|
||||
|
@ -206,6 +251,9 @@ public class ServletUpgradeRequest
|
|||
return parameterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WebSocket protocol version from "Sec-WebSocket-Version" header
|
||||
*/
|
||||
public String getProtocolVersion()
|
||||
{
|
||||
String version = request.getHeader(HttpHeader.SEC_WEBSOCKET_VERSION.asString());
|
||||
|
@ -216,26 +264,32 @@ public class ServletUpgradeRequest
|
|||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The request query string
|
||||
* @see HttpServletRequest#getQueryString()
|
||||
*/
|
||||
public String getQueryString()
|
||||
{
|
||||
return this.queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link SocketAddress} for the remote socket.
|
||||
* <p>
|
||||
* Warning: this can cause a DNS lookup
|
||||
*
|
||||
* @return the remote socket address
|
||||
* @return The remote request address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress}
|
||||
* @see ServletRequest#getRemoteAddr()
|
||||
* @see ServletRequest#getRemotePort()
|
||||
*/
|
||||
public SocketAddress getRemoteSocketAddress()
|
||||
{
|
||||
return new InetSocketAddress(request.getRemoteAddr(), request.getRemotePort());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The request URI path within the context
|
||||
*/
|
||||
public String getRequestPath()
|
||||
{
|
||||
// Since this can be called from a filter, we need to be smart about determining the target request path.
|
||||
// TODO probably better adding servletPath and pathInfo
|
||||
String contextPath = request.getContextPath();
|
||||
String requestPath = request.getRequestURI();
|
||||
if (requestPath.startsWith(contextPath))
|
||||
|
@ -243,55 +297,78 @@ public class ServletUpgradeRequest
|
|||
return requestPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The request URI
|
||||
* @see HttpServletRequest#getRequestURL()
|
||||
*/
|
||||
public URI getRequestURI()
|
||||
{
|
||||
return requestURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name Attribute name
|
||||
* @return Attribute value or null
|
||||
* @see ServletRequest#getAttribute(String)
|
||||
*/
|
||||
public Object getServletAttribute(String name)
|
||||
{
|
||||
return request.getAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Request attribute map
|
||||
* @see UpgradeHttpServletRequest#getAttributes()
|
||||
*/
|
||||
public Map<String, Object> getServletAttributes()
|
||||
{
|
||||
return request.getAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Request parameters
|
||||
* @see ServletRequest#getParameterMap()
|
||||
*/
|
||||
public Map<String, List<String>> getServletParameters()
|
||||
{
|
||||
return getParameterMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HttpSession if it exists.
|
||||
* <p>
|
||||
* Note: this is equivalent to {@link HttpServletRequest#getSession(boolean)}
|
||||
* and will not create a new HttpSession.
|
||||
* @return The HttpSession, which may be null or invalidated
|
||||
* @see HttpServletRequest#getSession(boolean)
|
||||
*/
|
||||
public HttpSession getSession()
|
||||
{
|
||||
return request.getSession(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Get WebSocket negotiation offered sub protocols
|
||||
*/
|
||||
public List<String> getSubProtocols()
|
||||
{
|
||||
return negotiation.getOfferedSubprotocols();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link HttpServletRequest#getUserPrincipal()}
|
||||
* @return The User's {@link Principal} or null
|
||||
* @see HttpServletRequest#getUserPrincipal()
|
||||
*/
|
||||
public Principal getUserPrincipal()
|
||||
{
|
||||
return request.getUserPrincipal();
|
||||
}
|
||||
|
||||
public boolean hasSubProtocol(String test)
|
||||
/**
|
||||
* @param subprotocol A sub protocol name
|
||||
* @return True if the sub protocol was offered
|
||||
*/
|
||||
public boolean hasSubProtocol(String subprotocol)
|
||||
{
|
||||
for (String protocol : getSubProtocols())
|
||||
{
|
||||
if (protocol.equalsIgnoreCase(test))
|
||||
if (protocol.equalsIgnoreCase(subprotocol))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -299,16 +376,30 @@ public class ServletUpgradeRequest
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the request is secure
|
||||
* @see ServletRequest#isSecure()
|
||||
*/
|
||||
public boolean isSecure()
|
||||
{
|
||||
return this.secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param role The user role
|
||||
* @return True if the requests user has the role
|
||||
* @see HttpServletRequest#isUserInRole(String)
|
||||
*/
|
||||
public boolean isUserInRole(String role)
|
||||
{
|
||||
return request.isUserInRole(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name Attribute name
|
||||
* @param value Attribute value to set
|
||||
* @see ServletRequest#setAttribute(String, Object)
|
||||
*/
|
||||
public void setServletAttribute(String name, Object value)
|
||||
{
|
||||
request.setAttribute(name, value);
|
||||
|
|
|
@ -23,7 +23,7 @@ package org.eclipse.jetty.websocket.servlet;
|
|||
* <p>
|
||||
* Should you desire filtering of the WebSocket object creation due to criteria such as origin or sub-protocol, then you will be required to implement a custom
|
||||
* WebSocketCreator implementation.
|
||||
* <p>
|
||||
* </p>
|
||||
*/
|
||||
public interface WebSocketCreator
|
||||
{
|
||||
|
|
|
@ -1,369 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jetty.http.pathmap.MappedResource;
|
||||
import org.eclipse.jetty.http.pathmap.PathMappings;
|
||||
import org.eclipse.jetty.http.pathmap.PathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||
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.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketConstants;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
|
||||
import org.eclipse.jetty.websocket.core.server.Negotiation;
|
||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class WebSocketCreatorMapping implements Dumpable, FrameHandler.CoreCustomizer, WebSocketServletFactory
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketCreatorMapping.class);
|
||||
private final PathMappings<CreatorNegotiator> mappings = new PathMappings<>();
|
||||
private final Set<WebSocketServletFrameHandlerFactory> frameHandlerFactories = new HashSet<>();
|
||||
private Duration defaultIdleTimeout;
|
||||
private int defaultInputBufferSize;
|
||||
private long defaultMaxBinaryMessageSize = WebSocketConstants.DEFAULT_MAX_BINARY_MESSAGE_SIZE;
|
||||
private long defaultMaxTextMessageSize = WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE;
|
||||
private long defaultMaxAllowedFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE;
|
||||
private int defaultOutputBufferSize = WebSocketConstants.DEFAULT_OUTPUT_BUFFER_SIZE;
|
||||
private boolean defaultAutoFragment = WebSocketConstants.DEFAULT_AUTO_FRAGMENT;
|
||||
private DecoratedObjectFactory objectFactory;
|
||||
private ClassLoader contextClassLoader;
|
||||
private WebSocketExtensionRegistry extensionRegistry;
|
||||
private ByteBufferPool bufferPool;
|
||||
|
||||
public WebSocketCreatorMapping()
|
||||
{
|
||||
this(new WebSocketExtensionRegistry(), new DecoratedObjectFactory(), new MappedByteBufferPool());
|
||||
}
|
||||
|
||||
public WebSocketCreatorMapping(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory, ByteBufferPool bufferPool)
|
||||
{
|
||||
this.extensionRegistry = extensionRegistry;
|
||||
this.objectFactory = objectFactory;
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually add a WebSocket mapping.
|
||||
* <p>
|
||||
* If mapping is added before this configuration is started, then it is persisted through
|
||||
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
|
||||
* this configuration is stopped.
|
||||
* </p>
|
||||
*
|
||||
* @param pathSpec the pathspec to respond on
|
||||
* @param creator the websocket creator to activate on the provided mapping.
|
||||
*/
|
||||
public void addMapping(PathSpec pathSpec, WebSocketCreator creator)
|
||||
{
|
||||
// Handling for response forbidden (and similar paths)
|
||||
// no creation, sorry
|
||||
// No factory worked!
|
||||
mappings.put(pathSpec, new CreatorNegotiator(creator));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketCreator getMapping(PathSpec pathSpec)
|
||||
{
|
||||
CreatorNegotiator cn = mappings.get(pathSpec);
|
||||
return cn == null?null:cn.getWebSocketCreator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketCreator getMatch(String target)
|
||||
{
|
||||
MappedResource<CreatorNegotiator> resource = mappings.getMatch(target);
|
||||
return resource == null?null:resource.getResource().getWebSocketCreator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeMapping(PathSpec pathSpec)
|
||||
{
|
||||
return mappings.remove(pathSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dump()
|
||||
{
|
||||
return Dumpable.dump(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
Dumpable.dumpObjects(out, indent, this, mappings);
|
||||
}
|
||||
|
||||
public ByteBufferPool getBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
}
|
||||
|
||||
public void setContextClassLoader(ClassLoader classLoader)
|
||||
{
|
||||
this.contextClassLoader = classLoader;
|
||||
}
|
||||
|
||||
public ClassLoader getContextClassloader()
|
||||
{
|
||||
return contextClassLoader;
|
||||
}
|
||||
|
||||
public Duration getDefaultIdleTimeout()
|
||||
{
|
||||
return defaultIdleTimeout;
|
||||
}
|
||||
|
||||
public WebSocketExtensionRegistry getExtensionRegistry()
|
||||
{
|
||||
return this.extensionRegistry;
|
||||
}
|
||||
|
||||
public DecoratedObjectFactory getObjectFactory()
|
||||
{
|
||||
return this.objectFactory;
|
||||
}
|
||||
|
||||
public void addFrameHandlerFactory(WebSocketServletFrameHandlerFactory webSocketServletFrameHandlerFactory)
|
||||
{
|
||||
// TODO should this be done by a ServiceLoader?
|
||||
this.frameHandlerFactories.add(webSocketServletFrameHandlerFactory);
|
||||
}
|
||||
|
||||
public void setDefaultIdleTimeout(Duration duration)
|
||||
{
|
||||
this.defaultIdleTimeout = duration;
|
||||
}
|
||||
|
||||
public int getDefaultInputBufferSize()
|
||||
{
|
||||
return defaultInputBufferSize;
|
||||
}
|
||||
|
||||
public void setDefaultInputBufferSize(int bufferSize)
|
||||
{
|
||||
this.defaultInputBufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public long getDefaultMaxAllowedFrameSize()
|
||||
{
|
||||
return this.defaultMaxAllowedFrameSize;
|
||||
}
|
||||
|
||||
public void setDefaultMaxAllowedFrameSize(long maxFrameSize)
|
||||
{
|
||||
this.defaultMaxAllowedFrameSize = maxFrameSize;
|
||||
}
|
||||
|
||||
public long getDefaultMaxBinaryMessageSize()
|
||||
{
|
||||
return defaultMaxBinaryMessageSize;
|
||||
}
|
||||
|
||||
public void setDefaultMaxBinaryMessageSize(long bufferSize)
|
||||
{
|
||||
this.defaultMaxBinaryMessageSize = bufferSize;
|
||||
}
|
||||
|
||||
public long getDefaultMaxTextMessageSize()
|
||||
{
|
||||
return defaultMaxTextMessageSize;
|
||||
}
|
||||
|
||||
public void setDefaultMaxTextMessageSize(long bufferSize)
|
||||
{
|
||||
this.defaultMaxTextMessageSize = bufferSize;
|
||||
}
|
||||
|
||||
public int getDefaultOutputBufferSize()
|
||||
{
|
||||
return this.defaultOutputBufferSize;
|
||||
}
|
||||
|
||||
public void setDefaultOutputBufferSize(int bufferSize)
|
||||
{
|
||||
this.defaultOutputBufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public boolean isAutoFragment()
|
||||
{
|
||||
return this.defaultAutoFragment;
|
||||
}
|
||||
|
||||
public void setAutoFragment(boolean autoFragment)
|
||||
{
|
||||
this.defaultAutoFragment = autoFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matching {@link MappedResource} for the provided target.
|
||||
*
|
||||
* @param target the target path
|
||||
* @return the matching resource, or null if no match.
|
||||
*/
|
||||
public WebSocketNegotiator getMatchedNegotiator(String target, Consumer<PathSpec> pathSpecConsumer)
|
||||
{
|
||||
MappedResource<CreatorNegotiator> mapping = this.mappings.getMatch(target);
|
||||
if (mapping == null)
|
||||
return null;
|
||||
|
||||
pathSpecConsumer.accept(mapping.getPathSpec());
|
||||
return mapping.getResource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a PathSpec string into a PathSpec instance.
|
||||
* <p>
|
||||
* Recognized Path Spec syntaxes:
|
||||
* </p>
|
||||
* <dl>
|
||||
* <dt><code>/path/to</code> or <code>/</code> or <code>*.ext</code> or <code>servlet|{spec}</code></dt>
|
||||
* <dd>Servlet Syntax</dd>
|
||||
* <dt><code>^{spec}</code> or <code>regex|{spec}</code></dt>
|
||||
* <dd>Regex Syntax</dd>
|
||||
* <dt><code>uri-template|{spec}</code></dt>
|
||||
* <dd>URI Template (see JSR356 and RFC6570 level 1)</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @param rawSpec the raw path spec as String to parse.
|
||||
* @return the {@link PathSpec} implementation for the rawSpec
|
||||
*/
|
||||
public PathSpec parsePathSpec(String rawSpec)
|
||||
{
|
||||
// Determine what kind of path spec we are working with
|
||||
if (rawSpec.charAt(0) == '/' || rawSpec.startsWith("*.") || rawSpec.startsWith("servlet|"))
|
||||
{
|
||||
return new ServletPathSpec(rawSpec);
|
||||
}
|
||||
else if (rawSpec.charAt(0) == '^' || rawSpec.startsWith("regex|"))
|
||||
{
|
||||
return new RegexPathSpec(rawSpec);
|
||||
}
|
||||
else if (rawSpec.startsWith("uri-template|"))
|
||||
{
|
||||
return new UriTemplatePathSpec(rawSpec.substring("uri-template|".length()));
|
||||
}
|
||||
|
||||
// TODO: add ability to load arbitrary jetty-http PathSpec implementation
|
||||
// TODO: perhaps via "fully.qualified.class.name|spec" style syntax
|
||||
|
||||
throw new IllegalArgumentException("Unrecognized path spec syntax [" + rawSpec + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(FrameHandler.CoreSession session)
|
||||
{
|
||||
session.setIdleTimeout(getDefaultIdleTimeout());
|
||||
session.setAutoFragment(isAutoFragment());
|
||||
session.setInputBufferSize(getDefaultInputBufferSize());
|
||||
session.setOutputBufferSize(getDefaultOutputBufferSize());
|
||||
session.setMaxFrameSize(getDefaultMaxAllowedFrameSize());
|
||||
}
|
||||
|
||||
private class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator
|
||||
{
|
||||
private final WebSocketCreator creator;
|
||||
|
||||
public CreatorNegotiator(WebSocketCreator creator)
|
||||
{
|
||||
super(WebSocketCreatorMapping.this.getExtensionRegistry(), WebSocketCreatorMapping.this.getObjectFactory(),
|
||||
WebSocketCreatorMapping.this.getBufferPool(),
|
||||
WebSocketCreatorMapping.this);
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
public WebSocketCreator getWebSocketCreator()
|
||||
{
|
||||
return creator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FrameHandler negotiate(Negotiation negotiation1)
|
||||
{
|
||||
return ((Function<Negotiation, FrameHandler>)negotiation ->
|
||||
{
|
||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||
try
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader(getContextClassloader());
|
||||
|
||||
ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation);
|
||||
ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation);
|
||||
|
||||
Object websocketPojo = creator.createWebSocket(upgradeRequest, upgradeResponse);
|
||||
|
||||
// Handling for response forbidden (and similar paths)
|
||||
if (upgradeResponse.isCommitted())
|
||||
return null;
|
||||
|
||||
if (websocketPojo == null)
|
||||
{
|
||||
// no creation, sorry
|
||||
upgradeResponse.sendError(SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (WebSocketServletFrameHandlerFactory factory : frameHandlerFactories)
|
||||
{
|
||||
FrameHandler frameHandler = factory.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse);
|
||||
if (frameHandler != null)
|
||||
return frameHandler;
|
||||
}
|
||||
|
||||
if (frameHandlerFactories.isEmpty())
|
||||
LOG.warn("There are no {} instances registered", WebSocketServletFrameHandlerFactory.class);
|
||||
|
||||
// No factory worked!
|
||||
return null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
throw new RuntimeIOException("Unable to negotiate websocket due to mangled request URI", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader(old);
|
||||
}
|
||||
}).apply(negotiation1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.servlet;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.pathmap.MappedResource;
|
||||
import org.eclipse.jetty.http.pathmap.PathMappings;
|
||||
import org.eclipse.jetty.http.pathmap.PathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
|
||||
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.DecoratedObjectFactory;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
|
||||
import org.eclipse.jetty.websocket.core.server.Handshaker;
|
||||
import org.eclipse.jetty.websocket.core.server.Negotiation;
|
||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||
|
||||
/**
|
||||
* Mapping of pathSpec to a tupple of {@link WebSocketCreator}, {@link FrameHandlerFactory} and
|
||||
* {@link org.eclipse.jetty.websocket.core.FrameHandler.Customizer}.
|
||||
* <p>
|
||||
* When the {@link #upgrade(HttpServletRequest, HttpServletResponse, FrameHandler.Customizer)}
|
||||
* method is called, a match for the pathSpec is looked for. If one is found then the
|
||||
* creator is used to create a POJO for the WebSocket endpoint, the factory is used to
|
||||
* wrap that POJO with a {@link FrameHandler} and the customizer is used to configure the resulting
|
||||
* {@link FrameHandler.CoreSession}.</p>
|
||||
*/
|
||||
public class WebSocketMapping implements Dumpable, LifeCycle.Listener
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketMapping.class);
|
||||
|
||||
public static WebSocketMapping ensureMapping(ServletContext servletContext) throws ServletException
|
||||
{
|
||||
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
|
||||
|
||||
// Ensure a mapping exists
|
||||
WebSocketMapping mapping = contextHandler.getBean(WebSocketMapping.class);
|
||||
if (mapping == null)
|
||||
{
|
||||
mapping = new WebSocketMapping();
|
||||
mapping.setContextClassLoader(servletContext.getClassLoader());
|
||||
contextHandler.addBean(mapping);
|
||||
contextHandler.addLifeCycleListener(mapping);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private final PathMappings<Negotiator> mappings = new PathMappings<>();
|
||||
private final Set<FrameHandlerFactory> frameHandlerFactories = new HashSet<>();
|
||||
private final Handshaker handshaker = Handshaker.newInstance();
|
||||
|
||||
private DecoratedObjectFactory objectFactory;
|
||||
private ClassLoader contextClassLoader;
|
||||
private WebSocketExtensionRegistry extensionRegistry;
|
||||
private ByteBufferPool bufferPool;
|
||||
|
||||
public WebSocketMapping()
|
||||
{
|
||||
this(new WebSocketExtensionRegistry(), new DecoratedObjectFactory(), new MappedByteBufferPool());
|
||||
}
|
||||
|
||||
public WebSocketMapping(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory, ByteBufferPool bufferPool)
|
||||
{
|
||||
this.extensionRegistry = extensionRegistry;
|
||||
this.objectFactory = objectFactory;
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lifeCycleStopping(LifeCycle context)
|
||||
{
|
||||
ContextHandler contextHandler = (ContextHandler) context;
|
||||
WebSocketMapping mapping = contextHandler.getBean(WebSocketMapping.class);
|
||||
if (mapping == this)
|
||||
{
|
||||
contextHandler.removeBean(mapping);
|
||||
mappings.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually add a WebSocket mapping.
|
||||
* <p>
|
||||
* If mapping is added before this configuration is started, then it is persisted through
|
||||
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
|
||||
* this configuration is stopped.
|
||||
* </p>
|
||||
*
|
||||
* @param pathSpec the pathspec to respond on
|
||||
* @param creator the websocket creator to activate on the provided mapping.
|
||||
* @param factory the factory to use to create a FrameHandler for the websocket
|
||||
* @param customizer the customizer to use to customize the WebSocket session.
|
||||
*/
|
||||
public void addMapping(PathSpec pathSpec, WebSocketCreator creator, FrameHandlerFactory factory, FrameHandler.Customizer customizer)
|
||||
{
|
||||
// Handling for response forbidden (and similar paths)
|
||||
// no creation, sorry
|
||||
// No factory worked!
|
||||
mappings.put(pathSpec, new Negotiator(creator, factory, customizer));
|
||||
}
|
||||
|
||||
public WebSocketCreator getMapping(PathSpec pathSpec)
|
||||
{
|
||||
Negotiator cn = mappings.get(pathSpec);
|
||||
return cn == null?null:cn.getWebSocketCreator();
|
||||
}
|
||||
|
||||
public boolean removeMapping(PathSpec pathSpec)
|
||||
{
|
||||
return mappings.remove(pathSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dump()
|
||||
{
|
||||
return Dumpable.dump(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
Dumpable.dumpObjects(out, indent, this, mappings);
|
||||
}
|
||||
|
||||
public ByteBufferPool getBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
}
|
||||
|
||||
public void setContextClassLoader(ClassLoader classLoader)
|
||||
{
|
||||
this.contextClassLoader = classLoader;
|
||||
}
|
||||
|
||||
public ClassLoader getContextClassloader()
|
||||
{
|
||||
return contextClassLoader;
|
||||
}
|
||||
|
||||
public WebSocketExtensionRegistry getExtensionRegistry()
|
||||
{
|
||||
return this.extensionRegistry;
|
||||
}
|
||||
|
||||
public DecoratedObjectFactory getObjectFactory()
|
||||
{
|
||||
return this.objectFactory;
|
||||
}
|
||||
|
||||
public void addFrameHandlerFactory(FrameHandlerFactory webSocketServletFrameHandlerFactory)
|
||||
{
|
||||
// TODO should this be done by a ServiceLoader?
|
||||
this.frameHandlerFactories.add(webSocketServletFrameHandlerFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matching {@link MappedResource} for the provided target.
|
||||
*
|
||||
* @param target the target path
|
||||
* @return the matching resource, or null if no match.
|
||||
*/
|
||||
public WebSocketNegotiator getMatchedNegotiator(String target, Consumer<PathSpec> pathSpecConsumer)
|
||||
{
|
||||
MappedResource<Negotiator> mapping = this.mappings.getMatch(target);
|
||||
if (mapping == null)
|
||||
return null;
|
||||
|
||||
pathSpecConsumer.accept(mapping.getPathSpec());
|
||||
return mapping.getResource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a PathSpec string into a PathSpec instance.
|
||||
* <p>
|
||||
* Recognized Path Spec syntaxes:
|
||||
* </p>
|
||||
* <dl>
|
||||
* <dt><code>/path/to</code> or <code>/</code> or <code>*.ext</code> or <code>servlet|{spec}</code></dt>
|
||||
* <dd>Servlet Syntax</dd>
|
||||
* <dt><code>^{spec}</code> or <code>regex|{spec}</code></dt>
|
||||
* <dd>Regex Syntax</dd>
|
||||
* <dt><code>uri-template|{spec}</code></dt>
|
||||
* <dd>URI Template (see JSR356 and RFC6570 level 1)</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @param rawSpec the raw path spec as String to parse.
|
||||
* @return the {@link PathSpec} implementation for the rawSpec
|
||||
*/
|
||||
public static PathSpec parsePathSpec(String rawSpec)
|
||||
{
|
||||
// Determine what kind of path spec we are working with
|
||||
if (rawSpec.charAt(0) == '/' || rawSpec.startsWith("*.") || rawSpec.startsWith("servlet|"))
|
||||
{
|
||||
return new ServletPathSpec(rawSpec);
|
||||
}
|
||||
else if (rawSpec.charAt(0) == '^' || rawSpec.startsWith("regex|"))
|
||||
{
|
||||
return new RegexPathSpec(rawSpec);
|
||||
}
|
||||
else if (rawSpec.startsWith("uri-template|"))
|
||||
{
|
||||
return new UriTemplatePathSpec(rawSpec.substring("uri-template|".length()));
|
||||
}
|
||||
|
||||
// TODO: add ability to load arbitrary jetty-http PathSpec implementation
|
||||
// TODO: perhaps via "fully.qualified.class.name|spec" style syntax
|
||||
|
||||
throw new IllegalArgumentException("Unrecognized path spec syntax [" + rawSpec + "]");
|
||||
}
|
||||
|
||||
public boolean upgrade(HttpServletRequest request, HttpServletResponse response, FrameHandler.Customizer defaultCustomizer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Since this may be a filter, we need to be smart about determining the target path.
|
||||
// We should rely on the Container for stripping path parameters and its ilk before
|
||||
// attempting to match a specific mapped websocket creator.
|
||||
String target = request.getServletPath();
|
||||
if (request.getPathInfo() != null)
|
||||
target = target + request.getPathInfo();
|
||||
|
||||
WebSocketNegotiator negotiator = getMatchedNegotiator(target, pathSpec ->
|
||||
{
|
||||
// Store PathSpec resource mapping as request attribute, for WebSocketCreator
|
||||
// implementors to use later if they wish
|
||||
request.setAttribute(PathSpec.class.getName(), pathSpec);
|
||||
});
|
||||
|
||||
if (negotiator == null)
|
||||
return false;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("WebSocket Negotiated detected on {} for endpoint {}", target, negotiator);
|
||||
|
||||
// We have an upgrade request
|
||||
return handshaker.upgradeRequest(negotiator, request, response, defaultCustomizer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Unable to upgrade: "+e);
|
||||
LOG.ignore(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class Negotiator extends WebSocketNegotiator.AbstractNegotiator
|
||||
{
|
||||
private final WebSocketCreator creator;
|
||||
private final FrameHandlerFactory factory;
|
||||
|
||||
public Negotiator(WebSocketCreator creator, FrameHandlerFactory factory, FrameHandler.Customizer customizer)
|
||||
{
|
||||
super(WebSocketMapping.this.getExtensionRegistry(), WebSocketMapping.this.getObjectFactory(),
|
||||
WebSocketMapping.this.getBufferPool(),
|
||||
customizer);
|
||||
this.creator = creator;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public WebSocketCreator getWebSocketCreator()
|
||||
{
|
||||
return creator;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FrameHandler negotiate(Negotiation negotiation)
|
||||
{
|
||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||
try
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader(getContextClassloader());
|
||||
|
||||
ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation);
|
||||
ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation);
|
||||
|
||||
Object websocketPojo = creator.createWebSocket(upgradeRequest, upgradeResponse);
|
||||
|
||||
// Handling for response forbidden (and similar paths)
|
||||
if (upgradeResponse.isCommitted())
|
||||
return null;
|
||||
|
||||
if (websocketPojo == null)
|
||||
{
|
||||
// no creation, sorry
|
||||
upgradeResponse.sendError(SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
|
||||
return null;
|
||||
}
|
||||
|
||||
FrameHandler frameHandler = factory.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse);
|
||||
if (frameHandler != null)
|
||||
return frameHandler;
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeIOException(e);
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
throw new RuntimeIOException("Unable to negotiate websocket due to mangled request URI", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader(old);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x{%s,%s,%s}",getClass().getSimpleName(), hashCode(), creator, factory, getCustomizer());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.servlet;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -29,19 +28,20 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.pathmap.PathSpec;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
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.server.Handshaker;
|
||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||
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.
|
||||
* <p>
|
||||
* To use this servlet, you will be required to register your websockets with the {@link WebSocketCreatorMapping} so that it can create your websockets under the
|
||||
* To use this servlet, you will be required to register your websockets with the {@link WebSocketMapping} so that it can create your websockets under the
|
||||
* appropriate conditions.
|
||||
* <p>
|
||||
* The most basic implementation would be as follows.
|
||||
* <p>
|
||||
* </p>
|
||||
* <p>The most basic implementation would be as follows:</p>
|
||||
* <pre>
|
||||
* package my.example;
|
||||
*
|
||||
|
@ -53,106 +53,97 @@ import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
|||
* @Override
|
||||
* public void configure(WebSocketServletFactory factory)
|
||||
* {
|
||||
* // set a 10 second idle timeout
|
||||
* factory.getPolicy().setIdleTimeout(10000);
|
||||
* // register my socket
|
||||
* factory.register(MyEchoSocket.class);
|
||||
* factory.setDefaultMaxFrameSize(4096);
|
||||
* factory.addMapping(factory.parsePathSpec("/"), (req,res)->new EchoSocket());
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Note: that only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketCreatorMapping} handling of creating
|
||||
* WebSockets.<br>
|
||||
* All other requests are treated as normal servlet requests.
|
||||
* <p>
|
||||
* <p>
|
||||
* <b>Configuration / Init-Parameters:</b><br>
|
||||
* Only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketMapping} handling of creating
|
||||
* WebSockets. All other requests are treated as normal servlet requests. The configuration defined by this servlet init parameters will
|
||||
* be used as the customizer for any mappings created by {@link WebSocketServletFactory#addMapping(PathSpec, WebSocketCreator)} during
|
||||
* {@link #configure(WebSocketServletFactory)} calls. The request upgrade may be peformed by this servlet, or is may be performed by a
|
||||
* {@link WebSocketUpgradeFilter} instance that will share the same {@link WebSocketMapping} instance. If the filter is used, then the
|
||||
* filter configuraton is used as the default configuration prior to this servlets configuration being applied.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Configuration / Init-Parameters:</b>
|
||||
* </p>
|
||||
* <dl>
|
||||
* <dt>maxIdleTime</dt>
|
||||
* <dd>set the time in ms that a websocket may be idle before closing<br>
|
||||
* <p>
|
||||
* <dt>maxTextMessageSize</dt>
|
||||
* <dd>set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing<br>
|
||||
* <p>
|
||||
* <dt>maxBinaryMessageSize</dt>
|
||||
* <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br>
|
||||
* <p>
|
||||
* <dt>inputBufferSize</dt>
|
||||
* <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br>
|
||||
* <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br> * <dt>outputBufferSize</dt>
|
||||
* <dd>set the size in bytes of the buffer used to write bytes to the network layer<br>
|
||||
* <dt>maxFrameSize</dt>
|
||||
* <dd>The maximum frame size sent or received.<br>
|
||||
* <dt>autoFragment</dt>
|
||||
* <dd>If true, frames are automatically fragmented to respect the maximum frame size.<br>
|
||||
* </dl>
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class WebSocketServlet extends HttpServlet
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketServlet.class);
|
||||
private WebSocketCreatorMapping factory;
|
||||
private final Handshaker handshaker = Handshaker.newInstance();
|
||||
// TODO This servlet should be split into an API neutral version and a Jetty API specific one.
|
||||
|
||||
public abstract void configure(WebSocketServletFactory factory);
|
||||
private static final Logger LOG = Log.getLogger(WebSocketServlet.class);
|
||||
private final CustomizedWebSocketServletFactory customizer = new CustomizedWebSocketServletFactory();
|
||||
|
||||
private WebSocketMapping mapping;
|
||||
|
||||
/**
|
||||
* @see javax.servlet.GenericServlet#init()
|
||||
* Configure the WebSocketServletFactory for this servlet instance by setting default
|
||||
* configuration (which may be overriden by annotations) and mapping {@link WebSocketCreator}s.
|
||||
* This method assumes a single {@link FrameHandlerFactory} will be available as a bean on the
|
||||
* {@link ContextHandler}, which in practise will mostly the the Jetty WebSocket API factory.
|
||||
* @param factory the WebSocketServletFactory
|
||||
*/
|
||||
public abstract void configure(WebSocketServletFactory factory);
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
ServletContext ctx = getServletContext();
|
||||
ServletContext servletContext = getServletContext();
|
||||
|
||||
mapping = WebSocketMapping.ensureMapping(servletContext);
|
||||
|
||||
factory = new WebSocketCreatorMapping();
|
||||
factory.setContextClassLoader(ctx.getClassLoader());
|
||||
String max = getInitParameter("maxIdleTime");
|
||||
if (max != null)
|
||||
{
|
||||
factory.setDefaultIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
|
||||
}
|
||||
customizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
|
||||
|
||||
max = getInitParameter("maxTextMessageSize");
|
||||
if (max != null)
|
||||
{
|
||||
factory.setDefaultMaxTextMessageSize(Long.parseLong(max));
|
||||
}
|
||||
customizer.setMaxTextMessageSize(Long.parseLong(max));
|
||||
|
||||
max = getInitParameter("maxBinaryMessageSize");
|
||||
if (max != null)
|
||||
{
|
||||
factory.setDefaultMaxBinaryMessageSize(Long.parseLong(max));
|
||||
}
|
||||
customizer.setMaxBinaryMessageSize(Long.parseLong(max));
|
||||
|
||||
max = getInitParameter("inputBufferSize");
|
||||
if (max != null)
|
||||
{
|
||||
factory.setDefaultInputBufferSize(Integer.parseInt(max));
|
||||
}
|
||||
customizer.setInputBufferSize(Integer.parseInt(max));
|
||||
|
||||
max = getInitParameter("outputBufferSize");
|
||||
if (max != null)
|
||||
{
|
||||
factory.setDefaultOutputBufferSize(Integer.parseInt(max));
|
||||
}
|
||||
customizer.setOutputBufferSize(Integer.parseInt(max));
|
||||
|
||||
max = getInitParameter("maxAllowedFrameSize");
|
||||
max = getInitParameter("maxFrameSize");
|
||||
if (max==null)
|
||||
max = getInitParameter("maxAllowedFrameSize");
|
||||
if (max != null)
|
||||
{
|
||||
factory.setDefaultMaxAllowedFrameSize(Long.parseLong(max));
|
||||
}
|
||||
customizer.setMaxFrameSize(Long.parseLong(max));
|
||||
|
||||
String autoFragment = getInitParameter("autoFragment");
|
||||
if (autoFragment != null)
|
||||
{
|
||||
factory.setAutoFragment(Boolean.parseBoolean(autoFragment));
|
||||
}
|
||||
customizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
|
||||
|
||||
List<WebSocketServletFrameHandlerFactory> factories = (List<WebSocketServletFrameHandlerFactory>)ctx.getAttribute(
|
||||
WebSocketServletFrameHandlerFactory.ATTR_HANDLERS);
|
||||
if (factories != null)
|
||||
factories.forEach(factory::addFrameHandlerFactory);
|
||||
|
||||
configure(factory); // Let user modify factory
|
||||
|
||||
ctx.setAttribute(WebSocketCreatorMapping.class.getName(), factory);
|
||||
configure(customizer); // Let user modify customizer prior after init params
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -160,60 +151,148 @@ public abstract class WebSocketServlet extends HttpServlet
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
// Since this is a filter, we need to be smart about determining the target path.
|
||||
// We should rely on the Container for stripping path parameters and its ilk before
|
||||
// attempting to match a specific mapped websocket creator.
|
||||
String target = request.getServletPath();
|
||||
if (request.getPathInfo() != null)
|
||||
// Often this servlet is used together with the WebSocketUpgradeFilter,
|
||||
// so upgrade requests will normally be upgraded by the filter. But we
|
||||
// can do it here as well if for some reason the filter did not match.
|
||||
if (mapping.upgrade(req, resp, null))
|
||||
return;
|
||||
|
||||
// If we reach this point, it means we had an incoming request to upgrade
|
||||
// but it was either not a proper websocket upgrade, or it was possibly rejected
|
||||
// due to incoming request constraints (controlled by WebSocketCreator)
|
||||
if (resp.isCommitted())
|
||||
return;
|
||||
|
||||
// Handle normally
|
||||
super.service(req, resp);
|
||||
}
|
||||
|
||||
|
||||
private class CustomizedWebSocketServletFactory extends FrameHandler.ConfigurationCustomizer implements WebSocketServletFactory
|
||||
{
|
||||
public WebSocketExtensionRegistry getExtensionRegistry()
|
||||
{
|
||||
target = target + request.getPathInfo();
|
||||
return mapping.getExtensionRegistry();
|
||||
}
|
||||
|
||||
WebSocketNegotiator negotiator = factory.getMatchedNegotiator(target, pathSpec ->
|
||||
@Override
|
||||
public Duration getDefaultIdleTimeout()
|
||||
{
|
||||
// Store PathSpec resource mapping as request attribute, for WebSocketCreator
|
||||
// implementors to use later if they wish
|
||||
request.setAttribute(PathSpec.class.getName(), pathSpec);
|
||||
});
|
||||
|
||||
if (negotiator != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("WebSocket Upgrade detected on {} for endpoint {}", target, negotiator);
|
||||
}
|
||||
|
||||
// Attempt to upgrade
|
||||
if (handshaker.upgradeRequest(negotiator, request, response))
|
||||
{
|
||||
// Upgrade was a success, nothing else to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reach this point, it means we had an incoming request to upgrade
|
||||
// but it was either not a proper websocket upgrade, or it was possibly rejected
|
||||
// due to incoming request constraints (controlled by WebSocketCreator)
|
||||
if (response.isCommitted())
|
||||
{
|
||||
// not much we can do at this point.
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("No match for WebSocket Upgrade at target: {}", target);
|
||||
}
|
||||
return getIdleTimeout();
|
||||
}
|
||||
|
||||
// All other processing
|
||||
super.service(request, response);
|
||||
@Override
|
||||
public void setDefaultIdleTimeout(Duration duration)
|
||||
{
|
||||
setIdleTimeout(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultInputBufferSize()
|
||||
{
|
||||
return getInputBufferSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultInputBufferSize(int bufferSize)
|
||||
{
|
||||
setInputBufferSize(bufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDefaultMaxAllowedFrameSize()
|
||||
{
|
||||
return getMaxFrameSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultMaxAllowedFrameSize(long maxFrameSize)
|
||||
{
|
||||
setMaxFrameSize(maxFrameSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDefaultMaxBinaryMessageSize()
|
||||
{
|
||||
return getMaxBinaryMessageSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultMaxBinaryMessageSize(long size)
|
||||
{
|
||||
setMaxBinaryMessageSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDefaultMaxTextMessageSize()
|
||||
{
|
||||
return getMaxTextMessageSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultMaxTextMessageSize(long size)
|
||||
{
|
||||
setMaxTextMessageSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultOutputBufferSize()
|
||||
{
|
||||
return getOutputBufferSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDefaultOutputBufferSize(int bufferSize)
|
||||
{
|
||||
setOutputBufferSize(bufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMapping(String pathSpec, WebSocketCreator creator)
|
||||
{
|
||||
addMapping(WebSocketMapping.parsePathSpec(pathSpec), creator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMapping(PathSpec pathSpec, WebSocketCreator creator)
|
||||
{
|
||||
// TODO a bit fragile. This code knows that only the JettyFHF is added directly as a been
|
||||
ServletContext servletContext = getServletContext();
|
||||
ContextHandler contextHandler = ServletContextHandler.getServletContextHandler(servletContext, "WebSocketServlet");
|
||||
FrameHandlerFactory frameHandlerFactory = contextHandler.getBean(FrameHandlerFactory.class);
|
||||
|
||||
if (frameHandlerFactory==null)
|
||||
throw new IllegalStateException("No known FrameHandlerFactory");
|
||||
|
||||
mapping.addMapping(pathSpec, creator, frameHandlerFactory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketCreator getMapping(PathSpec pathSpec)
|
||||
{
|
||||
return mapping.getMapping(pathSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketCreator getMatch(String target)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeMapping(PathSpec pathSpec)
|
||||
{
|
||||
return mapping.removeMapping(pathSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathSpec parsePathSpec(String pathSpec)
|
||||
{
|
||||
return WebSocketMapping.parsePathSpec(pathSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,26 +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
|
||||
{
|
||||
void addFrameHandlerFactory(WebSocketServletFrameHandlerFactory frameHandlerFactory);
|
||||
|
||||
/**
|
||||
* add a WebSocket mapping to a provided {@link WebSocketCreator}.
|
||||
* <p>
|
||||
* If mapping is added before this configuration is started, then it is persisted through
|
||||
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
|
||||
* this configuration is stopped.
|
||||
* </p>
|
||||
*
|
||||
* @param pathSpec the pathspec to respond on
|
||||
* @param creator the WebSocketCreator to use
|
||||
* @since 10.0
|
||||
*/
|
||||
void addMapping(PathSpec pathSpec, WebSocketCreator creator);
|
||||
WebSocketExtensionRegistry getExtensionRegistry();
|
||||
|
||||
Duration getDefaultIdleTimeout();
|
||||
|
||||
|
@ -64,6 +52,26 @@ public interface WebSocketServletFactory
|
|||
|
||||
void setDefaultOutputBufferSize(int bufferSize);
|
||||
|
||||
boolean isAutoFragment();
|
||||
|
||||
void setAutoFragment(boolean autoFragment);
|
||||
|
||||
void addMapping(String pathSpec, WebSocketCreator creator);
|
||||
|
||||
/**
|
||||
* add a WebSocket mapping to a provided {@link WebSocketCreator}.
|
||||
* <p>
|
||||
* If mapping is added before this configuration is started, then it is persisted through
|
||||
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
|
||||
* this configuration is stopped.
|
||||
* </p>
|
||||
*
|
||||
* @param pathSpec the pathspec to respond on
|
||||
* @param creator the WebSocketCreator to use
|
||||
* @since 10.0
|
||||
*/
|
||||
void addMapping(PathSpec pathSpec, WebSocketCreator creator);
|
||||
|
||||
/**
|
||||
* Returns the creator for the given path spec.
|
||||
*
|
||||
|
@ -80,9 +88,6 @@ public interface WebSocketServletFactory
|
|||
*/
|
||||
WebSocketCreator getMatch(String target);
|
||||
|
||||
boolean isAutoFragment();
|
||||
|
||||
void setAutoFragment(boolean autoFragment);
|
||||
|
||||
/**
|
||||
* Parse a PathSpec string into a PathSpec instance.
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.servlet;
|
|||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
|
@ -35,169 +34,98 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.pathmap.PathSpec;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
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.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.server.Handshaker;
|
||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||
|
||||
/**
|
||||
* Inline Servlet Filter to capture WebSocket upgrade requests.
|
||||
* <p>
|
||||
* The configuration applied to this filter via init params will be used as the the default
|
||||
* configuration of any websocket upgraded by this filter, prior to the configuration of the
|
||||
* websocket applied by the {@link WebSocketMapping}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Configuration / Init-Parameters:</b>
|
||||
* </p>
|
||||
* <dl>
|
||||
* <dt>maxIdleTime</dt>
|
||||
* <dd>set the time in ms that a websocket may be idle before closing<br>
|
||||
* <dt>maxTextMessageSize</dt>
|
||||
* <dd>set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing<br>
|
||||
* <dt>maxBinaryMessageSize</dt>
|
||||
* <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br>
|
||||
* <dt>inputBufferSize</dt>
|
||||
* <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br>
|
||||
* <dt>outputBufferSize</dt>
|
||||
* <dd>set the size in bytes of the buffer used to write bytes to the network layer<br>
|
||||
* <dt>maxFrameSize</dt>
|
||||
* <dd>The maximum frame size sent or received.<br>
|
||||
* <dt>autoFragment</dt>
|
||||
* <dd>If true, frames are automatically fragmented to respect the maximum frame size.<br>
|
||||
* </dl>
|
||||
*/
|
||||
@ManagedObject("WebSocket Upgrade Filter")
|
||||
public class WebSocketUpgradeFilter implements Filter, Dumpable
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
|
||||
public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
|
||||
|
||||
private final Handshaker handshaker = Handshaker.newInstance();
|
||||
|
||||
/**
|
||||
* Initialize the default WebSocketUpgradeFilter that the various WebSocket APIs use.
|
||||
*
|
||||
* @param context the {@link ServletContextHandler} to use
|
||||
* @throws ServletException if the filer cannot be configured
|
||||
*/
|
||||
public static void configureContext(ServletContextHandler context) throws ServletException
|
||||
public static FilterHolder ensureFilter(ServletContext servletContext) throws ServletException
|
||||
{
|
||||
WebSocketCreatorMapping factory = (WebSocketCreatorMapping)context.getAttribute(WebSocketCreatorMapping.class.getName());
|
||||
if (factory == null)
|
||||
ServletHandler servletHandler = ContextHandler.getContextHandler(servletContext).getChildHandlerByClass(ServletHandler.class);
|
||||
|
||||
for (FilterHolder holder : servletHandler.getFilters())
|
||||
{
|
||||
factory = new WebSocketCreatorMapping();
|
||||
context.setAttribute(WebSocketCreatorMapping.class.getName(), factory);
|
||||
if (holder.getClassName().equals(WebSocketUpgradeFilter.class.getName()))
|
||||
return holder;
|
||||
if (holder.getHeldClass()!=null && WebSocketUpgradeFilter.class.isAssignableFrom(holder.getHeldClass()))
|
||||
return holder;
|
||||
}
|
||||
|
||||
for (FilterHolder filterHolder : context.getServletHandler().getFilters())
|
||||
{
|
||||
// TODO does not handle extended filter classes
|
||||
if (WebSocketUpgradeFilter.class.getName().equals(filterHolder.getClassName()))
|
||||
{
|
||||
Map<String, String> initParams = filterHolder.getInitParameters();
|
||||
String key = initParams.get(CONTEXT_ATTRIBUTE_KEY);
|
||||
if (key == null || WebSocketUpgradeFilter.class.getName().equals(key))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Filter already created: {}", filterHolder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically add filter
|
||||
WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter(factory);
|
||||
filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
|
||||
|
||||
String name = "Jetty_WebSocketUpgradeFilter";
|
||||
String name = "WebSocketUpgradeFilter";
|
||||
String pathSpec = "/*";
|
||||
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
|
||||
|
||||
FilterHolder fholder = new FilterHolder(filter);
|
||||
fholder.setName(name);
|
||||
fholder.setAsyncSupported(true);
|
||||
fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY, WebSocketUpgradeFilter.class.getName());
|
||||
context.addFilter(fholder, pathSpec, dispatcherTypes);
|
||||
|
||||
FilterHolder holder = new FilterHolder(new WebSocketUpgradeFilter());
|
||||
holder.setName(name);
|
||||
holder.setAsyncSupported(true);
|
||||
servletHandler.addFilterWithMapping(holder, pathSpec, dispatcherTypes);
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Adding [{}] {} mapped to {} to {}", name, filter, pathSpec, context);
|
||||
}
|
||||
LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
|
||||
return holder;
|
||||
}
|
||||
|
||||
private WebSocketCreatorMapping factory;
|
||||
private String instanceKey;
|
||||
private boolean alreadySetToAttribute = false;
|
||||
private final FrameHandler.ConfigurationCustomizer defaultCustomizer = new FrameHandler.ConfigurationCustomizer();
|
||||
private WebSocketMapping mapping;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public WebSocketUpgradeFilter()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
public WebSocketUpgradeFilter(WebSocketCreatorMapping factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
try
|
||||
{
|
||||
alreadySetToAttribute = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpServletRequest httpreq = (HttpServletRequest)request;
|
||||
HttpServletResponse httpresp = (HttpServletResponse)response;
|
||||
HttpServletRequest httpreq = (HttpServletRequest)request;
|
||||
HttpServletResponse httpresp = (HttpServletResponse)response;
|
||||
|
||||
// Since this is a filter, we need to be smart about determining the target path.
|
||||
// We should rely on the Container for stripping path parameters and its ilk before
|
||||
// attempting to match a specific mapped websocket creator.
|
||||
String target = httpreq.getServletPath();
|
||||
if (httpreq.getPathInfo() != null)
|
||||
{
|
||||
target = target + httpreq.getPathInfo();
|
||||
}
|
||||
if (mapping.upgrade(httpreq, httpresp, defaultCustomizer))
|
||||
return;
|
||||
|
||||
WebSocketNegotiator negotiator = factory.getMatchedNegotiator(target, pathSpec ->
|
||||
{
|
||||
// Store PathSpec resource mapping as request attribute, for WebSocketCreator
|
||||
// implementors to use later if they wish
|
||||
httpreq.setAttribute(PathSpec.class.getName(), pathSpec);
|
||||
});
|
||||
// If we reach this point, it means we had an incoming request to upgrade
|
||||
// but it was either not a proper websocket upgrade, or it was possibly rejected
|
||||
// due to incoming request constraints (controlled by WebSocketCreator)
|
||||
if (response.isCommitted())
|
||||
return;
|
||||
|
||||
if (negotiator == null)
|
||||
{
|
||||
// no match.
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("WebSocket Upgrade detected on {} for endpoint {}", target, negotiator);
|
||||
}
|
||||
|
||||
// We have an upgrade request
|
||||
if (handshaker.upgradeRequest(negotiator, httpreq, httpresp))
|
||||
{
|
||||
// We have a socket instance created
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reach this point, it means we had an incoming request to upgrade
|
||||
// but it was either not a proper websocket upgrade, or it was possibly rejected
|
||||
// due to incoming request constraints (controlled by WebSocketCreator)
|
||||
if (response.isCommitted())
|
||||
{
|
||||
// not much we can do at this point.
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ClassCastException e)
|
||||
{
|
||||
// We are in some kind of funky non-http environment.
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("Not a HttpServletRequest, skipping WebSocketUpgradeFilter");
|
||||
}
|
||||
}
|
||||
|
||||
// This means we got a request that looked like an upgrade request, but
|
||||
// didn't actually upgrade, or produce an error, so process normally in the servlet chain.
|
||||
// Handle normally
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
|
@ -210,115 +138,49 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
|
|||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
Dumpable.dumpObjects(out, indent, this, factory);
|
||||
Dumpable.dumpObjects(out, indent, this, mapping);
|
||||
}
|
||||
|
||||
@ManagedAttribute(value = "factory", readonly = true)
|
||||
public WebSocketCreatorMapping getFactory()
|
||||
public WebSocketMapping getMapping()
|
||||
{
|
||||
return factory;
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig config) throws ServletException
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
factory = (WebSocketCreatorMapping)config.getServletContext().getAttribute(WebSocketCreatorMapping.class.getName());
|
||||
final ServletContext context = config.getServletContext();
|
||||
mapping = WebSocketMapping.ensureMapping(context);
|
||||
|
||||
if (factory == null)
|
||||
factory = new WebSocketCreatorMapping();
|
||||
}
|
||||
String max = config.getInitParameter("maxIdleTime");
|
||||
if (max != null)
|
||||
defaultCustomizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
|
||||
|
||||
try
|
||||
{
|
||||
final ServletContext context = config.getServletContext();
|
||||
max = config.getInitParameter("maxTextMessageSize");
|
||||
if (max != null)
|
||||
defaultCustomizer.setMaxTextMessageSize(Integer.parseInt(max));
|
||||
|
||||
factory.setContextClassLoader(context.getClassLoader());
|
||||
max = config.getInitParameter("maxBinaryMessageSize");
|
||||
if (max != null)
|
||||
defaultCustomizer.setMaxBinaryMessageSize(Integer.parseInt(max));
|
||||
|
||||
String max = config.getInitParameter("maxIdleTime");
|
||||
if (max != null)
|
||||
{
|
||||
getFactory().setDefaultIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
|
||||
}
|
||||
max = config.getInitParameter("inputBufferSize");
|
||||
if (max != null)
|
||||
defaultCustomizer.setInputBufferSize(Integer.parseInt(max));
|
||||
|
||||
max = config.getInitParameter("maxTextMessageSize");
|
||||
if (max != null)
|
||||
{
|
||||
getFactory().setDefaultMaxTextMessageSize(Integer.parseInt(max));
|
||||
}
|
||||
|
||||
max = config.getInitParameter("maxBinaryMessageSize");
|
||||
if (max != null)
|
||||
{
|
||||
getFactory().setDefaultMaxBinaryMessageSize(Integer.parseInt(max));
|
||||
}
|
||||
|
||||
max = config.getInitParameter("inputBufferSize");
|
||||
if (max != null)
|
||||
{
|
||||
getFactory().setDefaultInputBufferSize(Integer.parseInt(max));
|
||||
}
|
||||
|
||||
max = config.getInitParameter("outputBufferSize");
|
||||
if (max != null)
|
||||
{
|
||||
getFactory().setDefaultOutputBufferSize(Integer.parseInt(max));
|
||||
}
|
||||
max = config.getInitParameter("outputBufferSize");
|
||||
if (max != null)
|
||||
defaultCustomizer.setOutputBufferSize(Integer.parseInt(max));
|
||||
|
||||
max = config.getInitParameter("maxFrameSize");
|
||||
if (max == null)
|
||||
max = config.getInitParameter("maxAllowedFrameSize");
|
||||
if (max != null)
|
||||
{
|
||||
getFactory().setDefaultMaxAllowedFrameSize(Long.parseLong(max));
|
||||
}
|
||||
if (max != null)
|
||||
defaultCustomizer.setMaxFrameSize(Long.parseLong(max));
|
||||
|
||||
String autoFragment = config.getInitParameter("autoFragment");
|
||||
if (autoFragment != null)
|
||||
{
|
||||
getFactory().setAutoFragment(Boolean.parseBoolean(autoFragment));
|
||||
}
|
||||
|
||||
instanceKey = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
|
||||
if (instanceKey == null)
|
||||
{
|
||||
// assume default
|
||||
instanceKey = WebSocketUpgradeFilter.class.getName();
|
||||
}
|
||||
|
||||
// Set instance of this filter to context attribute
|
||||
setToAttribute(config.getServletContext(), instanceKey);
|
||||
}
|
||||
catch (ServletException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new ServletException(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void setToAttribute(ServletContextHandler context, String key) throws ServletException
|
||||
{
|
||||
setToAttribute(context.getServletContext(), key);
|
||||
}
|
||||
|
||||
public void setToAttribute(ServletContext context, String key) throws ServletException
|
||||
{
|
||||
if (alreadySetToAttribute)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.getAttribute(key) != null)
|
||||
{
|
||||
throw new ServletException(WebSocketUpgradeFilter.class.getName() +
|
||||
" is defined twice for the same context attribute key '" + key
|
||||
+ "'. Make sure you have different init-param '" +
|
||||
CONTEXT_ATTRIBUTE_KEY + "' values set");
|
||||
}
|
||||
|
||||
context.setAttribute(key, this);
|
||||
alreadySetToAttribute = true;
|
||||
String autoFragment = config.getInitParameter("autoFragment");
|
||||
if (autoFragment != null)
|
||||
defaultCustomizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -192,6 +192,7 @@
|
|||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.websocket</groupId>
|
||||
|
@ -202,11 +203,13 @@
|
|||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>jetty-websocket-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-servlet</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
|
|
|
@ -32,9 +32,11 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
|||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.core.FrameHandler;
|
||||
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.WebSocketMapping;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
|
||||
|
|
Loading…
Reference in New Issue