From a73e466e4d37fefbc60db37f562628437b75041e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 23 Nov 2016 19:55:36 -0700 Subject: [PATCH] Issue #1124 - WebSocketUpgradeFilter + ServletContextListener + Now Spring and its initialization of WebSocketUpgradeFilter can work. + Improves life for cometd 3.1.0 as well --- .../websocket/jsr356/server/JsrCreator.java | 7 +- .../jsr356/server/JsrHandshakeRequest.java | 2 +- .../server/PathParamServerEndpointConfig.java | 6 +- .../jsr356/server/ServerContainer.java | 43 ++- .../WebSocketServerContainerInitializer.java | 21 +- .../websocket/jsr356/server/DummyCreator.java | 15 +- .../server/ExtensionStackProcessingTest.java | 7 +- jetty-websocket/websocket-server/pom.xml | 12 + .../server/MappedWebSocketCreator.java | 41 ++- .../server/NativeWebSocketConfiguration.java | 171 +++++++++++ ...eWebSocketServletContainerInitializer.java | 48 ++++ .../server/WebSocketUpgradeFilter.java | 167 ++++++----- .../WebSocketUpgradeHandlerWrapper.java | 49 +++- .../server/pathmap/PathMappings.java | 2 + .../javax.servlet.ServletContainerInitializer | 1 + .../websocket/server/InfoContextListener.java | 50 ++++ .../jetty/websocket/server/InfoSocket.java} | 26 +- .../jetty/websocket/server/WSServer.java | 204 +++++++++++++ .../server/WebSocketUpgradeFilterTest.java | 268 ++++++++++++++++++ .../resources/wsuf-config-via-listener.xml | 22 ++ 20 files changed, 1017 insertions(+), 145 deletions(-) create mode 100644 jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketConfiguration.java create mode 100644 jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java create mode 100644 jetty-websocket/websocket-server/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextListener.java rename jetty-websocket/websocket-server/src/{main/java/org/eclipse/jetty/websocket/server/DefaultMappedWebSocketCreator.java => test/java/org/eclipse/jetty/websocket/server/InfoSocket.java} (55%) create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java create mode 100644 jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-listener.xml diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java index 1193c07936e..c7693c0b9cd 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java @@ -28,6 +28,8 @@ import javax.websocket.Extension; import javax.websocket.Extension.Parameter; import javax.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.http.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -36,7 +38,6 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; import org.eclipse.jetty.websocket.jsr356.JsrExtension; import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; @@ -134,10 +135,10 @@ public class JsrCreator implements WebSocketCreator // [JSR] Step 4: build out new ServerEndpointConfig PathSpec pathSpec = jsrHandshakeRequest.getRequestPathSpec(); - if (pathSpec instanceof WebSocketPathSpec) + if (pathSpec instanceof UriTemplatePathSpec) { // We have a PathParam path spec - WebSocketPathSpec wspathSpec = (WebSocketPathSpec)pathSpec; + UriTemplatePathSpec wspathSpec = (UriTemplatePathSpec)pathSpec; String requestPath = req.getRequestPath(); // Wrap the config with the path spec information config = new PathParamServerEndpointConfig(config,wspathSpec,requestPath); diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java index f4c6b3d42c0..b740a6ca1e9 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrHandshakeRequest.java @@ -25,7 +25,7 @@ import java.util.Map; import javax.websocket.server.HandshakeRequest; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; public class JsrHandshakeRequest implements HandshakeRequest diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java index df92b762e50..a2895adca34 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/PathParamServerEndpointConfig.java @@ -23,7 +23,7 @@ import java.util.Map; import javax.websocket.server.ServerEndpointConfig; -import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec; +import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; /** * Wrapper for a {@link ServerEndpointConfig} where there PathParm information from the incoming request. @@ -32,12 +32,12 @@ public class PathParamServerEndpointConfig extends BasicServerEndpointConfig imp { private final Map pathParamMap; - public PathParamServerEndpointConfig(ServerEndpointConfig config, WebSocketPathSpec pathSpec, String requestPath) + public PathParamServerEndpointConfig(ServerEndpointConfig config, UriTemplatePathSpec pathSpec, String requestPath) { super(config); Map pathMap = pathSpec.getPathParams(requestPath); - pathParamMap = new HashMap(); + pathParamMap = new HashMap<>(); if (pathMap != null) { pathParamMap.putAll(pathMap); diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java index 8bbffef8004..e608a1cceec 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java @@ -25,6 +25,7 @@ import javax.websocket.Endpoint; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.common.events.EventDriverFactory; @@ -33,26 +34,22 @@ import org.eclipse.jetty.websocket.jsr356.JsrSessionFactory; import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner; import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; -import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec; -import org.eclipse.jetty.websocket.server.MappedWebSocketCreator; -import org.eclipse.jetty.websocket.server.WebSocketServerFactory; +import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration; public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer { private static final Logger LOG = Log.getLogger(ServerContainer.class); - private final MappedWebSocketCreator mappedCreator; - private final WebSocketServerFactory webSocketServerFactory; + private final NativeWebSocketConfiguration configuration; - public ServerContainer(MappedWebSocketCreator creator, WebSocketServerFactory factory, Executor executor) + public ServerContainer(NativeWebSocketConfiguration nativeWebSocketConfiguration, Executor executor) { super(executor); - this.mappedCreator = creator; - this.webSocketServerFactory = factory; - EventDriverFactory eventDriverFactory = this.webSocketServerFactory.getEventDriverFactory(); + this.configuration = nativeWebSocketConfiguration; + EventDriverFactory eventDriverFactory = this.configuration.getFactory().getEventDriverFactory(); eventDriverFactory.addImplementation(new JsrServerEndpointImpl()); eventDriverFactory.addImplementation(new JsrServerExtendsEndpointImpl()); - this.webSocketServerFactory.addSessionFactory(new JsrSessionFactory(this,this)); + this.configuration.getFactory().addSessionFactory(new JsrSessionFactory(this,this)); } public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path) @@ -82,8 +79,8 @@ public class ServerContainer extends ClientContainer implements javax.websocket. public void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException { - JsrCreator creator = new JsrCreator(metadata,webSocketServerFactory.getExtensionFactory()); - mappedCreator.addMapping(new WebSocketPathSpec(metadata.getPath()),creator); + JsrCreator creator = new JsrCreator(metadata,this.configuration.getFactory().getExtensionFactory()); + this.configuration.addMapping(new UriTemplatePathSpec(metadata.getPath()),creator); } @Override @@ -129,36 +126,36 @@ public class ServerContainer extends ClientContainer implements javax.websocket. return metadata; } - + @Override public long getDefaultAsyncSendTimeout() { - return webSocketServerFactory.getPolicy().getAsyncWriteTimeout(); + return this.configuration.getPolicy().getAsyncWriteTimeout(); } @Override public int getDefaultMaxBinaryMessageBufferSize() { - return webSocketServerFactory.getPolicy().getMaxBinaryMessageSize(); + return this.configuration.getPolicy().getMaxBinaryMessageSize(); } @Override public long getDefaultMaxSessionIdleTimeout() { - return webSocketServerFactory.getPolicy().getIdleTimeout(); + return this.configuration.getPolicy().getIdleTimeout(); } @Override public int getDefaultMaxTextMessageBufferSize() { - return webSocketServerFactory.getPolicy().getMaxTextMessageSize(); + return this.configuration.getPolicy().getMaxTextMessageSize(); } @Override public void setAsyncSendTimeout(long ms) { super.setAsyncSendTimeout(ms); - webSocketServerFactory.getPolicy().setAsyncWriteTimeout(ms); + this.configuration.getPolicy().setAsyncWriteTimeout(ms); } @Override @@ -166,16 +163,16 @@ public class ServerContainer extends ClientContainer implements javax.websocket. { super.setDefaultMaxBinaryMessageBufferSize(max); // overall message limit (used in non-streaming) - webSocketServerFactory.getPolicy().setMaxBinaryMessageSize(max); + this.configuration.getPolicy().setMaxBinaryMessageSize(max); // incoming streaming buffer size - webSocketServerFactory.getPolicy().setMaxBinaryMessageBufferSize(max); + this.configuration.getPolicy().setMaxBinaryMessageBufferSize(max); } @Override public void setDefaultMaxSessionIdleTimeout(long ms) { super.setDefaultMaxSessionIdleTimeout(ms); - webSocketServerFactory.getPolicy().setIdleTimeout(ms); + this.configuration.getPolicy().setIdleTimeout(ms); } @Override @@ -183,8 +180,8 @@ public class ServerContainer extends ClientContainer implements javax.websocket. { super.setDefaultMaxTextMessageBufferSize(max); // overall message limit (used in non-streaming) - webSocketServerFactory.getPolicy().setMaxTextMessageSize(max); + this.configuration.getPolicy().setMaxTextMessageSize(max); // incoming streaming buffer size - webSocketServerFactory.getPolicy().setMaxTextMessageBufferSize(max); + this.configuration.getPolicy().setMaxTextMessageBufferSize(max); } } diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java index 8ec0ca5a17d..c155f0426df 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java @@ -31,18 +31,14 @@ import javax.websocket.server.ServerApplicationConfig; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; -import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.jsr356.server.ServerContainer; -import org.eclipse.jetty.websocket.server.DefaultMappedWebSocketCreator; -import org.eclipse.jetty.websocket.server.MappedWebSocketCreator; -import org.eclipse.jetty.websocket.server.WebSocketServerFactory; +import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration; +import org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; @HandlesTypes( @@ -107,18 +103,12 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit public static ServerContainer configureContext(ServletContextHandler context) throws ServletException { // Create Basic components - WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - ByteBufferPool bufferPool = new MappedByteBufferPool(); - MappedWebSocketCreator creator = new DefaultMappedWebSocketCreator(); - WebSocketServerFactory factory = new WebSocketServerFactory(policy, bufferPool); - + NativeWebSocketConfiguration nativeWebSocketConfiguration = NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext()); + // Create the Jetty ServerContainer implementation - ServerContainer jettyContainer = new ServerContainer(creator,factory,context.getServer().getThreadPool()); + ServerContainer jettyContainer = new ServerContainer(nativeWebSocketConfiguration, context.getServer().getThreadPool()); context.addBean(jettyContainer); - context.setAttribute(WebSocketUpgradeFilter.CREATOR_KEY, creator); - context.setAttribute(WebSocketUpgradeFilter.FACTORY_KEY, factory); - // 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); @@ -126,6 +116,7 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit if(isEnabledViaContext(context.getServletContext(), ADD_DYNAMIC_FILTER_KEY, true)) { WebSocketUpgradeFilter.configureContext(context); + NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext()); } return jettyContainer; diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java index e7e4914bf1d..e9ac5d75bf2 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/DummyCreator.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.jsr356.server; +import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.websocket.server.MappedWebSocketCreator; import org.eclipse.jetty.websocket.server.pathmap.PathMappings; import org.eclipse.jetty.websocket.server.pathmap.PathSpec; @@ -30,10 +31,22 @@ public class DummyCreator implements MappedWebSocketCreator { /* do nothing */ } - + + @Override + public void addMapping(org.eclipse.jetty.http.pathmap.PathSpec spec, WebSocketCreator creator) + { + /* do nothing */ + } + @Override public PathMappings getMappings() { return null; } + + @Override + public MappedResource getMapping(String target) + { + return null; + } } diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java index 89e0498914d..222e1088c8c 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java @@ -47,7 +47,7 @@ import org.eclipse.jetty.websocket.jsr356.JsrExtension; import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint; -import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; +import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration; import org.junit.After; import org.junit.Assert; import org.junit.Assume; @@ -88,8 +88,9 @@ public class ExtensionStackProcessingTest private void assumeDeflateFrameAvailable() { - WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)servletContextHandler.getAttribute(WebSocketUpgradeFilter.class.getName()); - ExtensionFactory serverExtensionFactory = filter.getFactory().getExtensionFactory(); + NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) servletContextHandler + .getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName()); + ExtensionFactory serverExtensionFactory = configuration.getFactory().getExtensionFactory(); Assume.assumeTrue("Server has permessage-deflate extension registered",serverExtensionFactory.isAvailable("permessage-deflate")); } diff --git a/jetty-websocket/websocket-server/pom.xml b/jetty-websocket/websocket-server/pom.xml index 07328c1ec55..fe8e95dd06b 100644 --- a/jetty-websocket/websocket-server/pom.xml +++ b/jetty-websocket/websocket-server/pom.xml @@ -89,6 +89,18 @@ ${project.version} provided + + org.eclipse.jetty + jetty-webapp + ${project.version} + test + + + org.eclipse.jetty + jetty-annotations + ${project.version} + test + org.eclipse.jetty.websocket websocket-common diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java index 4e23d2441d9..6045d59789f 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/MappedWebSocketCreator.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.server; +import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.websocket.server.pathmap.PathMappings; import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; @@ -27,7 +28,41 @@ import org.eclipse.jetty.websocket.servlet.WebSocketCreator; */ public interface MappedWebSocketCreator { - public void addMapping(PathSpec spec, WebSocketCreator creator); - - public PathMappings getMappings(); + /** + * Add a mapping. + * + * @param spec the path spec to use + * @param creator the creator for the mapping + * @deprecated use {@link #addMapping(org.eclipse.jetty.http.pathmap.PathSpec, WebSocketCreator)} instead. + * (support classes moved to generic jetty-http project) + */ + @Deprecated + void addMapping(PathSpec spec, WebSocketCreator creator); + + /** + * Add a mapping. + * + * @param spec the path spec to use + * @param creator the creator for the mapping + * @since 9.2.20 + */ + void addMapping(org.eclipse.jetty.http.pathmap.PathSpec spec, WebSocketCreator creator); + + /** + * Get all of the PathMappings declared. + * + * @return the PathMappings + * @deprecated do not use, use {@link #getMapping(String)} instead. + */ + @Deprecated + PathMappings getMappings(); + + /** + * Get specific MappedResource for associated target. + * + * @param target the target to get mapping for + * @return the MappedResource for the target, or null if no match. + * @since 9.2.20 + */ + MappedResource getMapping(String target); } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketConfiguration.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketConfiguration.java new file mode 100644 index 00000000000..26c13df6094 --- /dev/null +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketConfiguration.java @@ -0,0 +1,171 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.io.IOException; + +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.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; + +/** + * Interface for Configuring Jetty Server Native WebSockets + *

