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 9c476f622c9..274a86a1c33 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,6 +23,7 @@ import java.util.Map; import javax.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; @@ -38,7 +39,7 @@ public class PathParamServerEndpointConfig extends BasicServerEndpointConfig imp super(containerScope, 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 62f8c3ce297..bd81e04b921 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 @@ -40,28 +40,25 @@ 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.server.MappedWebSocketCreator; +import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration; import org.eclipse.jetty.websocket.server.WebSocketServerFactory; 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; private List> deferredEndpointClasses; private List deferredEndpointConfigs; - public ServerContainer(MappedWebSocketCreator creator, WebSocketServerFactory factory, Executor executor) + public ServerContainer(NativeWebSocketConfiguration configuration, Executor executor) { - super(factory); - this.mappedCreator = creator; - this.webSocketServerFactory = factory; - EventDriverFactory eventDriverFactory = this.webSocketServerFactory.getEventDriverFactory(); + super(configuration.getFactory()); + this.configuration = configuration; + EventDriverFactory eventDriverFactory = this.configuration.getFactory().getEventDriverFactory(); eventDriverFactory.addImplementation(new JsrServerEndpointImpl()); eventDriverFactory.addImplementation(new JsrServerExtendsEndpointImpl()); - this.webSocketServerFactory.addSessionFactory(new JsrSessionFactory(this)); - addBean(webSocketServerFactory); + this.configuration.getFactory().addSessionFactory(new JsrSessionFactory(this)); } public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path) @@ -102,8 +99,8 @@ public class ServerContainer extends ClientContainer implements javax.websocket. private void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException { - JsrCreator creator = new JsrCreator(this,metadata,webSocketServerFactory.getExtensionFactory()); - mappedCreator.addMapping(new UriTemplatePathSpec(metadata.getPath()),creator); + JsrCreator creator = new JsrCreator(this,metadata,this.configuration.getFactory().getExtensionFactory()); + this.configuration.addMapping(new UriTemplatePathSpec(metadata.getPath()),creator); } @Override @@ -154,13 +151,6 @@ public class ServerContainer extends ClientContainer implements javax.websocket. } } - @Override - protected void doStop() throws Exception - { - mappedCreator.getMappings().reset(); - super.doStop(); - } - public ServerEndpointMetadata getServerEndpointMetadata(final Class endpoint, final ServerEndpointConfig config) throws DeploymentException { ServerEndpointMetadata metadata = null; @@ -193,36 +183,41 @@ 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(); + } + + public WebSocketServerFactory getWebSocketServerFactory() + { + return this.configuration.getFactory(); } @Override public void setAsyncSendTimeout(long ms) { super.setAsyncSendTimeout(ms); - webSocketServerFactory.getPolicy().setAsyncWriteTimeout(ms); + this.configuration.getPolicy().setAsyncWriteTimeout(ms); } @Override @@ -230,16 +225,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 @@ -247,26 +242,26 @@ 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); } @Override public void onSessionClosed(WebSocketSession session) { - webSocketServerFactory.onSessionClosed(session); + getWebSocketServerFactory().onSessionClosed(session); } @Override public void onSessionOpened(WebSocketSession session) { - webSocketServerFactory.onSessionOpened(session); + getWebSocketServerFactory().onSessionOpened(session); } @Override public Set getOpenSessions() { - return new HashSet<>(webSocketServerFactory.getBeans(Session.class)); + return new HashSet<>(getWebSocketServerFactory().getBeans(Session.class)); } } 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 82c35278cf5..f40f633fb61 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 @@ -33,18 +33,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( @@ -137,18 +133,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); @@ -156,6 +146,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 eb927386578..2917d5a0605 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,21 +18,27 @@ package org.eclipse.jetty.websocket.jsr356.server; -import org.eclipse.jetty.http.pathmap.PathMappings; +import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.websocket.server.MappedWebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; public class DummyCreator implements MappedWebSocketCreator { + @Override + public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator) + { + /* do nothing */ + } + @Override public void addMapping(PathSpec spec, WebSocketCreator creator) { /* do nothing */ } - + @Override - public PathMappings getMappings() + 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 7645e65d7f4..e40a8933785 100644 --- a/jetty-websocket/websocket-server/pom.xml +++ b/jetty-websocket/websocket-server/pom.xml @@ -79,6 +79,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 bbc8100658b..80397852b50 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,8 +18,8 @@ package org.eclipse.jetty.websocket.server; -import org.eclipse.jetty.http.pathmap.PathMappings; -import org.eclipse.jetty.http.pathmap.PathSpec; +import org.eclipse.jetty.http.pathmap.MappedResource; +import org.eclipse.jetty.websocket.server.pathmap.PathSpec; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; /** @@ -27,7 +27,32 @@ 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 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..423b29cb8bd --- /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.getSpec()), creator); + } + else if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec) + { + addMapping(new RegexPathSpec(spec.getSpec()), 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 6ce8d190e7d..e9fd1db5624 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 @@ -33,12 +33,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.pathmap.MappedResource; -import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.http.pathmap.PathSpec; -import org.eclipse.jetty.http.pathmap.RegexPathSpec; -import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.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; @@ -51,6 +47,7 @@ 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; /** * Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects. @@ -59,6 +56,7 @@ import org.eclipse.jetty.websocket.servlet.WebSocketCreator; 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); private boolean localMapper; private boolean localFactory; @@ -73,7 +71,8 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle 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"; @@ -115,69 +114,67 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle 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 use new {@link #addMapping(org.eclipse.jetty.http.pathmap.PathSpec, WebSocketCreator)} instead */ @Deprecated + @Override 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.getSpec()), creator); - } - else if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec) - { - addMapping(new RegexPathSpec(spec.getSpec()), creator); - } - else - { - throw new RuntimeException("Unsupported (Deprecated) PathSpec implementation: " + spec.getClass().getName()); - } + configuration.addMapping(spec, creator); } @Override public void destroy() { - if (localFactory) + try { - factory.cleanup(); + alreadySetToAttribute = false; + configuration.stop(); } - if (localMapper) + catch (Exception e) { - mappedWebSocketCreator.getMappings().reset(); + 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 @@ -206,14 +203,9 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle 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; @@ -267,68 +259,85 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle 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 public void init(FilterConfig config) throws ServletException { - fname = config.getFilterName(); - try { - ServletContext context = config.getServletContext(); - - mappedWebSocketCreator = (MappedWebSocketCreator) context.getAttribute(CREATOR_KEY); - if (mappedWebSocketCreator == null) + String configurationKey = config.getInitParameter(CONFIG_ATTRIBUTE_KEY); + if (configurationKey == null) { - mappedWebSocketCreator = new DefaultMappedWebSocketCreator(); - localMapper = true; + configurationKey = NativeWebSocketConfiguration.class.getName(); } - factory = (WebSocketServerFactory) context.getAttribute(FACTORY_KEY); - if (factory == null) + if (configuration == null) { - factory = new WebSocketServerFactory(policy, bufferPool); - localFactory = true; + 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 + "'"); + } } - factory.init(context); + 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); + } + } + + 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); @@ -338,33 +347,22 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter, key = WebSocketUpgradeFilter.class.getName(); } - setToAttribute(context, key); - - factory.start(); + // Set instance of this filter to context attribute + setToAttribute(config.getServletContext(), key); } - 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 @@ -389,6 +387,6 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle 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 ce6fff924f4..0797114d3b0 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 @@ -25,7 +25,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.pathmap.MappedResource; -import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; @@ -35,8 +34,7 @@ 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,41 @@ 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 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 +91,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/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/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/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/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/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 62648c59797..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.http.pathmap.PathMappings; -import org.eclipse.jetty.http.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..14a55761790 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java @@ -0,0 +1,291 @@ +// +// ======================================================================== +// 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(InfoContextAttributeListener.class); + server.copyWebInf("wsuf-config-via-listener.xml"); + server.start(); + + WebAppContext webapp = server.createWebAppContext(); + server.deployWebapp(webapp); + + 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; + } + + 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..8e75569ae23 --- /dev/null +++ b/jetty-websocket/websocket-server/src/test/resources/wsuf-config-via-listener.xml @@ -0,0 +1,22 @@ + + + + + org.eclipse.jetty.websocket.server.InfoContextAttributeListener + + + + wsuf + org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter + + + + wsuf + /* + + 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 + /* + +