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.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket; 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.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

View File

@ -97,6 +97,18 @@ public class ServletContextHandler extends ContextHandler
public final static int NO_SESSIONS=0; public final static int NO_SESSIONS=0;
public final static int NO_SECURITY=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 {}; public interface ServletContainerInitializerCaller extends LifeCycle {};
protected final DecoratedObjectFactory _objFactory; protected final DecoratedObjectFactory _objFactory;

View File

@ -116,14 +116,13 @@ public interface LifeCycle
*/ */
public interface Listener extends EventListener public interface Listener extends EventListener
{ {
public void lifeCycleStarting(LifeCycle event); default void lifeCycleStarting(LifeCycle event) {}
public void lifeCycleStarted(LifeCycle event); default void lifeCycleStarted(LifeCycle event) {}
public void lifeCycleFailure(LifeCycle event,Throwable cause); default void lifeCycleFailure(LifeCycle event,Throwable cause) {}
public void lifeCycleStopping(LifeCycle event); default void lifeCycleStopping(LifeCycle event) {}
public void lifeCycleStopped(LifeCycle event); default void lifeCycleStopped(LifeCycle event) {}
} }
/** /**
* Utility to start an object if it is a LifeCycle and to convert * Utility to start an object if it is a LifeCycle and to convert
* any exception thrown to a {@link RuntimeException} * any exception thrown to a {@link RuntimeException}

View File

@ -18,6 +18,22 @@
package org.eclipse.jetty.websocket.javax.client; 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.client.HttpClient;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.DecoratedObjectFactory; 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.JavaxWebSocketExtensionConfig;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; 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. * Container for Client use of the javax.websocket API.
* <p> * <p>
@ -52,33 +54,33 @@ import java.util.concurrent.Future;
@ManagedObject("JSR356 Client Container") @ManagedObject("JSR356 Client Container")
public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer implements javax.websocket.WebSocketContainer 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 final JavaxWebSocketClientFrameHandlerFactory frameHandlerFactory;
private ClassLoader contextClassLoader;
private DecoratedObjectFactory objectFactory; private DecoratedObjectFactory objectFactory;
private WebSocketExtensionRegistry extensionRegistry; private WebSocketExtensionRegistry extensionRegistry;
public JavaxWebSocketClientContainer() public JavaxWebSocketClientContainer()
{ {
this(new WebSocketCoreClient()); this(() -> {
this.coreClient.getHttpClient().setName("Javax-WebSocketClient@" + Integer.toHexString(this.coreClient.getHttpClient().hashCode())); WebSocketCoreClient coreClient = new WebSocketCoreClient();
// We created WebSocketCoreClient, let lifecycle be managed by us coreClient.getHttpClient().setName("Javax-WebSocketClient@" + Integer.toHexString(coreClient.getHttpClient().hashCode()));
addManaged(coreClient); return coreClient;
});
} }
public JavaxWebSocketClientContainer(HttpClient httpClient) public JavaxWebSocketClientContainer(Supplier<WebSocketCoreClient> coreClientFactory)
{ {
this(new WebSocketCoreClient(httpClient)); this((WebSocketCoreClient)null);
// We created WebSocketCoreClient, let lifecycle be managed by us this.coreClientFactory = coreClientFactory;
addManaged(coreClient); this.addBean(coreClientFactory);
} }
public JavaxWebSocketClientContainer(WebSocketCoreClient coreClient) public JavaxWebSocketClientContainer(WebSocketCoreClient coreClient)
{ {
super(); super();
this.coreClient = coreClient; this.coreClient = coreClient;
this.addBean(this.coreClient); this.addBean(coreClient);
this.contextClassLoader = this.getClass().getClassLoader();
this.objectFactory = new DecoratedObjectFactory(); this.objectFactory = new DecoratedObjectFactory();
this.extensionRegistry = new WebSocketExtensionRegistry(); this.extensionRegistry = new WebSocketExtensionRegistry();
this.frameHandlerFactory = new JavaxWebSocketClientFrameHandlerFactory(this); this.frameHandlerFactory = new JavaxWebSocketClientFrameHandlerFactory(this);
@ -98,11 +100,20 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
protected HttpClient getHttpClient() 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; return coreClient;
} }
@ -132,7 +143,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple
Objects.requireNonNull(configuredEndpoint, "WebSocket configured endpoint cannot be null"); Objects.requireNonNull(configuredEndpoint, "WebSocket configured endpoint cannot be null");
Objects.requireNonNull(destURI, "Destination URI 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(); EndpointConfig config = configuredEndpoint.getConfig();
if (config != null && config instanceof ClientEndpointConfig) if (config != null && config instanceof ClientEndpointConfig)

View File

@ -18,6 +18,25 @@
package org.eclipse.jetty.websocket.javax.common; 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.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log; 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.messages.PartialStringMessageSink;
import org.eclipse.jetty.websocket.javax.common.util.InvokerUtils; 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 public class JavaxWebSocketFrameHandler implements FrameHandler
{ {
private final Logger LOG; private final Logger LOG;
@ -217,7 +218,7 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
if (errorHandle == null) if (errorHandle == null)
{ {
LOG.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " missing onError handler", cause); LOG.warn("Unhandled Error: " + endpointInstance, cause);
return; return;
} }

View File

@ -23,9 +23,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.websocket.DeploymentException; import javax.websocket.DeploymentException;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
import javax.websocket.WebSocketContainer; import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig; 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.servlet.ServletContextHandler;
import org.eclipse.jetty.util.DecoratedObjectFactory; import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.annotation.ManagedObject; 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.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry; import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; 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.AnnotatedServerEndpointConfig;
import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketCreator; 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.javax.server.internal.UndefinedServerEndpointConfig;
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping; import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
@ManagedObject("JSR356 Server Container") @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); 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"); 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 JavaxWebSocketServerFrameHandlerFactory frameHandlerFactory;
private final Executor executor; private final Executor executor;
private final FrameHandler.ConfigurationCustomizer customizer = new FrameHandler.ConfigurationCustomizer();
private long asyncSendTimeout = -1; private long asyncSendTimeout = -1;
private List<Class<?>> deferredEndpointClasses; private List<Class<?>> deferredEndpointClasses;
private List<ServerEndpointConfig> deferredEndpointConfigs; private List<ServerEndpointConfig> deferredEndpointConfigs;
/** /**
* Main entry point for {@link JavaxWebSocketServerContainerInitializer}. * Main entry point for {@link JavaxWebSocketServerContainerInitializer}.
* * @param webSocketMapping the {@link WebSocketMapping} that this container belongs to
* @param webSocketCreatorMapping the {@link WebSocketCreatorMapping} that this container belongs to
* @param httpClient the {@link HttpClient} instance to use * @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)); super(() ->
this._webSocketCreatorMapping = webSocketCreatorMapping; {
WebSocketCoreClient client = new WebSocketCoreClient(httpClient);
if (executor != null && httpClient == null)
client.getHttpClient().setExecutor(executor);
return client;
});
this.webSocketMapping = webSocketMapping;
this.executor = executor; this.executor = executor;
this.frameHandlerFactory = new JavaxWebSocketServerFrameHandlerFactory(this); 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 @Override
public ByteBufferPool getBufferPool() public ByteBufferPool getBufferPool()
{ {
return this._webSocketCreatorMapping.getBufferPool(); return this.webSocketMapping.getBufferPool();
} }
@Override @Override
@ -110,7 +170,7 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
@Override @Override
public WebSocketExtensionRegistry getExtensionRegistry() public WebSocketExtensionRegistry getExtensionRegistry()
{ {
return this._webSocketCreatorMapping.getExtensionRegistry(); return this.webSocketMapping.getExtensionRegistry();
} }
@Override @Override
@ -122,25 +182,7 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
@Override @Override
public DecoratedObjectFactory getObjectFactory() public DecoratedObjectFactory getObjectFactory()
{ {
return this._webSocketCreatorMapping.getObjectFactory(); return this.webSocketMapping.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;
} }
@Override @Override
@ -161,14 +203,6 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
return config; 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 @Override
public void addEndpoint(Class<?> endpointClass) throws DeploymentException public void addEndpoint(Class<?> endpointClass) throws DeploymentException
{ {
@ -199,20 +233,11 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
else else
{ {
if (deferredEndpointClasses == null) if (deferredEndpointClasses == null)
{
deferredEndpointClasses = new ArrayList<>(); deferredEndpointClasses = new ArrayList<>();
}
deferredEndpointClasses.add(endpointClass); 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 @Override
public void addEndpoint(ServerEndpointConfig config) throws DeploymentException public void addEndpoint(ServerEndpointConfig config) throws DeploymentException
{ {
@ -243,8 +268,11 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
{ {
frameHandlerFactory.getMetadata(config.getEndpointClass(), config); frameHandlerFactory.getMetadata(config.getEndpointClass(), config);
JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, this._webSocketCreatorMapping.getExtensionRegistry()); JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, this.webSocketMapping
this._webSocketCreatorMapping.addMapping(new UriTemplatePathSpec(config.getPath()), creator); .getExtensionRegistry());
this.webSocketMapping
.addMapping(new UriTemplatePathSpec(config.getPath()), creator, frameHandlerFactory, customizer);
} }
@Override @Override
@ -283,20 +311,21 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
public int getDefaultMaxBinaryMessageBufferSize() public int getDefaultMaxBinaryMessageBufferSize()
{ {
// TODO: warn on long -> int conversion issue // TODO: warn on long -> int conversion issue
return (int)this._webSocketCreatorMapping.getDefaultMaxBinaryMessageSize(); // TODO: Should this be Filter?
return (int)customizer.getMaxBinaryMessageSize();
} }
@Override @Override
public long getDefaultMaxSessionIdleTimeout() public long getDefaultMaxSessionIdleTimeout()
{ {
return this._webSocketCreatorMapping.getDefaultIdleTimeout().toMillis(); return customizer.getIdleTimeout().toMillis();
} }
@Override @Override
public int getDefaultMaxTextMessageBufferSize() public int getDefaultMaxTextMessageBufferSize()
{ {
// TODO: warn on long -> int conversion issue // TODO: warn on long -> int conversion issue
return (int)this._webSocketCreatorMapping.getDefaultMaxTextMessageSize(); return (int)customizer.getMaxTextMessageSize();
} }
@Override @Override
@ -308,18 +337,18 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
@Override @Override
public void setDefaultMaxBinaryMessageBufferSize(int max) public void setDefaultMaxBinaryMessageBufferSize(int max)
{ {
this._webSocketCreatorMapping.setDefaultMaxBinaryMessageSize(max); customizer.setMaxBinaryMessageSize(max);
} }
@Override @Override
public void setDefaultMaxSessionIdleTimeout(long ms) public void setDefaultMaxSessionIdleTimeout(long ms)
{ {
this._webSocketCreatorMapping.setDefaultIdleTimeout(Duration.ofMillis(ms)); customizer.setIdleTimeout(Duration.ofMillis(ms));
} }
@Override @Override
public void setDefaultMaxTextMessageBufferSize(int max) 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.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor;
import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes; import javax.servlet.annotation.HandlesTypes;
import javax.websocket.DeploymentException; import javax.websocket.DeploymentException;
@ -34,14 +31,13 @@ import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig; import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ThreadClassLoaderScope; 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; import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
@HandlesTypes({ ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class }) @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 ENABLE_KEY = "org.eclipse.jetty.websocket.javax";
public static final String DEPRECATED_ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356"; 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"; public static final String HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.javax.HttpClient";
private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainerInitializer.class);
/**
* 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());
}
}
/** /**
* Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for * 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; return defValue;
} }
/** public static JavaxWebSocketServerContainer configureContext(ServletContextHandler context)
* Jetty Native approach. throws ServletException
* <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
{ {
WebSocketUpgradeFilter.configureContext(context); WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext());
WebSocketCreatorMapping webSocketCreatorMapping = (WebSocketCreatorMapping)context.getAttribute(WebSocketCreatorMapping.class.getName()); FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(context.getServletContext());
JavaxWebSocketServerContainer container = JavaxWebSocketServerContainer.ensureContainer(context.getServletContext());
// Find Pre-Existing (Shared?) HttpClient and/or executor if (LOG.isDebugEnabled())
HttpClient httpClient = (HttpClient)context.getServletContext().getAttribute(HTTPCLIENT_ATTRIBUTE); LOG.debug("configureContext {} {} {}",mapping,upgradeFilter,container);
if ((httpClient == null) && (context.getServer() != null)) return container;
{
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);
} }
@Override @Override
@ -211,30 +122,13 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
return; return;
} }
ContextHandler handler = ContextHandler.getContextHandler(context); JavaxWebSocketServerContainer container = configureContext(ServletContextHandler.getServletContextHandler(context,"Javax WebSocket SCI"));
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;
try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader())) try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader()))
{ {
// Create the Jetty ServerContainer implementation // 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()) if (LOG.isDebugEnabled())
{
LOG.debug("Found {} classes", c.size()); LOG.debug("Found {} classes", c.size());
}
// Now process the incoming classes // Now process the incoming classes
Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>(); Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
@ -258,9 +152,8 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs) for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{
LOG.debug("Found ServerApplicationConfig: {}", clazz); LOG.debug("Found ServerApplicationConfig: {}", clazz);
}
try try
{ {
ServerApplicationConfig config = clazz.getDeclaredConstructor().newInstance(); ServerApplicationConfig config = clazz.getDeclaredConstructor().newInstance();
@ -302,7 +195,7 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
{ {
try try
{ {
jettyContainer.addEndpoint(config); container.addEndpoint(config);
} }
catch (DeploymentException e) catch (DeploymentException e)
{ {
@ -318,7 +211,7 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
{ {
try try
{ {
jettyContainer.addEndpoint(annotatedClass); container.addEndpoint(annotatedClass);
} }
catch (DeploymentException e) 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.javax.common.JavaxWebSocketFrameHandlerMetadata;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; 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.Endpoint;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
@ -36,7 +36,7 @@ import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory implements WebSocketServletFrameHandlerFactory public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory implements FrameHandlerFactory
{ {
public JavaxWebSocketServerFrameHandlerFactory(JavaxWebSocketContainer container) 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.client.HttpClient;
import org.eclipse.jetty.util.thread.QueuedThreadPool; 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 class DummyServerContainer extends JavaxWebSocketServerContainer
{ {
public DummyServerContainer() public DummyServerContainer()
{ {
super(new WebSocketCreatorMapping(), new HttpClient(), new QueuedThreadPool()); super(new WebSocketMapping(), new HttpClient(), new QueuedThreadPool());
addBean(getHttpClient(), true); addBean(getHttpClient(), true);
addBean(getExecutor(), 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.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool; 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.core.internal.Parser;
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainerInitializer; import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainerInitializer;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator; 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.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; 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.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.websocket.ClientEndpoint; import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider; import javax.websocket.ContainerProvider;
import javax.websocket.DecodeException; 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.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders; import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainer; 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.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@ -47,7 +47,7 @@ public abstract class AbstractJavaxWebSocketServerFrameHandlerTest
context = new ServletContextHandler(); context = new ServletContextHandler();
server.setHandler(context); server.setHandler(context);
WebSocketCreatorMapping factory = new WebSocketCreatorMapping(); WebSocketMapping factory = new WebSocketMapping();
HttpClient httpClient = new HttpClient(); HttpClient httpClient = new HttpClient();
container = new JavaxWebSocketServerContainer(factory, httpClient, server.getThreadPool()); 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.InvalidOpenIntSocket;
import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenSessionIntSocket; import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenSessionIntSocket;
import org.eclipse.jetty.websocket.javax.common.util.InvalidSignatureException; 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.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@ -99,7 +99,7 @@ public class DeploymentExceptionTest
{ {
ServletContextHandler context = new ServletContextHandler(); ServletContextHandler context = new ServletContextHandler();
WebSocketCreatorMapping factory = new WebSocketCreatorMapping(); WebSocketMapping factory = new WebSocketMapping();
HttpClient httpClient = new HttpClient(); HttpClient httpClient = new HttpClient();
JavaxWebSocketServerContainer container = new JavaxWebSocketServerContainer(factory, httpClient, server.getThreadPool()); JavaxWebSocketServerContainer container = new JavaxWebSocketServerContainer(factory, httpClient, server.getThreadPool());

View File

@ -18,11 +18,14 @@
package org.eclipse.jetty.websocket.javax.tests.server.sockets; package org.eclipse.jetty.websocket.javax.tests.server.sockets;
import javax.websocket.OnError;
import javax.websocket.OnMessage; import javax.websocket.OnMessage;
import javax.websocket.OnOpen; import javax.websocket.OnOpen;
import javax.websocket.Session; import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.core.WebSocketTimeoutException;
@ServerEndpoint(value = "/idle-onopen-socket") @ServerEndpoint(value = "/idle-onopen-socket")
public class IdleTimeoutOnOpenSocket public class IdleTimeoutOnOpenSocket
{ {
@ -37,4 +40,11 @@ public class IdleTimeoutOnOpenSocket
{ {
return msg; 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 UpgradeResponse upgradeResponse;
private final CompletableFuture<Session> futureSession; private final CompletableFuture<Session> futureSession;
private final CoreCustomizer customizer; private final Customizer customizer;
private MessageSink textSink; private MessageSink textSink;
private MessageSink binarySink; private MessageSink binarySink;
private MessageSink activeMessageSink; private MessageSink activeMessageSink;
private WebSocketSessionImpl session; private WebSocketSessionImpl session;
private long maxBinaryMessageSize = -1;
private long maxTextMessageSize = -1;
public JettyWebSocketFrameHandler(Executor executor, public JettyWebSocketFrameHandler(Executor executor,
Object endpointInstance, Object endpointInstance,
@ -85,7 +83,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler
MethodHandle frameHandle, MethodHandle frameHandle,
MethodHandle pingHandle, MethodHandle pongHandle, MethodHandle pingHandle, MethodHandle pongHandle,
CompletableFuture<Session> futureSession, CompletableFuture<Session> futureSession,
CoreCustomizer customizer) Customizer customizer)
{ {
this.log = Log.getLogger(endpointInstance.getClass()); this.log = Log.getLogger(endpointInstance.getClass());
@ -130,7 +128,9 @@ public class JettyWebSocketFrameHandler implements FrameHandler
if (errorHandle == null) 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; return;
} }
@ -228,14 +228,10 @@ public class JettyWebSocketFrameHandler implements FrameHandler
pongHandle = JettyWebSocketFrameHandlerFactory.bindTo(pongHandle, session); pongHandle = JettyWebSocketFrameHandlerFactory.bindTo(pongHandle, session);
if (textHandle != null) if (textHandle != null)
{ textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, coreSession.getMaxTextMessageSize());
textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, getMaxTextMessageSize());
}
if (binaryHandle != null) if (binaryHandle != null)
{ binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, coreSession.getMaxBinaryMessageSize());
binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, getMaxBinaryMessageSize());
}
if (openHandle != null) if (openHandle != null)
{ {
@ -252,26 +248,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler
futureSession.complete(session); 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() public String toString()
{ {
return String.format("%s@%x[%s]", this.getClass().getSimpleName(), this.hashCode(), endpointInstance.getClass().getName()); 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; 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.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest; 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.message.StringMessageSink;
import org.eclipse.jetty.websocket.common.util.ReflectUtils; import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.core.Frame; 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. * use with jetty-native websocket API.
* <p> * <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> * </p>
* <ul> * <ul>
* <li>Is &#64;{@link org.eclipse.jetty.websocket.api.annotations.WebSocket} annotated</li> * <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> * <li>Implements {@link org.eclipse.jetty.websocket.api.WebSocketFrameListener}</li>
* </ul> * </ul>
*/ */
public class JettyWebSocketFrameHandlerFactory public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
{ {
private final Executor executor; private final Executor executor;
private Map<Class<?>, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>(); private Map<Class<?>, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>();
@ -85,6 +87,7 @@ public class JettyWebSocketFrameHandlerFactory
public JettyWebSocketFrameHandlerFactory(Executor executor) public JettyWebSocketFrameHandlerFactory(Executor executor)
{ {
this.executor = executor; this.executor = executor;
addBean(executor);
} }
public JettyWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass) public JettyWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass)
@ -156,13 +159,6 @@ public class JettyWebSocketFrameHandlerFactory
future, future,
metadata); 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; return frameHandler;
} }
@ -298,10 +294,14 @@ public class JettyWebSocketFrameHandlerFactory
{ {
JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata();
metadata.setInputBufferSize(anno.inputBufferSize()); if (anno.inputBufferSize()>=0)
metadata.setMaxBinaryMessageSize(anno.maxBinaryMessageSize()); metadata.setInputBufferSize(anno.inputBufferSize());
if (anno.maxBinaryMessageSize()>=0)
metadata.setMaxBinaryMessageSize(anno.maxBinaryMessageSize());
if (anno.maxTextMessageSize()>=0)
metadata.setMaxTextMessageSize(anno.maxTextMessageSize()); metadata.setMaxTextMessageSize(anno.maxTextMessageSize());
metadata.setIdleTimeout(anno.maxIdleTime()); if (anno.maxIdleTime()>=0)
metadata.setIdleTimeout(Duration.ofMillis(anno.maxIdleTime()));
metadata.setBatchMode(anno.batchMode()); metadata.setBatchMode(anno.batchMode());
Method onmethod; Method onmethod;
@ -486,4 +486,9 @@ public class JettyWebSocketFrameHandlerFactory
throw new InvalidSignatureException(err.toString()); 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.lang.invoke.MethodHandle;
import java.time.Duration; import java.time.Duration;
public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCustomizer public class JettyWebSocketFrameHandlerMetadata extends FrameHandler.ConfigurationCustomizer
{ {
private MethodHandle openHandle; private MethodHandle openHandle;
private MethodHandle closeHandle; private MethodHandle closeHandle;
@ -41,12 +41,6 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
private MethodHandle pingHandle; private MethodHandle pingHandle;
private MethodHandle pongHandle; private MethodHandle pongHandle;
// Policy Configuration
private int idleTimeout = -1;
private int inputBufferSize = -1;
private int maxBinaryMessageSize = -1;
private int maxTextMessageSize = -1;
// Batch Configuration // Batch Configuration
// TODO remove? // TODO remove?
private BatchMode batchMode = BatchMode.OFF; private BatchMode batchMode = BatchMode.OFF;
@ -111,46 +105,6 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
return frameHandle; 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) public void setOpenHandler(MethodHandle open, Object origin)
{ {
assertNotSet(this.openHandle, "OPEN Handler", origin); assertNotSet(this.openHandle, "OPEN Handler", origin);
@ -226,16 +180,4 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
return obj.toString(); 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; package org.eclipse.jetty.websocket.common;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.websocket.api.CloseStatus; import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.SuspendToken;
@ -26,11 +27,12 @@ import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import java.io.IOException;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.time.Duration; import java.time.Duration;
import java.util.Objects; import java.util.Objects;
public class WebSocketSessionImpl implements Session public class WebSocketSessionImpl implements Session, Dumpable
{ {
private final FrameHandler.CoreSession coreSession; private final FrameHandler.CoreSession coreSession;
private final JettyWebSocketFrameHandler frameHandler; private final JettyWebSocketFrameHandler frameHandler;
@ -104,13 +106,13 @@ public class WebSocketSessionImpl implements Session
@Override @Override
public long getMaxBinaryMessageSize() public long getMaxBinaryMessageSize()
{ {
return frameHandler.getMaxBinaryMessageSize(); return coreSession.getMaxBinaryMessageSize();
} }
@Override @Override
public long getMaxTextMessageSize() public long getMaxTextMessageSize()
{ {
return frameHandler.getMaxTextMessageSize(); return coreSession.getMaxTextMessageSize();
} }
@Override @Override
@ -134,13 +136,13 @@ public class WebSocketSessionImpl implements Session
@Override @Override
public void setMaxBinaryMessageSize(long size) public void setMaxBinaryMessageSize(long size)
{ {
frameHandler.setMaxBinaryMessageSize(size); coreSession.setMaxBinaryMessageSize(size);
} }
@Override @Override
public void setMaxTextMessageSize(long size) public void setMaxTextMessageSize(long size)
{ {
frameHandler.setMaxTextMessageSize(size); coreSession.setMaxTextMessageSize(size);
} }
@Override @Override
@ -204,6 +206,21 @@ public class WebSocketSessionImpl implements Session
return null; return null;
} }
@Override
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this, upgradeRequest, coreSession, remoteEndpoint, frameHandler);
}
@Override
public String dumpSelf()
{
return String.format("%s@%x[behavior=%s,idleTimeout=%dms]",
this.getClass().getSimpleName(), hashCode(),
getPolicy().getBehavior(),
getIdleTimeout().toMillis());
}
@Override @Override
public String toString() 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 * Might need to drop calling args and/or reorder the calling args to fit
* the actual method being called. * the actual method being called.
* </p> * </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) public static MethodHandle mutatedInvoker(Class<?> targetClass, Method method, Arg... callingArgs)
{ {

View File

@ -32,6 +32,12 @@
<artifactId>jetty-server</artifactId> <artifactId>jetty-server</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.websocket</groupId> <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-api</artifactId> <artifactId>jetty-websocket-api</artifactId>
@ -51,6 +57,11 @@
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -28,6 +28,9 @@ module org.eclipse.jetty.websocket.jetty.server
requires javax.servlet.api; requires javax.servlet.api;
requires org.eclipse.jetty.util; 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.servlet;
requires org.eclipse.jetty.webapp; requires org.eclipse.jetty.webapp;
requires org.eclipse.jetty.websocket.jetty.api; 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; 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.servlet.ServletContextHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle; 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.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.ServletContainerInitializer;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
@ -37,6 +43,8 @@ import java.util.concurrent.Executor;
*/ */
public class JettyWebSocketServletContainerInitializer implements ServletContainerInitializer public class JettyWebSocketServletContainerInitializer implements ServletContainerInitializer
{ {
private static final Logger LOG = Log.getLogger(JettyWebSocketServletContainerInitializer.class);
public static class JettyWebSocketEmbeddedStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller public static class JettyWebSocketEmbeddedStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller
{ {
private ServletContainerInitializer sci; private ServletContainerInitializer sci;
@ -70,33 +78,13 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain
} }
@Override @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? WebSocketMapping mapping = WebSocketMapping.ensureMapping(servletContext);
List<WebSocketServletFrameHandlerFactory> factories = (List<WebSocketServletFrameHandlerFactory>)ctx FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(servletContext);
.getAttribute(WebSocketServletFrameHandlerFactory.ATTR_HANDLERS); JettyServerFrameHandlerFactory factory = JettyServerFrameHandlerFactory.ensureFactory(servletContext);
if (factories == null)
{
factories = new ArrayList<>();
ctx.setAttribute(WebSocketServletFrameHandlerFactory.ATTR_HANDLERS, factories);
}
Executor executor = (Executor)ctx.getAttribute("org.eclipse.jetty.server.Executor"); if (LOG.isDebugEnabled())
if (executor == null) LOG.debug("onStartup {} {} {}",mapping, upgradeFilter, factory);
{
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));
} }
} }

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; import org.eclipse.jetty.websocket.core.internal.WebSocketChannel;
@ManagedObject("Abstract Extension") @ManagedObject("Abstract Extension")
public abstract class AbstractExtension implements Extension, Dumpable public abstract class AbstractExtension implements Extension
{ {
private final Logger log; private final Logger log;
private ByteBufferPool bufferPool; private ByteBufferPool bufferPool;
@ -44,27 +44,6 @@ public abstract class AbstractExtension implements Extension, Dumpable
log = Log.getLogger(this.getClass()); log = Log.getLogger(this.getClass());
} }
@Override
public String dump()
{
return Dumpable.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
// incoming
dumpWithHeading(out, indent, "incoming", this.nextIncoming);
dumpWithHeading(out, indent, "outgoing", this.nextOutgoing);
}
protected void dumpWithHeading(Appendable out, String indent, String heading, Object bean) throws IOException
{
out.append(indent).append(" +- ");
out.append(heading).append(" : ");
out.append(bean.toString());
}
@Override @Override
public void init(ExtensionConfig config, ByteBufferPool bufferPool) public void init(ExtensionConfig config, ByteBufferPool bufferPool)
{ {

View File

@ -125,10 +125,53 @@ public interface FrameHandler extends IncomingFrames
return false; 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. * Represents the outgoing Frames.
*/ */
interface CoreSession extends OutgoingFrames interface CoreSession extends OutgoingFrames, Configuration
{ {
/** /**
* The negotiated WebSocket Sub-Protocol for this channel. * The negotiated WebSocket Sub-Protocol for this channel.
@ -229,20 +272,6 @@ public interface FrameHandler extends IncomingFrames
*/ */
boolean isOpen(); 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. * 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); 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 class Empty implements CoreSession
{ {
@Override @Override
@ -375,31 +388,26 @@ public interface FrameHandler extends IncomingFrames
@Override @Override
public void setIdleTimeout(Duration timeout) public void setIdleTimeout(Duration timeout)
{ {
} }
@Override @Override
public void flush(Callback callback) public void flush(Callback callback)
{ {
} }
@Override @Override
public void close(Callback callback) public void close(Callback callback)
{ {
} }
@Override @Override
public void close(int statusCode, String reason, Callback callback) public void close(int statusCode, String reason, Callback callback)
{ {
} }
@Override @Override
public void demand(long n) public void demand(long n)
{ {
} }
@Override @Override
@ -411,7 +419,6 @@ public interface FrameHandler extends IncomingFrames
@Override @Override
public void setAutoFragment(boolean autoFragment) public void setAutoFragment(boolean autoFragment)
{ {
} }
@Override @Override
@ -423,7 +430,6 @@ public interface FrameHandler extends IncomingFrames
@Override @Override
public void setMaxFrameSize(long maxFrameSize) public void setMaxFrameSize(long maxFrameSize)
{ {
} }
@Override @Override
@ -435,7 +441,6 @@ public interface FrameHandler extends IncomingFrames
@Override @Override
public void setOutputBufferSize(int outputBufferSize) public void setOutputBufferSize(int outputBufferSize)
{ {
} }
@Override @Override
@ -447,19 +452,154 @@ public interface FrameHandler extends IncomingFrames
@Override @Override
public void setInputBufferSize(int inputBufferSize) public void setInputBufferSize(int inputBufferSize)
{ {
} }
@Override @Override
public void sendFrame(Frame frame, Callback callback, boolean batch) 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); 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; 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.client.HttpClient;
import org.eclipse.jetty.util.DecoratedObjectFactory; import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.component.ContainerLifeCycle; 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.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry; import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import java.io.IOException; public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHandler.Customizer
import java.net.URI;
import java.util.concurrent.CompletableFuture;
public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHandler.CoreCustomizer
{ {
private static final Logger LOG = Log.getLogger(WebSocketCoreClient.class); private static final Logger LOG = Log.getLogger(WebSocketCoreClient.class);
private final HttpClient httpClient; private final HttpClient httpClient;
private WebSocketExtensionRegistry extensionRegistry; private WebSocketExtensionRegistry extensionRegistry;
private DecoratedObjectFactory objectFactory; 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) // TODO: Things to consider for inclusion in this class (or removal if they can be set elsewhere, like HttpClient)
// - AsyncWrite Idle Timeout // - AsyncWrite Idle Timeout
@ -51,12 +51,7 @@ public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHand
public WebSocketCoreClient() public WebSocketCoreClient()
{ {
this(new HttpClient(new SslContextFactory())); this(null,null);
// 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);
} }
public WebSocketCoreClient(HttpClient httpClient) public WebSocketCoreClient(HttpClient httpClient)
@ -64,9 +59,15 @@ public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHand
this(httpClient, null); 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.extensionRegistry = new WebSocketExtensionRegistry();
this.objectFactory = new DecoratedObjectFactory(); this.objectFactory = new DecoratedObjectFactory();
this.customizer = customizer; this.customizer = customizer;

View File

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

View File

@ -70,6 +70,8 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
private long maxFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE; private long maxFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE;
private int inputBufferSize = WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE; private int inputBufferSize = WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE;
private int outputBufferSize = WebSocketConstants.DEFAULT_OUTPUT_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, public WebSocketChannel(FrameHandler handler,
Behavior behavior, Behavior behavior,
@ -576,6 +578,30 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
this.inputBufferSize = inputBufferSize; 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 private class IncomingState extends FrameSequence implements IncomingFrames
{ {
@Override @Override
@ -672,7 +698,7 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
public void dump(Appendable out, String indent) throws IOException public void dump(Appendable out, String indent) throws IOException
{ {
Dumpable.dumpObjects(out, indent, this, Dumpable.dumpObjects(out, indent, this,
negotiated.getSubProtocol(), "subprotocol="+negotiated.getSubProtocol(),
negotiated.getExtensions(), negotiated.getExtensions(),
handler); handler);
} }

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.core.server; package org.eclipse.jetty.websocket.core.server;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.internal.RFC6455Handshaker; import org.eclipse.jetty.websocket.core.server.internal.RFC6455Handshaker;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -31,5 +32,10 @@ public interface Handshaker
return new RFC6455Handshaker(); 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.io.IOException;
import java.util.function.Function; import java.util.function.Function;
public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer public interface WebSocketNegotiator extends FrameHandler.Customizer
{ {
FrameHandler negotiate(Negotiation negotiation) throws IOException; 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) return new AbstractNegotiator(null, null, null, customizer)
{ {
@ -65,7 +65,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
WebSocketExtensionRegistry extensionRegistry, WebSocketExtensionRegistry extensionRegistry,
DecoratedObjectFactory objectFactory, DecoratedObjectFactory objectFactory,
ByteBufferPool bufferPool, ByteBufferPool bufferPool,
FrameHandler.CoreCustomizer customizer) FrameHandler.Customizer customizer)
{ {
return new AbstractNegotiator(extensionRegistry, objectFactory, bufferPool, customizer) return new AbstractNegotiator(extensionRegistry, objectFactory, bufferPool, customizer)
{ {
@ -82,7 +82,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
final WebSocketExtensionRegistry extensionRegistry; final WebSocketExtensionRegistry extensionRegistry;
final DecoratedObjectFactory objectFactory; final DecoratedObjectFactory objectFactory;
final ByteBufferPool bufferPool; final ByteBufferPool bufferPool;
final FrameHandler.CoreCustomizer customizer; final FrameHandler.Customizer customizer;
public AbstractNegotiator() public AbstractNegotiator()
{ {
@ -93,7 +93,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
WebSocketExtensionRegistry extensionRegistry, WebSocketExtensionRegistry extensionRegistry,
DecoratedObjectFactory objectFactory, DecoratedObjectFactory objectFactory,
ByteBufferPool bufferPool, ByteBufferPool bufferPool,
FrameHandler.CoreCustomizer customizer) FrameHandler.Customizer customizer)
{ {
this.extensionRegistry = extensionRegistry == null?new WebSocketExtensionRegistry():extensionRegistry; this.extensionRegistry = extensionRegistry == null?new WebSocketExtensionRegistry():extensionRegistry;
this.objectFactory = objectFactory == null?new DecoratedObjectFactory():objectFactory; this.objectFactory = objectFactory == null?new DecoratedObjectFactory():objectFactory;
@ -125,5 +125,10 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
{ {
return bufferPool; return bufferPool;
} }
public FrameHandler.Customizer getCustomizer()
{
return customizer;
}
} }
} }

View File

@ -74,7 +74,7 @@ public class WebSocketUpgradeHandler extends HandlerWrapper
return; return;
} }
if (handshaker.upgradeRequest(negotiator, request, response)) if (handshaker.upgradeRequest(negotiator, request, response, null))
return; return;
if (!baseRequest.isHandled()) 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 CONNECTION_UPGRADE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeader.UPGRADE.asString());
private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION); 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); Request baseRequest = Request.getBaseRequest(request);
HttpChannel httpChannel = baseRequest.getHttpChannel(); HttpChannel httpChannel = baseRequest.getHttpChannel();
@ -192,7 +194,8 @@ public final class RFC6455Handshaker implements Handshaker
} }
channel.setWebSocketConnection(connection); channel.setWebSocketConnection(connection);
if (defaultCustomizer!=null)
defaultCustomizer.customize(channel);
negotiator.customize(channel); negotiator.customize(channel);
// send upgrade response // send upgrade response

View File

@ -23,6 +23,7 @@ module org.eclipse.jetty.websocket.servlet
requires javax.servlet.api; requires javax.servlet.api;
requires org.eclipse.jetty.util; requires org.eclipse.jetty.util;
requires org.eclipse.jetty.http; requires org.eclipse.jetty.http;
requires org.eclipse.jetty.server;
requires org.eclipse.jetty.io; requires org.eclipse.jetty.io;
requires org.eclipse.jetty.servlet; requires org.eclipse.jetty.servlet;
requires org.eclipse.jetty.websocket.core; 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; 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 * Factory for FrameHandler instances
* websocket-core will eventually utilize.
* <p>
* This is used by Servlet based APIs only.
* </p>
*/ */
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 websocketPojo the websocket pojo to work with
* @param upgradeRequest the Upgrade Handshake Request used to create the FrameHandler * @param upgradeRequest the Upgrade Handshake Request used to create the FrameHandler
* @param upgradeResponse the Upgrade Handshake Response 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; 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.HttpCookie;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
@ -42,8 +33,19 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; 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 public class ServletUpgradeRequest
{ {
@ -62,7 +64,9 @@ public class ServletUpgradeRequest
this.queryString = httpRequest.getQueryString(); this.queryString = httpRequest.getQueryString();
this.secure = httpRequest.isSecure(); this.secure = httpRequest.isSecure();
// TODO why is this URL and not URI?
StringBuffer uri = httpRequest.getRequestURL(); StringBuffer uri = httpRequest.getRequestURL();
// WHY?
if (this.queryString != null) if (this.queryString != null)
uri.append("?").append(this.queryString); uri.append("?").append(this.queryString);
uri.replace(0, uri.indexOf(":"), secure?"wss":"ws"); uri.replace(0, uri.indexOf(":"), secure?"wss":"ws");
@ -70,11 +74,18 @@ public class ServletUpgradeRequest
this.request = new UpgradeHttpServletRequest(httpRequest); this.request = new UpgradeHttpServletRequest(httpRequest);
} }
/**
* @return The {@link X509Certificate} instance at request attribute "javax.servlet.request.X509Certificate" or null.
*/
public X509Certificate[] getCertificates() public X509Certificate[] getCertificates()
{ {
return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate"); return (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
} }
/**
* @see HttpServletRequest#getCookies()
* @return Request cookies
*/
public List<HttpCookie> getCookies() public List<HttpCookie> getCookies()
{ {
if (cookies == null) if (cookies == null)
@ -95,16 +106,30 @@ public class ServletUpgradeRequest
return cookies; return cookies;
} }
/**
* @return The extensions offered
* @see Negotiation#getOfferedExtensions()
*/
public List<ExtensionConfig> getExtensions() public List<ExtensionConfig> getExtensions()
{ {
return negotiation.getOfferedExtensions(); return negotiation.getOfferedExtensions();
} }
/**
* @param name Header name
* @return Header value or null
* @see HttpServletRequest#getHeader(String)
*/
public String getHeader(String name) public String getHeader(String name)
{ {
return request.getHeader(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) public int getHeaderInt(String name)
{ {
String val = request.getHeader(name); String val = request.getHeader(name);
@ -115,43 +140,55 @@ public class ServletUpgradeRequest
return Integer.parseInt(val); return Integer.parseInt(val);
} }
/**
* @return Map of headers
* @see UpgradeHttpServletRequest#getHeaders()
*/
public Map<String, List<String>> getHeadersMap() public Map<String, List<String>> getHeadersMap()
{ {
return request.getHeaders(); return request.getHeaders();
} }
/**
* @param name Header name
* @return List of header values or null
* @see UpgradeHttpServletRequest#getHeaders()
*/
public List<String> getHeaders(String name) public List<String> getHeaders(String name)
{ {
return request.getHeaders().get(name); return request.getHeaders().get(name);
} }
/**
* @return The requested host
* @see HttpServletRequest#getRequestURL()
*/
public String getHost() public String getHost()
{ {
// TODO why is this not HttpServletRequest#getHost ?
return requestURI.getHost(); return requestURI.getHost();
} }
/** /**
* Return the underlying HttpServletRequest that existed at Upgrade time. * @return Immutable version of {@link HttpServletRequest}
* <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
*/ */
public HttpServletRequest getHttpServletRequest() public HttpServletRequest getHttpServletRequest()
{ {
return request; return request;
} }
/**
* @return The HTTP protocol version
* @see HttpServletRequest#getProtocol()
*/
public String getHttpVersion() public String getHttpVersion()
{ {
return request.getProtocol(); return request.getProtocol();
} }
/** /**
* Equivalent to {@link HttpServletRequest#getLocale()} * @return The requested Locale
* * @see HttpServletRequest#getLocale()
* @return the preferred <code>Locale</code> for the client
*/ */
public Locale getLocale() public Locale getLocale()
{ {
@ -159,9 +196,8 @@ public class ServletUpgradeRequest
} }
/** /**
* Equivalent to {@link HttpServletRequest#getLocales()} * @return The requested Locales
* * @see HttpServletRequest#getLocales()
* @return an Enumeration of preferred Locale objects
*/ */
public Enumeration<Locale> getLocales() public Enumeration<Locale> getLocales()
{ {
@ -169,11 +205,9 @@ public class ServletUpgradeRequest
} }
/** /**
* Return a {@link java.net.SocketAddress} for the local socket. * @return The local requested address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress}
* <p> * @see ServletRequest#getLocalAddr()
* Warning: this can cause a DNS lookup * @see ServletRequest#getLocalPort()
*
* @return the local socket address
*/ */
public SocketAddress getLocalSocketAddress() public SocketAddress getLocalSocketAddress()
{ {
@ -181,16 +215,27 @@ public class ServletUpgradeRequest
return new InetSocketAddress(request.getLocalAddr(), request.getLocalPort()); return new InetSocketAddress(request.getLocalAddr(), request.getLocalPort());
} }
/**
* @return The requested method
* @see HttpServletRequest#getMethod()
*/
public String getMethod() public String getMethod()
{ {
return request.getMethod(); return request.getMethod();
} }
/**
* @return The origin header value
*/
public String getOrigin() public String getOrigin()
{ {
return getHeader("Origin"); return getHeader("Origin");
} }
/**
* @return The request parameter map
* @see ServletRequest#getParameterMap()
*/
public Map<String, List<String>> getParameterMap() public Map<String, List<String>> getParameterMap()
{ {
if (parameterMap == null) if (parameterMap == null)
@ -206,6 +251,9 @@ public class ServletUpgradeRequest
return parameterMap; return parameterMap;
} }
/**
* @return WebSocket protocol version from "Sec-WebSocket-Version" header
*/
public String getProtocolVersion() public String getProtocolVersion()
{ {
String version = request.getHeader(HttpHeader.SEC_WEBSOCKET_VERSION.asString()); String version = request.getHeader(HttpHeader.SEC_WEBSOCKET_VERSION.asString());
@ -216,26 +264,32 @@ public class ServletUpgradeRequest
return version; return version;
} }
/**
* @return The request query string
* @see HttpServletRequest#getQueryString()
*/
public String getQueryString() public String getQueryString()
{ {
return this.queryString; return this.queryString;
} }
/** /**
* Return a {@link SocketAddress} for the remote socket. * @return The remote request address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress}
* <p> * @see ServletRequest#getRemoteAddr()
* Warning: this can cause a DNS lookup * @see ServletRequest#getRemotePort()
*
* @return the remote socket address
*/ */
public SocketAddress getRemoteSocketAddress() public SocketAddress getRemoteSocketAddress()
{ {
return new InetSocketAddress(request.getRemoteAddr(), request.getRemotePort()); return new InetSocketAddress(request.getRemoteAddr(), request.getRemotePort());
} }
/**
* @return The request URI path within the context
*/
public String getRequestPath() public String getRequestPath()
{ {
// Since this can be called from a filter, we need to be smart about determining the target request path. // 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 contextPath = request.getContextPath();
String requestPath = request.getRequestURI(); String requestPath = request.getRequestURI();
if (requestPath.startsWith(contextPath)) if (requestPath.startsWith(contextPath))
@ -243,55 +297,78 @@ public class ServletUpgradeRequest
return requestPath; return requestPath;
} }
/**
* @return The request URI
* @see HttpServletRequest#getRequestURL()
*/
public URI getRequestURI() public URI getRequestURI()
{ {
return requestURI; return requestURI;
} }
/**
* @param name Attribute name
* @return Attribute value or null
* @see ServletRequest#getAttribute(String)
*/
public Object getServletAttribute(String name) public Object getServletAttribute(String name)
{ {
return request.getAttribute(name); return request.getAttribute(name);
} }
/**
* @return Request attribute map
* @see UpgradeHttpServletRequest#getAttributes()
*/
public Map<String, Object> getServletAttributes() public Map<String, Object> getServletAttributes()
{ {
return request.getAttributes(); return request.getAttributes();
} }
/**
* @return Request parameters
* @see ServletRequest#getParameterMap()
*/
public Map<String, List<String>> getServletParameters() public Map<String, List<String>> getServletParameters()
{ {
return getParameterMap(); return getParameterMap();
} }
/** /**
* Return the HttpSession if it exists. * @return The HttpSession, which may be null or invalidated
* <p> * @see HttpServletRequest#getSession(boolean)
* Note: this is equivalent to {@link HttpServletRequest#getSession(boolean)}
* and will not create a new HttpSession.
*/ */
public HttpSession getSession() public HttpSession getSession()
{ {
return request.getSession(false); return request.getSession(false);
} }
/**
* @return Get WebSocket negotiation offered sub protocols
*/
public List<String> getSubProtocols() public List<String> getSubProtocols()
{ {
return negotiation.getOfferedSubprotocols(); return negotiation.getOfferedSubprotocols();
} }
/** /**
* Equivalent to {@link HttpServletRequest#getUserPrincipal()} * @return The User's {@link Principal} or null
* @see HttpServletRequest#getUserPrincipal()
*/ */
public Principal getUserPrincipal() public Principal getUserPrincipal()
{ {
return request.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()) for (String protocol : getSubProtocols())
{ {
if (protocol.equalsIgnoreCase(test)) if (protocol.equalsIgnoreCase(subprotocol))
{ {
return true; return true;
} }
@ -299,16 +376,30 @@ public class ServletUpgradeRequest
return false; return false;
} }
/**
* @return True if the request is secure
* @see ServletRequest#isSecure()
*/
public boolean isSecure() public boolean isSecure()
{ {
return this.secure; 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) public boolean isUserInRole(String role)
{ {
return request.isUserInRole(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) public void setServletAttribute(String name, Object value)
{ {
request.setAttribute(name, value); request.setAttribute(name, value);

View File

@ -23,7 +23,7 @@ package org.eclipse.jetty.websocket.servlet;
* <p> * <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 * 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. * WebSocketCreator implementation.
* <p> * </p>
*/ */
public interface WebSocketCreator 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.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.List;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -29,19 +28,20 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.pathmap.PathSpec; 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.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.server.Handshaker; import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
/** /**
* Abstract Servlet used to bridge the Servlet API to the WebSocket API. * Abstract Servlet used to bridge the Servlet API to the WebSocket API.
* <p> * <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. * appropriate conditions.
* <p> * </p>
* The most basic implementation would be as follows. * <p>The most basic implementation would be as follows:</p>
* <p>
* <pre> * <pre>
* package my.example; * package my.example;
* *
@ -53,106 +53,97 @@ import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
* &#064;Override * &#064;Override
* public void configure(WebSocketServletFactory factory) * public void configure(WebSocketServletFactory factory)
* { * {
* // set a 10 second idle timeout * factory.setDefaultMaxFrameSize(4096);
* factory.getPolicy().setIdleTimeout(10000); * factory.addMapping(factory.parsePathSpec("/"), (req,res)-&gt;new EchoSocket());
* // register my socket
* factory.register(MyEchoSocket.class);
* } * }
* } * }
* </pre> * </pre>
* <p> * <p>
* Note: that only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketCreatorMapping} handling of creating * Only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketMapping} handling of creating
* WebSockets.<br> * WebSockets. All other requests are treated as normal servlet requests. The configuration defined by this servlet init parameters will
* All other requests are treated as normal servlet requests. * be used as the customizer for any mappings created by {@link WebSocketServletFactory#addMapping(PathSpec, WebSocketCreator)} during
* <p> * {@link #configure(WebSocketServletFactory)} calls. The request upgrade may be peformed by this servlet, or is may be performed by a
* <p> * {@link WebSocketUpgradeFilter} instance that will share the same {@link WebSocketMapping} instance. If the filter is used, then the
* <b>Configuration / Init-Parameters:</b><br> * filter configuraton is used as the default configuration prior to this servlets configuration being applied.
* </p>
* <p> * <p>
* <b>Configuration / Init-Parameters:</b>
* </p>
* <dl> * <dl>
* <dt>maxIdleTime</dt> * <dt>maxIdleTime</dt>
* <dd>set the time in ms that a websocket may be idle before closing<br> * <dd>set the time in ms that a websocket may be idle before closing<br>
* <p>
* <dt>maxTextMessageSize</dt> * <dt>maxTextMessageSize</dt>
* <dd>set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing<br> * <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> * <dt>maxBinaryMessageSize</dt>
* <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br> * <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br>
* <p>
* <dt>inputBufferSize</dt> * <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> * </dl>
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public abstract class WebSocketServlet extends HttpServlet public abstract class WebSocketServlet extends HttpServlet
{ {
private static final Logger LOG = Log.getLogger(WebSocketServlet.class); // TODO This servlet should be split into an API neutral version and a Jetty API specific one.
private WebSocketCreatorMapping factory;
private final Handshaker handshaker = Handshaker.newInstance();
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 @Override
public void init() throws ServletException public void init() throws ServletException
{ {
try try
{ {
ServletContext ctx = getServletContext(); ServletContext servletContext = getServletContext();
mapping = WebSocketMapping.ensureMapping(servletContext);
factory = new WebSocketCreatorMapping();
factory.setContextClassLoader(ctx.getClassLoader());
String max = getInitParameter("maxIdleTime"); String max = getInitParameter("maxIdleTime");
if (max != null) if (max != null)
{ customizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
factory.setDefaultIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
}
max = getInitParameter("maxTextMessageSize"); max = getInitParameter("maxTextMessageSize");
if (max != null) if (max != null)
{ customizer.setMaxTextMessageSize(Long.parseLong(max));
factory.setDefaultMaxTextMessageSize(Long.parseLong(max));
}
max = getInitParameter("maxBinaryMessageSize"); max = getInitParameter("maxBinaryMessageSize");
if (max != null) if (max != null)
{ customizer.setMaxBinaryMessageSize(Long.parseLong(max));
factory.setDefaultMaxBinaryMessageSize(Long.parseLong(max));
}
max = getInitParameter("inputBufferSize"); max = getInitParameter("inputBufferSize");
if (max != null) if (max != null)
{ customizer.setInputBufferSize(Integer.parseInt(max));
factory.setDefaultInputBufferSize(Integer.parseInt(max));
}
max = getInitParameter("outputBufferSize"); max = getInitParameter("outputBufferSize");
if (max != null) if (max != null)
{ customizer.setOutputBufferSize(Integer.parseInt(max));
factory.setDefaultOutputBufferSize(Integer.parseInt(max));
}
max = getInitParameter("maxAllowedFrameSize"); max = getInitParameter("maxFrameSize");
if (max==null)
max = getInitParameter("maxAllowedFrameSize");
if (max != null) if (max != null)
{ customizer.setMaxFrameSize(Long.parseLong(max));
factory.setDefaultMaxAllowedFrameSize(Long.parseLong(max));
}
String autoFragment = getInitParameter("autoFragment"); String autoFragment = getInitParameter("autoFragment");
if (autoFragment != null) if (autoFragment != null)
{ customizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
factory.setAutoFragment(Boolean.parseBoolean(autoFragment));
}
List<WebSocketServletFrameHandlerFactory> factories = (List<WebSocketServletFrameHandlerFactory>)ctx.getAttribute( configure(customizer); // Let user modify customizer prior after init params
WebSocketServletFrameHandlerFactory.ATTR_HANDLERS);
if (factories != null)
factories.forEach(factory::addFrameHandlerFactory);
configure(factory); // Let user modify factory
ctx.setAttribute(WebSocketCreatorMapping.class.getName(), factory);
} }
catch (Throwable x) 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 @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. // Often this servlet is used together with the WebSocketUpgradeFilter,
// We should rely on the Container for stripping path parameters and its ilk before // so upgrade requests will normally be upgraded by the filter. But we
// attempting to match a specific mapped websocket creator. // can do it here as well if for some reason the filter did not match.
String target = request.getServletPath(); if (mapping.upgrade(req, resp, null))
if (request.getPathInfo() != 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 return getIdleTimeout();
// 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);
}
} }
// All other processing @Override
super.service(request, response); 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; package org.eclipse.jetty.websocket.servlet;
import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import java.time.Duration; import java.time.Duration;
public interface WebSocketServletFactory public interface WebSocketServletFactory
{ {
void addFrameHandlerFactory(WebSocketServletFrameHandlerFactory frameHandlerFactory);
/** WebSocketExtensionRegistry getExtensionRegistry();
* 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);
Duration getDefaultIdleTimeout(); Duration getDefaultIdleTimeout();
@ -64,6 +52,26 @@ public interface WebSocketServletFactory
void setDefaultOutputBufferSize(int bufferSize); 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. * Returns the creator for the given path spec.
* *
@ -80,9 +88,6 @@ public interface WebSocketServletFactory
*/ */
WebSocketCreator getMatch(String target); WebSocketCreator getMatch(String target);
boolean isAutoFragment();
void setAutoFragment(boolean autoFragment);
/** /**
* Parse a PathSpec string into a PathSpec instance. * 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.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Map;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import javax.servlet.Filter; import javax.servlet.Filter;
@ -35,169 +34,98 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.pathmap.PathSpec; 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.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.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.Handshaker; import org.eclipse.jetty.websocket.core.server.Handshaker;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
/** /**
* Inline Servlet Filter to capture WebSocket upgrade requests. * 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") @ManagedObject("WebSocket Upgrade Filter")
public class WebSocketUpgradeFilter implements Filter, Dumpable public class WebSocketUpgradeFilter implements Filter, Dumpable
{ {
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class); private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
private final Handshaker handshaker = Handshaker.newInstance(); public static FilterHolder ensureFilter(ServletContext servletContext) throws ServletException
/**
* 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
{ {
WebSocketCreatorMapping factory = (WebSocketCreatorMapping)context.getAttribute(WebSocketCreatorMapping.class.getName()); ServletHandler servletHandler = ContextHandler.getContextHandler(servletContext).getChildHandlerByClass(ServletHandler.class);
if (factory == null)
for (FilterHolder holder : servletHandler.getFilters())
{ {
factory = new WebSocketCreatorMapping(); if (holder.getClassName().equals(WebSocketUpgradeFilter.class.getName()))
context.setAttribute(WebSocketCreatorMapping.class.getName(), factory); return holder;
if (holder.getHeldClass()!=null && WebSocketUpgradeFilter.class.isAssignableFrom(holder.getHeldClass()))
return holder;
} }
for (FilterHolder filterHolder : context.getServletHandler().getFilters()) String name = "WebSocketUpgradeFilter";
{
// 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 pathSpec = "/*"; String pathSpec = "/*";
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST); EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
FilterHolder fholder = new FilterHolder(filter); FilterHolder holder = new FilterHolder(new WebSocketUpgradeFilter());
fholder.setName(name); holder.setName(name);
fholder.setAsyncSupported(true); holder.setAsyncSupported(true);
fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY, WebSocketUpgradeFilter.class.getName()); servletHandler.addFilterWithMapping(holder, pathSpec, dispatcherTypes);
context.addFilter(fholder, pathSpec, dispatcherTypes);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
LOG.debug("Adding [{}] {} mapped to {} to {}", name, filter, pathSpec, context); return holder;
}
} }
private WebSocketCreatorMapping factory; private final FrameHandler.ConfigurationCustomizer defaultCustomizer = new FrameHandler.ConfigurationCustomizer();
private String instanceKey; private WebSocketMapping mapping;
private boolean alreadySetToAttribute = false;
@SuppressWarnings("unused")
public WebSocketUpgradeFilter() 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 @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 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. if (mapping.upgrade(httpreq, httpresp, defaultCustomizer))
// We should rely on the Container for stripping path parameters and its ilk before return;
// attempting to match a specific mapped websocket creator.
String target = httpreq.getServletPath();
if (httpreq.getPathInfo() != null)
{
target = target + httpreq.getPathInfo();
}
WebSocketNegotiator negotiator = factory.getMatchedNegotiator(target, 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
// Store PathSpec resource mapping as request attribute, for WebSocketCreator // due to incoming request constraints (controlled by WebSocketCreator)
// implementors to use later if they wish if (response.isCommitted())
httpreq.setAttribute(PathSpec.class.getName(), pathSpec); return;
});
if (negotiator == null) // Handle normally
{
// 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.
chain.doFilter(request, response); chain.doFilter(request, response);
} }
@ -210,115 +138,49 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
@Override @Override
public void dump(Appendable out, String indent) throws IOException 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) @ManagedAttribute(value = "factory", readonly = true)
public WebSocketCreatorMapping getFactory() public WebSocketMapping getMapping()
{ {
return factory; return mapping;
} }
@Override @Override
public void init(FilterConfig config) throws ServletException public void init(FilterConfig config) throws ServletException
{ {
if (factory == null) final ServletContext context = config.getServletContext();
{ mapping = WebSocketMapping.ensureMapping(context);
factory = (WebSocketCreatorMapping)config.getServletContext().getAttribute(WebSocketCreatorMapping.class.getName());
if (factory == null) String max = config.getInitParameter("maxIdleTime");
factory = new WebSocketCreatorMapping(); if (max != null)
} defaultCustomizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
try max = config.getInitParameter("maxTextMessageSize");
{ if (max != null)
final ServletContext context = config.getServletContext(); 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"); max = config.getInitParameter("inputBufferSize");
if (max != null) if (max != null)
{ defaultCustomizer.setInputBufferSize(Integer.parseInt(max));
getFactory().setDefaultIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
}
max = config.getInitParameter("maxTextMessageSize"); max = config.getInitParameter("outputBufferSize");
if (max != null) if (max != null)
{ defaultCustomizer.setOutputBufferSize(Integer.parseInt(max));
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("maxFrameSize");
if (max == null)
max = config.getInitParameter("maxAllowedFrameSize"); max = config.getInitParameter("maxAllowedFrameSize");
if (max != null) if (max != null)
{ defaultCustomizer.setMaxFrameSize(Long.parseLong(max));
getFactory().setDefaultMaxAllowedFrameSize(Long.parseLong(max));
}
String autoFragment = config.getInitParameter("autoFragment"); String autoFragment = config.getInitParameter("autoFragment");
if (autoFragment != null) if (autoFragment != null)
{ defaultCustomizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
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;
} }
} }

View File

@ -192,6 +192,7 @@
<dependency> <dependency>
<groupId>javax.annotation</groupId> <groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId> <artifactId>javax.annotation-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.websocket</groupId> <groupId>javax.websocket</groupId>
@ -202,11 +203,13 @@
<groupId>org.eclipse.jetty.websocket</groupId> <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-api</artifactId> <artifactId>jetty-websocket-api</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.websocket</groupId> <groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-servlet</artifactId> <artifactId>websocket-servlet</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty.websocket</groupId> <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.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket; 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.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator; 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.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;