From a3f32911faae264fce47c7c8c027eb51ce61ea92 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 23 Nov 2016 19:55:36 -0700 Subject: [PATCH 1/7] 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 + /* + + From fb509eefe42d502e0ece68500a3f856d7e61808e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Nov 2016 15:10:32 +0100 Subject: [PATCH 2/7] Resetting the field that marks the filter exported as a context attribute. --- .../eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java | 1 + 1 file changed, 1 insertion(+) 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 40e62dee333..c9d8004f08b 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 @@ -148,6 +148,7 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, { try { + alreadySetToAttribute = false; configuration.stop(); } catch (Exception e) From c509a83d3b998fb762864e0308b381da6b0f03d3 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 25 Nov 2016 07:38:20 -0700 Subject: [PATCH 3/7] Issue #1124 - adding more web.xml test cases --- .../server/InfoContextAttributeListener.java | 49 +++++++++++++++++++ .../jetty/websocket/server/InfoServlet.java | 47 ++++++++++++++++++ .../server/WebSocketUpgradeFilterTest.java | 25 +++++++++- .../resources/wsuf-config-via-listener.xml | 2 +- .../wsuf-config-via-servlet-init.xml | 24 +++++++++ 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAttributeListener.java create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoServlet.java create mode 100644 jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-servlet-init.xml diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAttributeListener.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAttributeListener.java new file mode 100644 index 00000000000..11697a03d33 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAttributeListener.java @@ -0,0 +1,49 @@ +// +// ======================================================================== +// 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 InfoContextAttributeListener implements WebSocketCreator, ServletContextListener +{ + @Override + public void contextInitialized(ServletContextEvent sce) + { + NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) sce.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName()); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping(new ServletPathSpec("/info/*"), this); + } + + @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/test/java/org/eclipse/jetty/websocket/server/InfoServlet.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoServlet.java new file mode 100644 index 00000000000..8cdb07ef7d0 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoServlet.java @@ -0,0 +1,47 @@ +// +// ======================================================================== +// 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.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; + +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 InfoServlet extends HttpServlet implements WebSocketCreator +{ + @Override + public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) + { + return new InfoSocket(); + } + + @Override + public void init(ServletConfig config) throws ServletException + { + ServletContext context = config.getServletContext(); + NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getAttribute(NativeWebSocketConfiguration.class.getName()); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping(new ServletPathSpec("/info/*"), this); + } +} 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 index bef91c93979..14a55761790 100644 --- 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 @@ -210,7 +210,7 @@ public class WebSocketUpgradeFilterTest WSServer server = new WSServer(testDir, "/"); server.copyClass(InfoSocket.class); - server.copyClass(InfoContextListener.class); + server.copyClass(InfoContextAttributeListener.class); server.copyWebInf("wsuf-config-via-listener.xml"); server.start(); @@ -220,6 +220,29 @@ public class WebSocketUpgradeFilterTest return server.getServer(); } }}); + + // WSUF from web.xml, SCI active, apply app-ws configuration via Servlet.init + + cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", 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(InfoServlet.class); + server.copyWebInf("wsuf-config-via-servlet-init.xml"); + server.start(); + + WebAppContext webapp = server.createWebAppContext(); + server.deployWebapp(webapp); + + return server.getServer(); + } + }}); return cases; } 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 index 1e75370335f..8e75569ae23 100644 --- 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 @@ -7,7 +7,7 @@ version="3.1"> - org.eclipse.jetty.websocket.server.InfoContextListener + org.eclipse.jetty.websocket.server.InfoContextAttributeListener diff --git a/jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-servlet-init.xml b/jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-servlet-init.xml new file mode 100644 index 00000000000..a2a082f53cd --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-servlet-init.xml @@ -0,0 +1,24 @@ + + + + + info-servlet + org.eclipse.jetty.websocket.server.InfoServlet + 1 + + + + wsuf + org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter + + + + wsuf + /* + + From a73e466e4d37fefbc60db37f562628437b75041e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 23 Nov 2016 19:55:36 -0700 Subject: [PATCH 4/7] 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 + /* + + From 1a77cb95bae3306e48669a213c64237c0e2b5eba Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Nov 2016 15:10:32 +0100 Subject: [PATCH 5/7] Resetting the field that marks the filter exported as a context attribute. --- .../eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java | 1 + 1 file changed, 1 insertion(+) 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 40e62dee333..c9d8004f08b 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 @@ -148,6 +148,7 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, { try { + alreadySetToAttribute = false; configuration.stop(); } catch (Exception e) From 70247d74d943e178d91e87f5dd2959a0b927bcbe Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 29 Nov 2016 15:46:03 -0700 Subject: [PATCH 6/7] Issue #1124 - Fixing up merge issues + Making WebSocketServletFactory always load a new WebSocketServerFactory + Making WebSocketServerFactory need a ServletContext to construct it, if appropriate (the WebSocketHandler approach doesn't use a ServletContext) + NativeWebSocketConfiguration is now a bean of ServerContainer + Removed WebSocketServletFactory.init(ServletContext) method + Renamed WebSocketServletFactory.init() to .start() + Renamed WebSocketServletFactory.cleanup() to .stop() + CDI & Websocket now works + Using a ServletContextListener now works + DecoderFactory and EncoderFactory now work --- .../websocket/jsr356/ClientContainer.java | 2 +- .../websocket/jsr356/DecoderFactory.java | 6 + .../jsr356/server/ServerContainer.java | 1 + .../jsr356/server/OnPartialTest.java | 2 + .../jsr356/server/SessionTrackingTest.java | 2 +- .../scopes/WebSocketContainerScope.java | 24 +- .../server/NativeWebSocketConfiguration.java | 6 +- ...eWebSocketServletContainerInitializer.java | 2 +- .../websocket/server/WebSocketHandler.java | 18 +- .../server/WebSocketServerFactory.java | 316 +++++++++--------- .../server/WebSocketUpgradeFilter.java | 10 +- .../WebSocketUpgradeHandlerWrapper.java | 9 +- .../websocket/server/InfoContextListener.java | 2 +- .../server/WebSocketUpgradeFilterTest.java | 2 +- .../websocket/servlet/WebSocketServlet.java | 18 +- .../servlet/WebSocketServletFactory.java | 93 +++--- 16 files changed, 267 insertions(+), 246 deletions(-) diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java index 2164bdded80..46235eaeaa2 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java @@ -190,7 +190,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont protected void doStart() throws Exception { super.doStart(); - + // Initialize the default decoder / encoder factories EmptyClientEndpointConfig empty = new EmptyClientEndpointConfig(); this.decoderFactory.init(empty); diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java index 38f7a7f84fe..92e90f1c450 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/DecoderFactory.java @@ -166,6 +166,12 @@ public class DecoderFactory implements Configurable { LOG.debug("init({})",config); } + + if(!containerScope.isRunning()) + { + throw new RuntimeException(containerScope.getClass().getName() + " is not running yet"); + } + // Instantiate all declared decoders for (DecoderMetadata metadata : metadatas) { 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 bd81e04b921..ee782b30155 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 @@ -59,6 +59,7 @@ public class ServerContainer extends ClientContainer implements javax.websocket. eventDriverFactory.addImplementation(new JsrServerEndpointImpl()); eventDriverFactory.addImplementation(new JsrServerExtendsEndpointImpl()); this.configuration.getFactory().addSessionFactory(new JsrSessionFactory(this)); + addBean(this.configuration); } public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java index 6628a026ad4..f45be2e08f8 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java @@ -85,6 +85,8 @@ public class OnPartialTest URI requestURI = URI.create("ws://localhost/" + id); DummyConnection connection = new DummyConnection(); ClientContainer container = new ClientContainer(); + container.start(); + @SuppressWarnings("resource") JsrSession session = new JsrSession(container,id,requestURI,driver,connection); session.setPolicy(policy); diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java index ca0cd2b00f5..7dac2a71a70 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/SessionTrackingTest.java @@ -67,7 +67,7 @@ public class SessionTrackingTest serverContainer = WebSocketServerContainerInitializer.configureContext(servletContextHandler); serverContainer.addEndpoint(EchoSocket.class); - wsServerFactory = serverContainer.getBean(WebSocketServerFactory.class); + wsServerFactory = serverContainer.getWebSocketServerFactory(); server.start(); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/scopes/WebSocketContainerScope.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/scopes/WebSocketContainerScope.java index aacaf85ab22..416598b2385 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/scopes/WebSocketContainerScope.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/scopes/WebSocketContainerScope.java @@ -36,48 +36,54 @@ public interface WebSocketContainerScope * * @return the buffer pool (never null) */ - public ByteBufferPool getBufferPool(); + ByteBufferPool getBufferPool(); /** * Executor in use by the container. * * @return the Executor in use by the container. */ - public Executor getExecutor(); + Executor getExecutor(); /** * Object Factory used to create objects. * * @return Object Factory used to create instances of objects. */ - public DecoratedObjectFactory getObjectFactory(); + DecoratedObjectFactory getObjectFactory(); /** * The policy the container is running on. * * @return the websocket policy */ - public WebSocketPolicy getPolicy(); + WebSocketPolicy getPolicy(); /** * The SslContextFactory in use by the container. * * @return the SslContextFactory in use by the container (can be null if no SSL context is defined) */ - public SslContextFactory getSslContextFactory(); - + SslContextFactory getSslContextFactory(); + + /** + * Test for if the container has been started. + * + * @return true if container is started and running + */ + boolean isRunning(); + /** * A Session has been opened * * @param session the session that was opened */ - public void onSessionOpened(WebSocketSession session); + void onSessionOpened(WebSocketSession session); /** * A Session has been closed * * @param session the session that was closed */ - public void onSessionClosed(WebSocketSession session); - + void onSessionClosed(WebSocketSession session); } 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 index 423b29cb8bd..aac64ec26ba 100644 --- 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 @@ -20,6 +20,8 @@ package org.eclipse.jetty.websocket.server; import java.io.IOException; +import javax.servlet.ServletContext; + import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.http.pathmap.PathSpec; @@ -44,9 +46,9 @@ public class NativeWebSocketConfiguration extends ContainerLifeCycle implements private final WebSocketServerFactory factory; private final PathMappings mappings = new PathMappings<>(); - public NativeWebSocketConfiguration() + public NativeWebSocketConfiguration(ServletContext context) { - this(new WebSocketServerFactory()); + this(new WebSocketServerFactory(context)); } public NativeWebSocketConfiguration(WebSocketServerFactory webSocketServerFactory) 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 index 87c48e6ac0e..3e9686512c5 100644 --- 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 @@ -33,7 +33,7 @@ public class NativeWebSocketServletContainerInitializer implements ServletContai NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getAttribute(KEY); if (configuration == null) { - configuration = new NativeWebSocketConfiguration(); + configuration = new NativeWebSocketConfiguration(context); context.setAttribute(KEY, configuration); } return configuration; diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java index e59f5b24731..70dba86ebcd 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketHandler.java @@ -53,8 +53,9 @@ public abstract class WebSocketHandler extends HandlerWrapper factory.register(websocketPojo); } } - - private final WebSocketServletFactory webSocketFactory; + + private final ByteBufferPool bufferPool; + private WebSocketServletFactory webSocketFactory; public WebSocketHandler() { @@ -63,10 +64,7 @@ public abstract class WebSocketHandler extends HandlerWrapper public WebSocketHandler(ByteBufferPool bufferPool) { - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - configurePolicy(policy); - webSocketFactory = new WebSocketServerFactory(policy, bufferPool); - addBean(webSocketFactory); + this.bufferPool = bufferPool; } public abstract void configure(WebSocketServletFactory factory); @@ -79,12 +77,18 @@ public abstract class WebSocketHandler extends HandlerWrapper @Override protected void doStart() throws Exception { + WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); + configurePolicy(policy); + webSocketFactory = new WebSocketServerFactory(policy, getServer().getThreadPool(), bufferPool); + addBean(webSocketFactory); configure(webSocketFactory); super.doStart(); } - + public WebSocketServletFactory getWebSocketFactory() { + if (!isRunning()) + throw new IllegalStateException("Not Started yet"); return webSocketFactory; } diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java index 9b0dc7859a6..49f9b5f6cd4 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java @@ -28,12 +28,12 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.servlet.ServletContext; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -48,7 +48,6 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.DecoratedObjectFactory; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.ContainerLifeCycle; @@ -83,7 +82,7 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketContainerScope, WebSocketServletFactory { private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class); - + private final ClassLoader contextClassloader; private final Map handshakes = new HashMap<>(); /** @@ -96,47 +95,58 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc private final EventDriverFactory eventDriverFactory; private final ByteBufferPool bufferPool; private final WebSocketExtensionFactory extensionFactory; + private ServletContext context; // can be null when this factory is used from WebSocketHandler private Executor executor; private List sessionFactories; private WebSocketCreator creator; private List> registeredSocketClasses; private DecoratedObjectFactory objectFactory; - - public WebSocketServerFactory() + + public WebSocketServerFactory(ServletContext context) { - this(WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool()); + this(context, WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool()); } - - public WebSocketServerFactory(WebSocketPolicy policy) + + public WebSocketServerFactory(ServletContext context, ByteBufferPool bufferPool) { - this(policy, new MappedByteBufferPool()); + this(context, WebSocketPolicy.newServerPolicy(), bufferPool); } - - public WebSocketServerFactory(ByteBufferPool bufferPool) + + /** + * Entry point for {@link org.eclipse.jetty.websocket.servlet.WebSocketServletFactory.Loader} + * + * @param context the servlet context + * @param policy the policy to use + */ + public WebSocketServerFactory(ServletContext context, WebSocketPolicy policy) { - this(WebSocketPolicy.newServerPolicy(), bufferPool); + this(context, policy, new MappedByteBufferPool()); } - - public WebSocketServerFactory(WebSocketPolicy policy, ByteBufferPool bufferPool) + + public WebSocketServerFactory(ServletContext context, WebSocketPolicy policy, ByteBufferPool bufferPool) { + Objects.requireNonNull(context, ServletContext.class.getName()); + + this.context = context; + handshakes.put(HandshakeRFC6455.VERSION, new HandshakeRFC6455()); - + addBean(scheduler); addBean(bufferPool); - + this.contextClassloader = Thread.currentThread().getContextClassLoader(); - + this.registeredSocketClasses = new ArrayList<>(); - + this.defaultPolicy = policy; this.eventDriverFactory = new EventDriverFactory(defaultPolicy); this.bufferPool = bufferPool; this.extensionFactory = new WebSocketExtensionFactory(this); - + this.sessionFactories = new ArrayList<>(); this.sessionFactories.add(new WebSocketSessionFactory(this)); this.creator = this; - + // Create supportedVersions List versions = new ArrayList<>(); for (int v : handshakes.keySet()) @@ -155,23 +165,72 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } supportedVersions = rv.toString(); } - + + /** + * Protected entry point for {@link WebSocketHandler} + * + * @param policy the policy to use + * @param executor the executor to use + * @param bufferPool the buffer pool to use + */ + protected WebSocketServerFactory(WebSocketPolicy policy, Executor executor, ByteBufferPool bufferPool) + { + this.objectFactory = new DecoratedObjectFactory(); + this.executor = executor; + + handshakes.put(HandshakeRFC6455.VERSION, new HandshakeRFC6455()); + + addBean(scheduler); + addBean(bufferPool); + + this.contextClassloader = Thread.currentThread().getContextClassLoader(); + + this.registeredSocketClasses = new ArrayList<>(); + + this.defaultPolicy = policy; + this.eventDriverFactory = new EventDriverFactory(defaultPolicy); + this.bufferPool = bufferPool; + this.extensionFactory = new WebSocketExtensionFactory(this); + + this.sessionFactories = new ArrayList<>(); + this.sessionFactories.add(new WebSocketSessionFactory(this)); + this.creator = this; + + // Create supportedVersions + List versions = new ArrayList<>(); + for (int v : handshakes.keySet()) + { + versions.add(v); + } + Collections.sort(versions, Collections.reverseOrder()); // newest first + StringBuilder rv = new StringBuilder(); + for (int v : versions) + { + if (rv.length() > 0) + { + rv.append(", "); + } + rv.append(v); + } + supportedVersions = rv.toString(); + } + public void addSessionListener(WebSocketSession.Listener listener) { listeners.add(listener); } - + public void removeSessionListener(WebSocketSession.Listener listener) { listeners.remove(listener); } - + @Override public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException { return acceptWebSocket(getCreator(), request, response); } - + @Override public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -179,32 +238,32 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc try { Thread.currentThread().setContextClassLoader(contextClassloader); - + // Create Servlet Specific Upgrade Request/Response objects ServletUpgradeRequest sockreq = new ServletUpgradeRequest(request); ServletUpgradeResponse sockresp = new ServletUpgradeResponse(response); - + Object websocketPojo = creator.createWebSocket(sockreq, sockresp); - + // Handle response forbidden (and similar paths) if (sockresp.isCommitted()) { return false; } - + if (websocketPojo == null) { // no creation, sorry sockresp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Endpoint Creation Failed"); return false; } - + // Allow Decorators to do their thing websocketPojo = getObjectFactory().decorate(websocketPojo); - + // Get the original HTTPConnection - HttpConnection connection = (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection"); - + HttpConnection connection = (HttpConnection) request.getAttribute("org.eclipse.jetty.server.HttpConnection"); + // Send the upgrade EventDriver driver = eventDriverFactory.wrap(websocketPojo); return upgrade(connection, sockreq, sockresp, driver); @@ -218,7 +277,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc Thread.currentThread().setContextClassLoader(old); } } - + public void addSessionFactory(SessionFactory sessionFactory) { if (sessionFactories.contains(sessionFactory)) @@ -227,33 +286,20 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } this.sessionFactories.add(sessionFactory); } - + @Override - public void cleanup() + public WebSocketServletFactory createFactory(ServletContext context, WebSocketPolicy policy) { - try - { - this.stop(); - } - catch (Exception e) - { - LOG.warn(e); - } + return new WebSocketServerFactory(context, policy, bufferPool); } - - @Override - public WebSocketServletFactory createFactory(WebSocketPolicy policy) - { - return new WebSocketServerFactory(policy, bufferPool); - } - + private WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection) { if (websocket == null) { throw new InvalidWebSocketException("Unable to create Session from null websocket"); } - + for (SessionFactory impl : sessionFactories) { if (impl.supports(websocket)) @@ -268,10 +314,10 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } } } - + throw new InvalidWebSocketException("Unable to create Session: unrecognized internal EventDriver type: " + websocket.getClass().getName()); } - + /** * Default Creator logic */ @@ -282,12 +328,12 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc { throw new WebSocketException("No WebSockets have been registered with the factory. Cannot use default implementation of WebSocketCreator."); } - + if (registeredSocketClasses.size() > 1) { LOG.warn("You have registered more than 1 websocket object, and are using the default WebSocketCreator! Using first registered websocket."); } - + Class firstClass = registeredSocketClasses.get(0); try { @@ -298,63 +344,76 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc throw new WebSocketException("Unable to create instance of " + firstClass, e); } } - + @Override protected void doStart() throws Exception { - if(this.objectFactory == null) + if(this.objectFactory == null && context != null) { - this.objectFactory = new DecoratedObjectFactory(); + this.objectFactory = (DecoratedObjectFactory) context.getAttribute(DecoratedObjectFactory.ATTR); + if (this.objectFactory == null) + { + throw new RuntimeException("Unable to find required ServletContext attribute: " + DecoratedObjectFactory.ATTR); + } } - + + if(this.executor == null && context != null) + { + ContextHandler contextHandler = ContextHandler.getContextHandler(context); + this.executor = contextHandler.getServer().getThreadPool(); + } + + Objects.requireNonNull(this.objectFactory, DecoratedObjectFactory.class.getName()); + Objects.requireNonNull(this.executor, Executor.class.getName()); + super.doStart(); } - + @Override public ByteBufferPool getBufferPool() { return this.bufferPool; } - + @Override public WebSocketCreator getCreator() { return this.creator; } - + @Override public Executor getExecutor() { return this.executor; } - + public DecoratedObjectFactory getObjectFactory() { return objectFactory; } - + public EventDriverFactory getEventDriverFactory() { return eventDriverFactory; } - + @Override public ExtensionFactory getExtensionFactory() { return extensionFactory; } - + public Collection getOpenSessions() { return getBeans(WebSocketSession.class); } - + @Override public WebSocketPolicy getPolicy() { return defaultPolicy; } - + @Override public SslContextFactory getSslContextFactory() { @@ -363,53 +422,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc */ return null; } - - public void init(ServletContextHandler context) throws ServletException - { - this.objectFactory = (DecoratedObjectFactory)context.getServletContext().getAttribute(DecoratedObjectFactory.ATTR); - if (this.objectFactory == null) - { - this.objectFactory = new DecoratedObjectFactory(); - } - - this.executor = context.getServer().getThreadPool(); - } - - @Override - public void init(ServletContext context) throws ServletException - { - // Setup ObjectFactory - this.objectFactory = (DecoratedObjectFactory)context.getAttribute(DecoratedObjectFactory.ATTR); - if (this.objectFactory == null) - { - this.objectFactory = new DecoratedObjectFactory(); - } - - // Validate Environment - ContextHandler handler = ContextHandler.getContextHandler(context); - - if (handler == null) - { - throw new ServletException("Not running on Jetty, WebSocket support unavailable"); - } - - this.executor = handler.getServer().getThreadPool(); - - try - { - // start lifecycle - start(); - } - catch (ServletException e) - { - throw e; - } - catch (Exception e) - { - throw new ServletException(e); - } - } - + @Override public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response) { @@ -434,7 +447,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc // no "Connection: upgrade" header present. return false; } - + // Test for "Upgrade" token boolean foundUpgradeToken = false; Iterator iter = QuoteUtil.splitAt(connection, ","); @@ -447,7 +460,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc break; } } - + if (!foundUpgradeToken) { return false; @@ -458,30 +471,30 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc // not a "GET" request (not a websocket upgrade) return false; } - + if (!"HTTP/1.1".equals(request.getProtocol())) { LOG.debug("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])"); return false; } - + return true; } - + @Override public void onSessionOpened(WebSocketSession session) { addManaged(session); notifySessionListeners(listener -> listener.onOpened(session)); } - + @Override public void onSessionClosed(WebSocketSession session) { removeBean(session); notifySessionListeners(listener -> listener.onClosed(session)); } - + private void notifySessionListeners(Consumer consumer) { for (WebSocketSession.Listener listener : listeners) @@ -496,30 +509,29 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } } } - + @Override public void register(Class websocketPojo) { registeredSocketClasses.add(websocketPojo); } - + @Override public void setCreator(WebSocketCreator creator) { this.creator = creator; } - + /** * Upgrade the request/response to a WebSocket Connection. *

* This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the * connection. * - * @param http the raw http connection - * @param request The request to upgrade + * @param http the raw http connection + * @param request The request to upgrade * @param response The response to upgrade - * @param driver The websocket handler implementation to use - * @throws IOException + * @param driver The websocket handler implementation to use */ private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver) throws IOException { @@ -531,14 +543,14 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc { throw new IllegalStateException("Not a 'HTTP/1.1' request"); } - + int version = request.getHeaderInt("Sec-WebSocket-Version"); if (version < 0) { // Old pre-RFC version specifications (header not present in RFC-6455) version = request.getHeaderInt("Sec-WebSocket-Draft"); } - + WebSocketHandshake handshaker = handshakes.get(version); if (handshaker == null) { @@ -563,14 +575,14 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } warn.append(": [").append(supportedVersions).append("]"); LOG.warn(warn.toString()); - + // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol // Using the examples as outlined response.setHeader("Sec-WebSocket-Version", supportedVersions); response.sendError(HttpStatus.BAD_REQUEST_400, "Unsupported websocket version specification"); return false; } - + // Initialize / Negotiate Extensions ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory()); // The JSR allows for the extensions to be pre-negotiated, filtered, etc... @@ -585,26 +597,26 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc // Use raw extension list from request extensionStack.negotiate(request.getExtensions()); } - + // Get original HTTP connection EndPoint endp = http.getEndPoint(); Connector connector = http.getConnector(); Executor executor = connector.getExecutor(); ByteBufferPool bufferPool = connector.getByteBufferPool(); - + // Setup websocket connection AbstractWebSocketConnection wsConnection = new WebSocketServerConnection(endp, executor, scheduler, driver.getPolicy(), bufferPool); - + extensionStack.setPolicy(driver.getPolicy()); extensionStack.configure(wsConnection.getParser()); extensionStack.configure(wsConnection.getGenerator()); - + if (LOG.isDebugEnabled()) { LOG.debug("HttpConnection: {}", http); LOG.debug("WebSocketConnection: {}", wsConnection); } - + // Setup Session WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection); session.setPolicy(driver.getPolicy()); @@ -613,51 +625,51 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc response.setExtensions(extensionStack.getNegotiatedExtensions()); session.setUpgradeResponse(response); wsConnection.addListener(session); - + // Setup Incoming Routing wsConnection.setNextIncomingFrames(extensionStack); extensionStack.setNextIncoming(session); - + // Setup Outgoing Routing session.setOutgoingHandler(extensionStack); extensionStack.setNextOutgoing(wsConnection); - + // Start Components session.addManaged(extensionStack); this.addManaged(session); - + if (session.isFailed()) { throw new IOException("Session failed to start"); } - + // Tell jetty about the new upgraded connection request.setServletAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, wsConnection); - + if (LOG.isDebugEnabled()) LOG.debug("Handshake Response: {}", handshaker); - + if (getSendServerVersion(connector)) - response.setHeader("Server",HttpConfiguration.SERVER_VERSION); - + response.setHeader("Server", HttpConfiguration.SERVER_VERSION); + // Process (version specific) handshake response handshaker.doHandshakeResponse(request, response); - + if (LOG.isDebugEnabled()) LOG.debug("Websocket upgrade {} {} {} {}", request.getRequestURI(), version, response.getAcceptedSubProtocol(), wsConnection); - + return true; } - + private boolean getSendServerVersion(Connector connector) { ConnectionFactory connFactory = connector.getConnectionFactory(HttpVersion.HTTP_1_1.asString()); if (connFactory == null) return false; - + if (connFactory instanceof HttpConnectionFactory) { - HttpConfiguration httpConf = ((HttpConnectionFactory)connFactory).getHttpConfiguration(); + HttpConfiguration httpConf = ((HttpConnectionFactory) connFactory).getHttpConfiguration(); if (httpConf != null) return httpConf.getSendServerVersion(); } 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 e9fd1db5624..039ce962506 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 @@ -34,7 +34,6 @@ 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.server.handler.ContextHandler; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -45,7 +44,6 @@ 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.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @@ -55,11 +53,9 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @ManagedObject("WebSocket Upgrade Filter") public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, MappedWebSocketCreator, Dumpable { + private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class); 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); - private boolean localMapper; - private boolean localFactory; public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) throws ServletException { @@ -122,9 +118,9 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, // do nothing } - public WebSocketUpgradeFilter(WebSocketPolicy policy, ByteBufferPool bufferPool) + public WebSocketUpgradeFilter(WebSocketServerFactory factory) { - this(new NativeWebSocketConfiguration(new WebSocketServerFactory(policy, bufferPool))); + this(new NativeWebSocketConfiguration(factory)); } public WebSocketUpgradeFilter(NativeWebSocketConfiguration 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 0797114d3b0..83ec7273ac4 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 @@ -30,20 +30,21 @@ 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.servlet.ServletContextHandler; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements MappedWebSocketCreator { private NativeWebSocketConfiguration configuration; - public WebSocketUpgradeHandlerWrapper() + public WebSocketUpgradeHandlerWrapper(ServletContextHandler context) { - this(new MappedByteBufferPool()); + this(context, new MappedByteBufferPool()); } - public WebSocketUpgradeHandlerWrapper(ByteBufferPool bufferPool) + public WebSocketUpgradeHandlerWrapper(ServletContextHandler context, ByteBufferPool bufferPool) { - this.configuration = new NativeWebSocketConfiguration(new WebSocketServerFactory(bufferPool)); + this.configuration = new NativeWebSocketConfiguration(new WebSocketServerFactory(context.getServletContext(), bufferPool)); } @Override 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 index 0e2e7a46d13..cf880774b96 100644 --- 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 @@ -31,7 +31,7 @@ public class InfoContextListener implements WebSocketCreator, ServletContextList @Override public void contextInitialized(ServletContextEvent sce) { - NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(); + NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(sce.getServletContext()); configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); configuration.addMapping(new ServletPathSpec("/info/*"), this); sce.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration); 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 index 14a55761790..f08e417ef63 100644 --- 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 @@ -163,7 +163,7 @@ public class WebSocketUpgradeFilterTest server.setHandler(context); context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(); + NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext()); configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator); context.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration); diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java index bf99fc1e02f..d233cfd480f 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java @@ -90,7 +90,14 @@ public abstract class WebSocketServlet extends HttpServlet @Override public void destroy() { - factory.cleanup(); + try + { + factory.stop(); + } + catch (Exception ignore) + { + // ignore; + } } /** @@ -126,14 +133,13 @@ public abstract class WebSocketServlet extends HttpServlet { policy.setInputBufferSize(Integer.parseInt(max)); } - - factory = WebSocketServletFactory.Loader.create(policy); + + ServletContext ctx = getServletContext(); + factory = WebSocketServletFactory.Loader.load(ctx, policy); configure(factory); - ServletContext ctx = getServletContext(); - - factory.init(ctx); + factory.start(); ctx.setAttribute(WebSocketServletFactory.class.getName(),factory); } diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java index 80046b2bcad..c7f6de5d382 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java @@ -19,8 +19,8 @@ package org.eclipse.jetty.websocket.servlet; import java.io.IOException; -import java.util.Iterator; -import java.util.ServiceLoader; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -35,77 +35,62 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; */ public interface WebSocketServletFactory { - public static class Loader + class Loader { - private static WebSocketServletFactory INSTANCE; - - public static WebSocketServletFactory create(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException + final static String DEFAULT_IMPL = "org.eclipse.jetty.websocket.server.WebSocketServerFactory"; + + public static WebSocketServletFactory load(ServletContext ctx, WebSocketPolicy policy) { - return load().createFactory(policy); - } - - public static WebSocketServletFactory load() throws ClassNotFoundException, InstantiationException, IllegalAccessException - { - if (INSTANCE != null) + try { - return INSTANCE; + Class wsClazz = + (Class) Class.forName(DEFAULT_IMPL); + Constructor ctor = wsClazz.getDeclaredConstructor(new Class[]{ServletContext.class, WebSocketPolicy.class}); + return ctor.newInstance(ctx, policy); } - WebSocketServletFactory baseFactory; - Iterator factories = ServiceLoader.load(WebSocketServletFactory.class).iterator(); - - if (factories.hasNext()) + catch (ClassNotFoundException e) { - baseFactory = factories.next(); + throw new RuntimeException("Unable to load " + DEFAULT_IMPL, e); } - else + catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { - // Load the default class if ServiceLoader mechanism isn't valid in this environment. (such as OSGi) - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - @SuppressWarnings("unchecked") - Class wssf = (Class)loader - .loadClass("org.eclipse.jetty.websocket.server.WebSocketServerFactory"); - baseFactory = wssf.newInstance(); + throw new RuntimeException("Unable to instantiate " + DEFAULT_IMPL, e); } - - INSTANCE = baseFactory; - return INSTANCE; } } - - public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException; - - public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException; - - public void cleanup(); - - public WebSocketServletFactory createFactory(WebSocketPolicy policy); - - public abstract WebSocketCreator getCreator(); - - public abstract ExtensionFactory getExtensionFactory(); - + + boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException; + + boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException; + + void start() throws Exception; + void stop() throws Exception; + + WebSocketServletFactory createFactory(ServletContext context, WebSocketPolicy policy); + + WebSocketCreator getCreator(); + + ExtensionFactory getExtensionFactory(); + /** * Get the base policy in use for WebSockets. *

* Note: individual WebSocket implementations can override some of the values in here by using the {@link WebSocket @WebSocket} annotation. - * + * * @return the base policy */ - public WebSocketPolicy getPolicy(); - - public void init(ServletContext servletContext) throws Exception; - - public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response); - + WebSocketPolicy getPolicy(); + + boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response); + /** * Register a websocket class pojo with the default {@link WebSocketCreator}. *

* Note: only required if using the default {@link WebSocketCreator} provided by this factory. - * - * @param websocketPojo - * the class to instantiate for each incoming websocket upgrade request. + * + * @param websocketPojo the class to instantiate for each incoming websocket upgrade request. */ - public void register(Class websocketPojo); - - public abstract void setCreator(WebSocketCreator creator); + void register(Class websocketPojo); + + void setCreator(WebSocketCreator creator); } From 63d93160f1b40c49b8b760fe72f707ec642c4ee6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 30 Nov 2016 10:16:25 -0700 Subject: [PATCH 7/7] Issue #1124 - post review cleanup of changes with @sbordet --- .../WebSocketServerContainerInitializer.java | 1 - .../server/WebSocketServerFactory.java | 71 ++++--------------- .../server/WebSocketUpgradeFilter.java | 15 ++-- .../InfoContextAltAttributeListener.java | 52 ++++++++++++++ .../server/WebSocketUpgradeFilterTest.java | 31 ++++++-- .../wsuf-alt-config-via-listener.xml | 26 +++++++ .../servlet/WebSocketServletFactory.java | 2 - 7 files changed, 130 insertions(+), 68 deletions(-) create mode 100644 jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAltAttributeListener.java create mode 100644 jetty-websocket/websocket-server/src/test/resources/wsuf-alt-config-via-listener.xml 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 f40f633fb61..67cf9ad6c5e 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 @@ -146,7 +146,6 @@ 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/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java index 49f9b5f6cd4..a0c32eee7b0 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java @@ -85,9 +85,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc private final ClassLoader contextClassloader; private final Map handshakes = new HashMap<>(); - /** - * Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler. - */ + // TODO: obtain shared (per server scheduler, somehow) private final Scheduler scheduler = new ScheduledExecutorScheduler(); private final List listeners = new CopyOnWriteArrayList<>(); private final String supportedVersions; @@ -95,12 +93,12 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc private final EventDriverFactory eventDriverFactory; private final ByteBufferPool bufferPool; private final WebSocketExtensionFactory extensionFactory; - private ServletContext context; // can be null when this factory is used from WebSocketHandler + private final ServletContext context; // can be null when this factory is used from WebSocketHandler + private final List sessionFactories = new ArrayList<>(); + private final List> registeredSocketClasses = new ArrayList<>(); private Executor executor; - private List sessionFactories; - private WebSocketCreator creator; - private List> registeredSocketClasses; private DecoratedObjectFactory objectFactory; + private WebSocketCreator creator; public WebSocketServerFactory(ServletContext context) { @@ -125,45 +123,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc public WebSocketServerFactory(ServletContext context, WebSocketPolicy policy, ByteBufferPool bufferPool) { - Objects.requireNonNull(context, ServletContext.class.getName()); - - this.context = context; - - handshakes.put(HandshakeRFC6455.VERSION, new HandshakeRFC6455()); - - addBean(scheduler); - addBean(bufferPool); - - this.contextClassloader = Thread.currentThread().getContextClassLoader(); - - this.registeredSocketClasses = new ArrayList<>(); - - this.defaultPolicy = policy; - this.eventDriverFactory = new EventDriverFactory(defaultPolicy); - this.bufferPool = bufferPool; - this.extensionFactory = new WebSocketExtensionFactory(this); - - this.sessionFactories = new ArrayList<>(); - this.sessionFactories.add(new WebSocketSessionFactory(this)); - this.creator = this; - - // Create supportedVersions - List versions = new ArrayList<>(); - for (int v : handshakes.keySet()) - { - versions.add(v); - } - Collections.sort(versions, Collections.reverseOrder()); // newest first - StringBuilder rv = new StringBuilder(); - for (int v : versions) - { - if (rv.length() > 0) - { - rv.append(", "); - } - rv.append(v); - } - supportedVersions = rv.toString(); + this(Objects.requireNonNull(context, ServletContext.class.getName()), policy, null, null, bufferPool); } /** @@ -175,7 +135,13 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc */ protected WebSocketServerFactory(WebSocketPolicy policy, Executor executor, ByteBufferPool bufferPool) { - this.objectFactory = new DecoratedObjectFactory(); + this(null, policy, new DecoratedObjectFactory(), executor, bufferPool); + } + + private WebSocketServerFactory(ServletContext context, WebSocketPolicy policy, DecoratedObjectFactory objectFactory, Executor executor, ByteBufferPool bufferPool) + { + this.context = context; + this.objectFactory = objectFactory; this.executor = executor; handshakes.put(HandshakeRFC6455.VERSION, new HandshakeRFC6455()); @@ -185,14 +151,11 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc this.contextClassloader = Thread.currentThread().getContextClassLoader(); - this.registeredSocketClasses = new ArrayList<>(); - this.defaultPolicy = policy; this.eventDriverFactory = new EventDriverFactory(defaultPolicy); this.bufferPool = bufferPool; this.extensionFactory = new WebSocketExtensionFactory(this); - this.sessionFactories = new ArrayList<>(); this.sessionFactories.add(new WebSocketSessionFactory(this)); this.creator = this; @@ -287,12 +250,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc this.sessionFactories.add(sessionFactory); } - @Override - public WebSocketServletFactory createFactory(ServletContext context, WebSocketPolicy policy) - { - return new WebSocketServerFactory(context, policy, bufferPool); - } - private WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection) { if (websocket == null) @@ -353,7 +310,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc this.objectFactory = (DecoratedObjectFactory) context.getAttribute(DecoratedObjectFactory.ATTR); if (this.objectFactory == null) { - throw new RuntimeException("Unable to find required ServletContext attribute: " + DecoratedObjectFactory.ATTR); + throw new IllegalStateException("Unable to find required ServletContext attribute: " + DecoratedObjectFactory.ATTR); } } 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 039ce962506..85f97b3b52e 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 @@ -39,7 +39,6 @@ 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; @@ -51,7 +50,7 @@ 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 AbstractLifeCycle implements Filter, MappedWebSocketCreator, Dumpable +public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, Dumpable { private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class); public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey"; @@ -111,6 +110,7 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, } private NativeWebSocketConfiguration configuration; + private boolean localConfiguration = false; private boolean alreadySetToAttribute = false; public WebSocketUpgradeFilter() @@ -150,7 +150,10 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, try { alreadySetToAttribute = false; - configuration.stop(); + if(localConfiguration) + { + configuration.stop(); + } } catch (Exception e) { @@ -310,7 +313,11 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, } } - this.configuration.start(); + if(!this.configuration.isRunning()) + { + localConfiguration = true; + this.configuration.start(); + } String max = config.getInitParameter("maxIdleTime"); if (max != null) diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAltAttributeListener.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAltAttributeListener.java new file mode 100644 index 00000000000..09b5da07ca2 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/InfoContextAltAttributeListener.java @@ -0,0 +1,52 @@ +// +// ======================================================================== +// 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 InfoContextAltAttributeListener implements WebSocketCreator, ServletContextListener +{ + private static final String ATTR = "alt.config"; + + @Override + public void contextInitialized(ServletContextEvent sce) + { + NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(sce.getServletContext()); + configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024); + configuration.addMapping(new ServletPathSpec("/info/*"), this); + sce.getServletContext().setAttribute(ATTR, 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/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java index f08e417ef63..fabffcc3405 100644 --- 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 @@ -208,10 +208,10 @@ public class WebSocketUpgradeFilterTest File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml"); WSServer server = new WSServer(testDir, "/"); - + + server.copyWebInf("wsuf-config-via-listener.xml"); server.copyClass(InfoSocket.class); server.copyClass(InfoContextAttributeListener.class); - server.copyWebInf("wsuf-config-via-listener.xml"); server.start(); WebAppContext webapp = server.createWebAppContext(); @@ -231,10 +231,33 @@ public class WebSocketUpgradeFilterTest File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml"); WSServer server = new WSServer(testDir, "/"); - + + server.copyWebInf("wsuf-config-via-servlet-init.xml"); server.copyClass(InfoSocket.class); server.copyClass(InfoServlet.class); - server.copyWebInf("wsuf-config-via-servlet-init.xml"); + server.start(); + + WebAppContext webapp = server.createWebAppContext(); + server.deployWebapp(webapp); + + return server.getServer(); + } + }}); + + // xml based, wsuf, on alternate url-pattern and config attribute location + + cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/alt-config", new ServerProvider() + { + @Override + public Server newServer() throws Exception + { + File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml"); + + WSServer server = new WSServer(testDir, "/"); + + server.copyWebInf("wsuf-alt-config-via-listener.xml"); + server.copyClass(InfoSocket.class); + server.copyClass(InfoContextAltAttributeListener.class); server.start(); WebAppContext webapp = server.createWebAppContext(); diff --git a/jetty-websocket/websocket-server/src/test/resources/wsuf-alt-config-via-listener.xml b/jetty-websocket/websocket-server/src/test/resources/wsuf-alt-config-via-listener.xml new file mode 100644 index 00000000000..4321a5cb0f6 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/resources/wsuf-alt-config-via-listener.xml @@ -0,0 +1,26 @@ + + + + + org.eclipse.jetty.websocket.server.InfoContextAltAttributeListener + + + + wsuf-alt + org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter + + configAttributeKey + alt.config + + + + + wsuf-alt + /info/* + + diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java index c7f6de5d382..72c14782346 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.java @@ -66,8 +66,6 @@ public interface WebSocketServletFactory void start() throws Exception; void stop() throws Exception; - WebSocketServletFactory createFactory(ServletContext context, WebSocketPolicy policy); - WebSocketCreator getCreator(); ExtensionFactory getExtensionFactory();