+ * Only applicable if using {@link WebSocketUpgradeFilter} + *

+ */ +public class NativeWebSocketConfiguration extends ContainerLifeCycle implements Dumpable +{ + private final WebSocketServerFactory factory; + private final PathMappings mappings = new PathMappings<>(); + + public NativeWebSocketConfiguration() + { + this(new WebSocketServerFactory()); + } + + public NativeWebSocketConfiguration(WebSocketServerFactory webSocketServerFactory) + { + this.factory = webSocketServerFactory; + addBean(this.factory); + } + + @Override + public void doStop() throws Exception + { + mappings.reset(); + super.doStop(); + } + + @Override + public String dump() + { + return ContainerLifeCycle.dump(this); + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + // TODO: show factory/mappings ? + mappings.dump(out, indent); + } + + /** + * Get WebSocketServerFactory being used. + * + * @return the WebSocketServerFactory being used. + */ + public WebSocketServerFactory getFactory() + { + return this.factory; + } + + /** + * Get the matching {@link MappedResource} for the provided target. + * + * @param target the target path + * @return the matching resource, or null if no match. + */ + public MappedResource getMatch(String target) + { + return this.mappings.getMatch(target); + } + + /** + * Used to configure the Default {@link WebSocketPolicy} used by all endpoints that + * don't redeclare the values. + * + * @return the default policy for all WebSockets + */ + public WebSocketPolicy getPolicy() + { + return this.factory.getPolicy(); + } + + /** + * Manually add a WebSocket mapping. + * + * @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) + { + mappings.put(pathSpec, creator); + } + + /** + * Manually add a WebSocket mapping. + * + * @param spec the pathspec to respond on + * @param creator the websocket creator to activate on the provided mapping + * @deprecated use {@link #addMapping(PathSpec, Class)} instead. + */ + @Deprecated + public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator) + { + if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec) + { + addMapping(new ServletPathSpec(spec.getPathSpec()), creator); + } + else if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec) + { + addMapping(new RegexPathSpec(spec.getPathSpec()), creator); + } + else + { + throw new RuntimeException("Unsupported (Deprecated) PathSpec implementation type: " + spec.getClass().getName()); + } + } + + /** + * Manually add a WebSocket mapping. + * + * @param pathSpec the pathspec to respond on + * @param endpointClass the endpoint class to use for new upgrade requests on the provided + * pathspec (can be an {@link org.eclipse.jetty.websocket.api.annotations.WebSocket} annotated + * POJO, or implementing {@link org.eclipse.jetty.websocket.api.WebSocketListener}) + */ + public void addMapping(PathSpec pathSpec, final Class endpointClass) + { + mappings.put(pathSpec, new WebSocketCreator() + { + @Override + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + { + try + { + return endpointClass.newInstance(); + } + catch (InstantiationException | IllegalAccessException e) + { + throw new WebSocketException("Unable to create instance of " + endpointClass.getName(), e); + } + } + }); + } +} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java new file mode 100644 index 00000000000..87c48e6ac0e --- /dev/null +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java @@ -0,0 +1,48 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.Set; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +public class NativeWebSocketServletContainerInitializer implements ServletContainerInitializer +{ + public static NativeWebSocketConfiguration getDefaultFrom(ServletContext context) + { + final String KEY = NativeWebSocketConfiguration.class.getName(); + + NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getAttribute(KEY); + if (configuration == null) + { + configuration = new NativeWebSocketConfiguration(); + context.setAttribute(KEY, configuration); + } + return configuration; + } + + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + // initialize + getDefaultFrom(ctx); + } +} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java index 1178c35c578..40e62dee333 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java @@ -32,30 +32,31 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.pathmap.MappedResource; +import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle; 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.WebSocketPolicy; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; /** * Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects. */ @ManagedObject("WebSocket Upgrade Filter") -public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter, MappedWebSocketCreator, Dumpable +public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, MappedWebSocketCreator, Dumpable { public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey"; + public static final String CONFIG_ATTRIBUTE_KEY = "configAttributeKey"; private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class); public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) throws ServletException @@ -68,7 +69,8 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter } // Dynamically add filter - filter = new WebSocketUpgradeFilter(); + NativeWebSocketConfiguration configuration = NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext()); + filter = new WebSocketUpgradeFilter(configuration); filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName()); String name = "Jetty_WebSocketUpgradeFilter"; @@ -110,43 +112,63 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter return configureContext((ServletContextHandler) handler); } - public static final String CREATOR_KEY = "org.eclipse.jetty.websocket.server.creator"; - public static final String FACTORY_KEY = "org.eclipse.jetty.websocket.server.factory"; - - private final WebSocketPolicy policy; - private final ByteBufferPool bufferPool; - private WebSocketServerFactory factory; - private MappedWebSocketCreator mappedWebSocketCreator; - private String fname; + private NativeWebSocketConfiguration configuration; private boolean alreadySetToAttribute = false; public WebSocketUpgradeFilter() { - this(WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool()); + // do nothing } public WebSocketUpgradeFilter(WebSocketPolicy policy, ByteBufferPool bufferPool) { - this.policy = policy; - this.bufferPool = bufferPool; + this(new NativeWebSocketConfiguration(new WebSocketServerFactory(policy, bufferPool))); + } + + public WebSocketUpgradeFilter(NativeWebSocketConfiguration configuration) + { + this.configuration = configuration; } @Override public void addMapping(PathSpec spec, WebSocketCreator creator) { - mappedWebSocketCreator.addMapping(spec, creator); + configuration.addMapping(spec, creator); + } + + @Deprecated + @Override + public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator) + { + configuration.addMapping(spec, creator); } @Override public void destroy() { - factory.cleanup(); - super.destroy(); + try + { + configuration.stop(); + } + catch (Exception e) + { + LOG.ignore(e); + } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (configuration == null) + { + // no configuration, cannot operate + LOG.debug("WebSocketUpgradeFilter is not operational - missing " + NativeWebSocketConfiguration.class.getName()); + chain.doFilter(request, response); + return; + } + + WebSocketServletFactory factory = configuration.getFactory(); + if (factory == null) { // no factory, cannot operate @@ -175,14 +197,9 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter target = target.substring(contextPath.length()); } - MappedResource resource = mappedWebSocketCreator.getMappings().getMatch(target); + MappedResource resource = configuration.getMatch(target); if (resource == null) { - if (LOG.isDebugEnabled()) - { - LOG.debug("WebSocket Upgrade on {} has no associated endpoint", target); - LOG.debug("PathMappings: {}", mappedWebSocketCreator.getMappings().dump()); - } // no match. chain.doFilter(request, response); return; @@ -236,66 +253,93 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter @Override public void dump(Appendable out, String indent) throws IOException { - out.append(indent).append(" +- pathmap=").append(mappedWebSocketCreator.toString()).append("\n"); - mappedWebSocketCreator.getMappings().dump(out, indent + " "); + out.append(indent).append(" +- configuration=").append(configuration.toString()).append("\n"); + configuration.dump(out, indent); } - public WebSocketServerFactory getFactory() + public WebSocketServletFactory getFactory() { - return factory; + return configuration.getFactory(); + } + + @ManagedAttribute(value = "configuration", readonly = true) + public NativeWebSocketConfiguration getConfiguration() + { + if (configuration == null) + { + throw new IllegalStateException(this.getClass().getName() + " not initialized yet"); + } + return configuration; } - @ManagedAttribute(value = "mappings", readonly = true) @Override - public PathMappings getMappings() + public MappedResource getMapping(String target) { - return mappedWebSocketCreator.getMappings(); + return getConfiguration().getMatch(target); + } + + @Override + @Deprecated + public org.eclipse.jetty.websocket.server.pathmap.PathMappings getMappings() + { + throw new IllegalStateException("Access to PathMappings cannot be supported. See alternative API in javadoc for " + + MappedWebSocketCreator.class.getName()); } @Override public void init(FilterConfig config) throws ServletException { - fname = config.getFilterName(); - try { - mappedWebSocketCreator = (MappedWebSocketCreator) config.getServletContext().getAttribute(CREATOR_KEY); - if (mappedWebSocketCreator == null) + String configurationKey = config.getInitParameter(CONFIG_ATTRIBUTE_KEY); + if (configurationKey == null) { - mappedWebSocketCreator = new DefaultMappedWebSocketCreator(); + configurationKey = NativeWebSocketConfiguration.class.getName(); } - factory = (WebSocketServerFactory) config.getServletContext().getAttribute(FACTORY_KEY); - if (factory == null) + if (configuration == null) { - factory = new WebSocketServerFactory(policy, bufferPool); + this.configuration = (NativeWebSocketConfiguration) config.getServletContext().getAttribute(configurationKey); + if (this.configuration == null) + { + // The NativeWebSocketConfiguration should have arrived from the NativeWebSocketServletContainerInitializer + throw new ServletException("Unable to find required instance of " + + NativeWebSocketConfiguration.class.getName() + " at ServletContext attribute '" + configurationKey + "'"); + } + } + else + { + // We have a NativeWebSocketConfiguration already present, make sure it exists on the ServletContext + if (config.getServletContext().getAttribute(configurationKey) == null) + { + config.getServletContext().setAttribute(configurationKey, this.configuration); + } } - addBean(factory, true); - // TODO: Policy isn't from attributes + this.configuration.start(); String max = config.getInitParameter("maxIdleTime"); if (max != null) { - factory.getPolicy().setIdleTimeout(Long.parseLong(max)); + getFactory().getPolicy().setIdleTimeout(Long.parseLong(max)); } max = config.getInitParameter("maxTextMessageSize"); if (max != null) { - factory.getPolicy().setMaxTextMessageSize(Integer.parseInt(max)); + getFactory().getPolicy().setMaxTextMessageSize(Integer.parseInt(max)); } max = config.getInitParameter("maxBinaryMessageSize"); if (max != null) { - factory.getPolicy().setMaxBinaryMessageSize(Integer.parseInt(max)); + getFactory().getPolicy().setMaxBinaryMessageSize(Integer.parseInt(max)); } max = config.getInitParameter("inputBufferSize"); if (max != null) { - factory.getPolicy().setInputBufferSize(Integer.parseInt(max)); + getFactory().getPolicy().setInputBufferSize(Integer.parseInt(max)); } String key = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY); @@ -305,33 +349,22 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter key = WebSocketUpgradeFilter.class.getName(); } + // Set instance of this filter to context attribute setToAttribute(config.getServletContext(), key); - - factory.start(); } - catch (Exception x) + catch (ServletException e) { - throw new ServletException(x); + throw e; + } + catch (Throwable t) + { + throw new ServletException(t); } } private void setToAttribute(ServletContextHandler 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; + setToAttribute(context.getServletContext(), key); } public void setToAttribute(ServletContext context, String key) throws ServletException @@ -356,6 +389,6 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter @Override public String toString() { - return String.format("%s[factory=%s,creator=%s]", this.getClass().getSimpleName(), factory, mappedWebSocketCreator); + return String.format("%s[configuration=%s]", this.getClass().getSimpleName(), configuration); } } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java index 88e9b0e6fde..66a415078a0 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeHandlerWrapper.java @@ -24,19 +24,17 @@ 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.PathSpec; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements MappedWebSocketCreator { - private PathMappings pathmap = new PathMappings<>(); - private final WebSocketServerFactory factory; + private NativeWebSocketConfiguration configuration; public WebSocketUpgradeHandlerWrapper() { @@ -45,27 +43,48 @@ public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements Ma public WebSocketUpgradeHandlerWrapper(ByteBufferPool bufferPool) { - factory = new WebSocketServerFactory(bufferPool); + this.configuration = new NativeWebSocketConfiguration(new WebSocketServerFactory(bufferPool)); } - + @Override public void addMapping(PathSpec spec, WebSocketCreator creator) { - pathmap.put(spec,creator); + this.configuration.addMapping(spec, creator); } - + + /** + * Add a mapping. + * + * @param spec the path spec to use + * @param creator the creator for the mapping + * @deprecated use {@link #addMapping(PathSpec, WebSocketCreator)} instead. + */ @Override - public PathMappings getMappings() + @Deprecated + public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator) { - return pathmap; + configuration.addMapping(spec, creator); } - + + @Override + public org.eclipse.jetty.websocket.server.pathmap.PathMappings getMappings() + { + throw new IllegalStateException("Access to PathMappings cannot be supported. See alternative API in javadoc for " + + MappedWebSocketCreator.class.getName()); + } + + @Override + public MappedResource getMapping(String target) + { + return this.configuration.getMatch(target); + } + @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if (factory.isUpgradeRequest(request,response)) + if (configuration.getFactory().isUpgradeRequest(request,response)) { - MappedResource resource = pathmap.getMatch(target); + MappedResource resource = configuration.getMatch(target); if (resource == null) { // no match. @@ -79,7 +98,7 @@ public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements Ma request.setAttribute(PathSpec.class.getName(),resource); // We have an upgrade request - if (factory.acceptWebSocket(creator,request,response)) + if (configuration.getFactory().acceptWebSocket(creator,request,response)) { // We have a socket instance created return; diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java index 2f5307331c5..f55b3c99be5 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/pathmap/PathMappings.java @@ -38,7 +38,9 @@ import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; * Sorted into search order upon entry into the Set * * @param + * @deprecated use {@link org.eclipse.jetty.http.pathmap.PathMappings} instead */ +@Deprecated @ManagedObject("Path Mappings") public class PathMappings implements Iterable>, Dumpable { diff --git a/jetty-websocket/websocket-server/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/jetty-websocket/websocket-server/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 00000000000..1be3ff3b8ad --- /dev/null +++ b/jetty-websocket/websocket-server/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextListener.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextListener.java new file mode 100644 index 00000000000..0e2e7a46d13 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextListener.java @@ -0,0 +1,50 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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 javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.eclipse.jetty.http.pathmap.ServletPathSpec; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; + +public class InfoContextListener implements WebSocketCreator, ServletContextListener +{ + @Override + public void contextInitialized(ServletContextEvent sce) + { + NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping(new ServletPathSpec("/info/*"), this); + sce.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + } + + @Override + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + { + return new InfoSocket(); + } +} diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/DefaultMappedWebSocketCreator.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoSocket.java similarity index 55% rename from jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/DefaultMappedWebSocketCreator.java rename to jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoSocket.java index fcbd5419703..904631f6607 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/DefaultMappedWebSocketCreator.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoSocket.java @@ -18,23 +18,27 @@ package org.eclipse.jetty.websocket.server; -import org.eclipse.jetty.websocket.server.pathmap.PathMappings; -import org.eclipse.jetty.websocket.server.pathmap.PathSpec; -import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; -public class DefaultMappedWebSocketCreator implements MappedWebSocketCreator +@WebSocket +public class InfoSocket { - private final PathMappings mappings = new PathMappings<>(); + private Session session; - @Override - public void addMapping(PathSpec spec, WebSocketCreator creator) + @OnWebSocketConnect + public void onConnect(Session session) { - this.mappings.put(spec, creator); + this.session = session; } - @Override - public PathMappings getMappings() + @OnWebSocketMessage + public void onMessage(String msg) { - return this.mappings; + RemoteEndpoint remote = this.session.getRemote(); + remote.sendStringByFuture("session.maxTextMessageSize=" + session.getPolicy().getMaxTextMessageSize()); } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java new file mode 100644 index 00000000000..9ca3a1d7f4a --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java @@ -0,0 +1,204 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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 static org.hamcrest.Matchers.notNullValue; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; + +import org.eclipse.jetty.annotations.AnnotationConfiguration; +import org.eclipse.jetty.plus.webapp.EnvConfiguration; +import org.eclipse.jetty.plus.webapp.PlusConfiguration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.OS; +import org.eclipse.jetty.toolchain.test.TestingDir; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.FragmentConfiguration; +import org.eclipse.jetty.webapp.MetaInfConfiguration; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.webapp.WebInfConfiguration; +import org.eclipse.jetty.webapp.WebXmlConfiguration; +import org.junit.Assert; + +/** + * Utility to build out exploded directory WebApps, in the /target/tests/ directory, for testing out servers that use javax.websocket endpoints. + *

+ * This is particularly useful when the WebSocket endpoints are discovered via the javax.websocket annotation scanning. + */ +public class WSServer +{ + private static final Logger LOG = Log.getLogger(WSServer.class); + private final File contextDir; + private final String contextPath; + private Server server; + private URI serverUri; + private ContextHandlerCollection contexts; + private File webinf; + private File classesDir; + + public WSServer(TestingDir testdir, String contextName) + { + this(testdir.getDir(),contextName); + } + + public WSServer(File testdir, String contextName) + { + this.contextDir = new File(testdir,contextName); + this.contextPath = "/" + contextName; + FS.ensureEmpty(contextDir); + } + + public void copyClass(Class clazz) throws Exception + { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + String endpointPath = clazz.getName().replace('.','/') + ".class"; + URL classUrl = cl.getResource(endpointPath); + Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue()); + File destFile = new File(classesDir,OS.separators(endpointPath)); + FS.ensureDirExists(destFile.getParentFile()); + File srcFile = new File(classUrl.toURI()); + IO.copy(srcFile,destFile); + } + + public void copyEndpoint(Class endpointClass) throws Exception + { + copyClass(endpointClass); + } + + public void copyWebInf(String testResourceName) throws IOException + { + webinf = new File(contextDir,"WEB-INF"); + FS.ensureDirExists(webinf); + classesDir = new File(webinf,"classes"); + FS.ensureDirExists(classesDir); + File webxml = new File(webinf,"web.xml"); + File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName); + IO.copy(testWebXml,webxml); + } + + public WebAppContext createWebAppContext() throws MalformedURLException, IOException + { + WebAppContext context = new WebAppContext(); + context.setContextPath(this.contextPath); + context.setBaseResource(Resource.newResource(this.contextDir)); + context.setAttribute("org.eclipse.jetty.websocket.jsr356",Boolean.TRUE); + + // @formatter:off + context.setConfigurations(new Configuration[] { + new AnnotationConfiguration(), + new WebXmlConfiguration(), + new WebInfConfiguration(), + new PlusConfiguration(), + new MetaInfConfiguration(), + new FragmentConfiguration(), + new EnvConfiguration()}); + // @formatter:on + + return context; + } + + public void createWebInf() throws IOException + { + copyWebInf("empty-web.xml"); + } + + public void deployWebapp(WebAppContext webapp) throws Exception + { + contexts.addHandler(webapp); + contexts.manage(webapp); + webapp.start(); + if (LOG.isDebugEnabled()) + { + webapp.dump(System.err); + } + } + + public void dump() + { + server.dumpStdErr(); + } + + public URI getServerBaseURI() + { + return serverUri; + } + + public Server getServer() + { + return server; + } + + public File getWebAppDir() + { + return this.contextDir; + } + + public void start() throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + HandlerCollection handlers = new HandlerCollection(); + contexts = new ContextHandlerCollection(); + handlers.addHandler(contexts); + server.setHandler(handlers); + + server.start(); + + String host = connector.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = connector.getLocalPort(); + serverUri = new URI(String.format("ws://%s:%d%s/",host,port,contextPath)); + if (LOG.isDebugEnabled()) + LOG.debug("Server started on {}",serverUri); + } + + public void stop() + { + if (server != null) + { + try + { + server.stop(); + } + catch (Exception e) + { + e.printStackTrace(System.err); + } + } + } +} diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java new file mode 100644 index 00000000000..bef91c93979 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java @@ -0,0 +1,268 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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 static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.http.pathmap.ServletPathSpec; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.toolchain.test.EventQueue; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.common.test.BlockheadClient; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class WebSocketUpgradeFilterTest +{ + interface ServerProvider + { + Server newServer() throws Exception; + } + + @Parameterized.Parameters(name = "{0}") + public static List data() + { + /** + * Case A: + * 1. embedded-jetty WSUF.configureContext() / app-ws configured at ... + * a. during server construction / before server.start (might not be possible with current impl, native SCI not run (yet)) + * might require NativeSCI.getDefaultFrom() first + * b. during server construction / after server.start + * c. during server start / via CustomServlet.init() + * 2. embedded-jetty WSUF addFilter / app-ws configured at server construction (before server.start) + * Case B: + * 1. web.xml WSUF / app-ws configured in CustomServlet.init() load-on-start + * Case C: + * 1. embedded-jetty WSUF.configureContext() / app-ws configured via ServletContextListener.contextInitialized + * 2. embedded-jetty WSUF addFilter / app-ws configured via ServletContextListener.contextInitialized + * Case D: + * 1. web.xml WSUF / app-ws configured via ServletContextListener.contextInitialized + * + * Every "app-ws configured" means it should access/set ws policy and add ws mappings + */ + + final WebSocketCreator infoCreator = new WebSocketCreator() + { + @Override + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + { + return new InfoSocket(); + } + }; + + List cases = new ArrayList<>(); + + // Embedded WSUF.configureContext(), directly app-ws configuration + + cases.add(new Object[]{"wsuf.configureContext/Direct configure", new ServerProvider() + { + @Override + public Server newServer() throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context); + + // direct configuration via WSUF + wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + wsuf.addMapping(new ServletPathSpec("/info/*"), infoCreator); + + server.start(); + return server; + } + }}); + + // Embedded WSUF.configureContext(), apply app-ws configuration via attribute + + cases.add(new Object[]{"wsuf.configureContext/Attribute based configure", new ServerProvider() + { + @Override + public Server newServer() throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + WebSocketUpgradeFilter.configureContext(context); + + // configuration via attribute + NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName()); + assertThat("NativeWebSocketConfiguration", configuration, notNullValue()); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator); + + server.start(); + + return server; + } + }}); + + // Embedded WSUF, added as filter, apply app-ws configuration via attribute + + cases.add(new Object[]{"wsuf/addFilter/Attribute based configure", new ServerProvider() + { + @Override + public Server newServer() throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator); + context.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration); + + server.start(); + + return server; + } + }}); + + // Embedded WSUF, added as filter, apply app-ws configuration via ServletContextListener + + cases.add(new Object[]{"wsuf.configureContext/ServletContextListener configure", new ServerProvider() + { + @Override + public Server newServer() throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + context.addEventListener(new InfoContextListener()); + + server.start(); + + return server; + } + }}); + + // WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener + + cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener", new ServerProvider() + { + @Override + public Server newServer() throws Exception + { + File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml"); + + WSServer server = new WSServer(testDir, "/"); + + server.copyClass(InfoSocket.class); + server.copyClass(InfoContextListener.class); + server.copyWebInf("wsuf-config-via-listener.xml"); + server.start(); + + WebAppContext webapp = server.createWebAppContext(); + server.deployWebapp(webapp); + + return server.getServer(); + } + }}); + + return cases; + } + + private final Server server; + private final URI serverUri; + + public WebSocketUpgradeFilterTest(String testId, ServerProvider serverProvider) throws Exception + { + this.server = serverProvider.newServer(); + + ServerConnector connector = (ServerConnector) server.getConnectors()[0]; + + // Establish the Server URI + String host = connector.getHost(); + if (host == null) + { + host = "localhost"; + } + int port = connector.getLocalPort(); + + serverUri = new URI(String.format("ws://%s:%d/", host, port)); + } + + @Test + public void testConfiguration() throws Exception + { + URI destUri = serverUri.resolve("/info/"); + + try (BlockheadClient client = new BlockheadClient(destUri)) + { + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + client.write(new TextFrame().setPayload("hello")); + + EventQueue frames = client.readFrames(1, 1000, TimeUnit.MILLISECONDS); + String payload = frames.poll().getPayloadAsUTF8(); + + // If we can connect and send a text message, we know that the endpoint was + // added properly, and the response will help us verify the policy configuration too + assertThat("payload", payload, containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024))); + } + } +} diff --git a/jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-listener.xml b/jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-listener.xml new file mode 100644 index 00000000000..1e75370335f --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-listener.xml @@ -0,0 +1,22 @@ + + + + + org.eclipse.jetty.websocket.server.InfoContextListener + + + + wsuf + org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter + + + + wsuf + /* + +