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:
Greg Wilkins 2019-01-16 09:13:42 +11:00 committed by GitHub
commit 4f65799a7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 2167 additions and 1286 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &#064;{@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);
}
}

View File

@ -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)
{

View File

@ -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)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &#64;{@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);
}
}

View File

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

View File

@ -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()
{

View File

@ -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)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,60 @@
<html>
<head>
<title>Jetty WebSocket Browser -&gt; Server Debug Tool</title>
<script type="text/javascript" src="websocket.js"></script>
<link rel="stylesheet" type="text/css" href="main.css" media="all" >
</head>
<body>
jetty websocket/browser/javascript -&gt; server debug tool #console
<div id="console"></div>
<div id="buttons">
<input id="connect" class="button" type="submit" name="connect" value="connect"/>
<input id="close" class="button" type="submit" name="close" value="close" disabled="disabled"/>
<input id="info" class="button" type="submit" name="info" value="info" disabled="disabled"/>
<input id="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>

View File

@ -0,0 +1,29 @@
body {
font-family: sans-serif;
}
div {
border: 0px solid black;
}
div#console {
clear: both;
width: 40em;
height: 20em;
overflow: auto;
background-color: #f0f0f0;
padding: 4px;
border: 1px solid black;
}
div#console .info {
color: black;
}
div#console .client {
color: blue;
}
div#console .server {
color: magenta;
}

View File

@ -0,0 +1,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);
}
};

View File

@ -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)
{

View File

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

View File

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

View File

@ -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())
{

View File

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

View File

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

View File

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

View File

@ -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())

View File

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

View File

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

View File

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

View File

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

View File

@ -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
{

View File

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

View File

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

View File

@ -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;
* &#064;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)-&gt;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);
}
}
}

View File

@ -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.

View File

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

View File

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

View File

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