diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index d53b4417dab..c6df710c723 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -722,6 +722,16 @@ public class ServletContextHandler extends ContextHandler _objFactory.destroy(filter); } + public static ServletContextHandler getServletContextHandler(ServletContext context) + { + ContextHandler handler = getContextHandler(context); + if (handler == null) + return null; + if (handler instanceof ServletContextHandler) + return (ServletContextHandler) handler; + return null; + } + /* ------------------------------------------------------------ */ public static class JspPropertyGroup implements JspPropertyGroupDescriptor { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java new file mode 100644 index 00000000000..648248f4401 --- /dev/null +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java @@ -0,0 +1,184 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.servlet.listener; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * Utility Methods for manual execution of {@link javax.servlet.ServletContainerInitializer} when + * using Embedded Jetty. + */ +public final class ContainerInitializer +{ + /** + * Utility Method to allow for manual execution of {@link javax.servlet.ServletContainerInitializer} when + * using Embedded Jetty. + * + *
+     * ServletContextHandler context = new ServletContextHandler();
+     * ServletContainerInitializer corpSci = new MyCorporateSCI();
+     * context.addEventListener(ContainerInitializer.asContextListener(corpSci));
+     * 
+ * + *

+ * The {@link ServletContainerInitializer} will have its {@link ServletContainerInitializer#onStartup(Set, ServletContext)} + * method called with the manually configured list of {@code Set> c} set. + * In other words, this usage does not perform bytecode or annotation scanning against the classes in + * your {@code ServletContextHandler} or {@code WebAppContext}. + *

+ * + * @param sci the {@link ServletContainerInitializer} to call + * @return the {@link ServletContextListener} wrapping the SCI + * @see ServletContainerInitializerServletContextListener#addClasses(Class[]) + * @see ServletContainerInitializerServletContextListener#addClasses(String...) + */ + public static ServletContainerInitializerServletContextListener asContextListener(ServletContainerInitializer sci) + { + return new ServletContainerInitializerServletContextListener(sci); + } + + public static class ServletContainerInitializerServletContextListener implements ServletContextListener + { + private final ServletContainerInitializer sci; + private Set classNames; + private Set> classes = new HashSet<>(); + private Consumer afterStartupConsumer; + + public ServletContainerInitializerServletContextListener(ServletContainerInitializer sci) + { + this.sci = sci; + } + + /** + * Add classes to be passed to the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} call. + *

+ * Note that these classes will be loaded using the context classloader for the ServletContext + * initialization phase. + *

+ * + * @param classNames the class names to load and pass into the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} call + * @return this configured {@link ServletContainerInitializerServletContextListener} instance. + */ + public ServletContainerInitializerServletContextListener addClasses(String... classNames) + { + if (this.classNames == null) + { + this.classNames = new HashSet<>(); + } + this.classNames.addAll(Arrays.asList(classNames)); + return this; + } + + /** + * Add classes to be passed to the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} call. + *

+ * Note that these classes will exist on the classloader that was used to call this method. + * If you want the classes to be loaded using the context classloader for the ServletContext + * then use the String form of the classes via the {@link #addClasses(String...)} method. + *

+ * + * @param classes the classes to pass into the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} call + * @return this configured {@link ServletContainerInitializerServletContextListener} instance. + */ + public ServletContainerInitializerServletContextListener addClasses(Class... classes) + { + this.classes.addAll(Arrays.asList(classes)); + return this; + } + + /** + * Add a optional consumer to execute once the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} has + * been called successfully. + *

+ * This would be for actions to perform on a ServletContext once this specific SCI has completed + * its execution. Actions that would require specific configurations that the SCI provides to be present on the + * ServletContext to function properly. + *

+ *

+ * This consumer is typically used for Embedded Jetty users to configure Jetty for their specific needs. + *

+ * + * @param consumer the consumer to execute after the SCI has executed + * @return this configured {@link ServletContainerInitializerServletContextListener} instance. + */ + public ServletContainerInitializerServletContextListener afterStartup(Consumer consumer) + { + this.afterStartupConsumer = consumer; + return this; + } + + @Override + public void contextInitialized(ServletContextEvent sce) + { + ServletContext servletContext = sce.getServletContext(); + try + { + sci.onStartup(getClasses(), servletContext); + if (afterStartupConsumer != null) + { + afterStartupConsumer.accept(servletContext); + } + } + catch (RuntimeException rte) + { + throw rte; + } + catch (Throwable cause) + { + throw new RuntimeException(cause); + } + } + + public Set> getClasses() + { + if (classNames != null && !classNames.isEmpty()) + { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + for (String className : classNames) + { + try + { + Class clazz = cl.loadClass(className); + classes.add(clazz); + } + catch (ClassNotFoundException e) + { + throw new RuntimeException("Unable to find class: " + className, e); + } + } + } + + return classes; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + // ignore + } + } +} diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/RestartContextTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/RestartContextTest.java new file mode 100644 index 00000000000..fd390547208 --- /dev/null +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/RestartContextTest.java @@ -0,0 +1,212 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.jsr356.server; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.websocket.DeploymentException; +import javax.websocket.OnMessage; +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class RestartContextTest +{ + private Server server; + private WebSocketClient client; + + @BeforeEach + public void startClient() throws Exception + { + client = new WebSocketClient(); + client.start(); + } + + @AfterEach + public void stopClient() throws Exception + { + client.stop(); + } + + @AfterEach + public void startServer() throws Exception + { + server.stop(); + } + + @Test + public void testStartStopStart_ServletContextListener() throws Exception + { + server = new Server(); + + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + // Setup Context + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + WebSocketServerContainerInitializer.configure(context, null); + // late initialization via my own ServletContextListener + context.addEventListener(new AddEndpointListener()); + + // Setup handler tree + HandlerList handlers = new HandlerList(); + handlers.addHandler(context); + handlers.addHandler(new DefaultHandler()); + + // Add handler tree to server + server.setHandler(handlers); + + // Start server + server.start(); + + // verify functionality + verifyWebSocketEcho(server.getURI().resolve("/echo")); + + // Stop server + server.stop(); + + // Start server (again) + server.start(); + + // verify functionality (again) + verifyWebSocketEcho(server.getURI().resolve("/echo")); + } + + @Test + public void testStartStopStart_Configurator() throws Exception + { + server = new Server(); + + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + // Setup Context + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + WebSocketServerContainerInitializer.configure(context, (servletContext, serverContainer) -> { + // Add endpoint via configurator + serverContainer.addEndpoint(EchoEndpoint.class); + }); + + // Setup handler tree + HandlerList handlers = new HandlerList(); + handlers.addHandler(context); + handlers.addHandler(new DefaultHandler()); + + // Add handler tree to server + server.setHandler(handlers); + + // Start server + server.start(); + + // verify functionality + verifyWebSocketEcho(server.getURI().resolve("/echo")); + + // Stop server + server.stop(); + + // Start server (again) + server.start(); + + // verify functionality (again) + verifyWebSocketEcho(server.getURI().resolve("/echo")); + } + + private void verifyWebSocketEcho(URI endpointUri) throws URISyntaxException, IOException, ExecutionException, InterruptedException + { + ClientEndpoint endpoint = new ClientEndpoint(); + Future fut = client.connect(endpoint, WSURI.toWebsocket(endpointUri)); + try(Session session = fut.get()) + { + session.getRemote().sendString("Test Echo"); + String msg = endpoint.messages.poll(5, TimeUnit.SECONDS); + assertThat("msg", msg, is("Test Echo")); + } + } + + public static class AddEndpointListener implements ServletContextListener + { + @Override + public void contextInitialized(ServletContextEvent sce) + { + ServerContainer container = (ServerContainer) sce.getServletContext().getAttribute(javax.websocket.server.ServerContainer.class.getName()); + try + { + container.addEndpoint(EchoEndpoint.class); + } + catch (DeploymentException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + } + } + + @ServerEndpoint("/echo") + public static class EchoEndpoint + { + @OnMessage + public String onMessage(String msg) + { + return msg; + } + } + + @WebSocket + public static class ClientEndpoint + { + public LinkedBlockingQueue messages = new LinkedBlockingQueue<>(); + + @OnWebSocketMessage + public void onMessage(String msg) + { + this.messages.offer(msg); + } + } +} diff --git a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerContainer.java b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerContainer.java index 4dd48323d34..83834672c51 100644 --- a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerContainer.java +++ b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServerContainer.java @@ -22,11 +22,9 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Supplier; - import javax.servlet.ServletContext; import javax.websocket.DeploymentException; import javax.websocket.EndpointConfig; -import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; @@ -51,7 +49,7 @@ import org.eclipse.jetty.websocket.servlet.WebSocketMapping; @ManagedObject("JSR356 Server Container") public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer implements javax.websocket.server.ServerContainer, LifeCycle.Listener { - public static final String JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE = ServerContainer.class.getName(); + public static final String JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE = javax.websocket.server.ServerContainer.class.getName(); private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainer.class); public static JavaxWebSocketServerContainer ensureContainer(ServletContext servletContext) diff --git a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServletContainerInitializer.java b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServletContainerInitializer.java index c642f5fbf91..6342444da2f 100644 --- a/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServletContainerInitializer.java +++ b/jetty-websocket/javax-websocket-server/src/main/java/org/eclipse/jetty/websocket/javax/server/JavaxWebSocketServletContainerInitializer.java @@ -27,11 +27,13 @@ import javax.servlet.annotation.HandlesTypes; import javax.websocket.DeploymentException; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; +import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -43,8 +45,13 @@ import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; @HandlesTypes({ ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class }) public class JavaxWebSocketServletContainerInitializer implements ServletContainerInitializer { + /** + * The ServletContext attribute key name for the + * ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment + */ + public static final String ATTR_JAVAX_SERVER_CONTAINER = javax.websocket.server.ServerContainer.class.getName(); + public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.javax"; - public static final String DEPRECATED_ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356"; public static final String HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.javax.HttpClient"; private static final Logger LOG = Log.getLogger(JavaxWebSocketServletContainerInitializer.class); @@ -81,32 +88,89 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain return null; } - public static JavaxWebSocketServerContainer configureContext(ServletContextHandler context) + public interface Configurator { - WebSocketComponents components = WebSocketComponents.ensureWebSocketComponents(context.getServletContext()); - FilterHolder filterHolder = WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); - WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext(), WebSocketMapping.DEFAULT_KEY); - JavaxWebSocketServerContainer container = JavaxWebSocketServerContainer.ensureContainer(context.getServletContext()); + void accept(ServletContext servletContext, ServerContainer serverContainer) throws DeploymentException; + } - if (LOG.isDebugEnabled()) - LOG.debug("configureContext {} {} {} {}", mapping, components, filterHolder, container); + /** + * Configure the {@link ServletContextHandler} to call {@link JavaxWebSocketServletContainerInitializer#onStartup(Set, ServletContext)} + * during the {@link ServletContext} initialization phase. + * + * @param context the context to add listener to + * @param configurator the lambda that is called to allow the {@link ServerContainer} to + * be configured during the {@link ServletContext} initialization phase + */ + public static void configure(ServletContextHandler context, Configurator configurator) + { + // In this embedded-jetty usage, allow ServletContext.addListener() to + // add other ServletContextListeners (such as the ContextDestroyListener) after + // the initialization phase is over. (important for this SCI to function) + context.getServletContext().setExtendedListenerTypes(true); - return container; + context.addEventListener(ContainerInitializer.asContextListener(new JavaxWebSocketServletContainerInitializer()) + .afterStartup((servletContext) -> + { + ServerContainer serverContainer = (ServerContainer)servletContext.getAttribute(ATTR_JAVAX_SERVER_CONTAINER); + if (configurator != null) + { + try + { + configurator.accept(servletContext, serverContainer); + } + catch (DeploymentException e) + { + throw new RuntimeException("Failed to deploy WebSocket Endpoint", e); + } + } + })); + } + + /** + * Immediately initialize the {@link ServletContext} with the default (and empty) {@link ServerContainer}. + * + *

+ * This method is typically called from {@link #onStartup(Set, ServletContext)} itself or from + * another dependent {@link ServletContainerInitializer} that requires minimal setup to + * be performed. + *

+ *

+ * This method SHOULD NOT BE CALLED by users of Jetty. + * Use the {@link #configure(ServletContextHandler, Configurator)} method instead. + *

+ *

+ * There is no enablement check here, and no automatic deployment of endpoints at this point + * in time. It merely sets up the {@link ServletContext} so with the basics needed to start + * configuring for `javax.websocket.server` based endpoints. + *

+ * + * @param context the context to work with + * @return the default {@link ServerContainer} for this context + */ + public static JavaxWebSocketServerContainer initialize(ServletContextHandler context) + { + JavaxWebSocketServerContainer serverContainer = (JavaxWebSocketServerContainer) context.getAttribute(ATTR_JAVAX_SERVER_CONTAINER); + if(serverContainer == null) + { + WebSocketComponents components = WebSocketComponents.ensureWebSocketComponents(context.getServletContext()); + FilterHolder filterHolder = WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); + WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext(), WebSocketMapping.DEFAULT_KEY); + serverContainer = JavaxWebSocketServerContainer.ensureContainer(context.getServletContext()); + + if (LOG.isDebugEnabled()) + LOG.debug("configureContext {} {} {} {}", mapping, components, filterHolder, serverContainer); + } + return serverContainer; } @Override public void onStartup(Set> c, ServletContext context) throws ServletException { Boolean enableKey = isEnabledViaContext(context, ENABLE_KEY); - Boolean deprecatedEnabledKey = isEnabledViaContext(context, DEPRECATED_ENABLE_KEY); - if (deprecatedEnabledKey != null) - LOG.warn("Deprecated parameter used: " + DEPRECATED_ENABLE_KEY); boolean websocketEnabled = true; if (enableKey != null) websocketEnabled = enableKey; - else if (deprecatedEnabledKey != null) - websocketEnabled = deprecatedEnabledKey; if (!websocketEnabled) { @@ -114,7 +178,8 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain return; } - JavaxWebSocketServerContainer container = configureContext(ServletContextHandler.getServletContextHandler(context,"Javax WebSocket SCI")); + ServletContextHandler servletContextHandler = ServletContextHandler.getServletContextHandler(context,"Javax WebSocket SCI"); + JavaxWebSocketServerContainer container = initialize(servletContextHandler); try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader())) { diff --git a/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/WebSocketServerExamplesTest.java b/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/WebSocketServerExamplesTest.java index ceade33a518..56438ccfb47 100644 --- a/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/WebSocketServerExamplesTest.java +++ b/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/WebSocketServerExamplesTest.java @@ -22,7 +22,6 @@ import java.net.URI; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; import javax.websocket.ContainerProvider; @@ -32,7 +31,6 @@ import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.WebSocketContainer; -import javax.websocket.server.ServerContainer; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; @@ -110,10 +108,12 @@ public class WebSocketServerExamplesTest _context.setSecurityHandler(getSecurityHandler("user", "password", "testRealm")); _server.setHandler(_context); - ServerContainer serverContainer = JavaxWebSocketServletContainerInitializer.configureContext(_context); - serverContainer.addEndpoint(MyAuthedSocket.class); - serverContainer.addEndpoint(StreamingEchoSocket.class); - serverContainer.addEndpoint(GetHttpSessionSocket.class); + JavaxWebSocketServletContainerInitializer.configure(_context, (context, container) -> + { + container.addEndpoint(MyAuthedSocket.class); + container.addEndpoint(StreamingEchoSocket.class); + container.addEndpoint(GetHttpSessionSocket.class); + }); _server.start(); System.setProperty("org.eclipse.jetty.websocket.port", Integer.toString(connector.getLocalPort())); @@ -125,11 +125,12 @@ public class WebSocketServerExamplesTest _server.stop(); } - private static SecurityHandler getSecurityHandler(String username, String password, String realm) { + private static SecurityHandler getSecurityHandler(String username, String password, String realm) + { HashLoginService loginService = new HashLoginService(); UserStore userStore = new UserStore(); - userStore.addUser(username, Credential.getCredential(password), new String[] {"websocket"}); + userStore.addUser(username, Credential.getCredential(password), new String[]{"websocket"}); loginService.setUserStore(userStore); loginService.setName(realm); @@ -154,11 +155,11 @@ public class WebSocketServerExamplesTest public void testMyAuthedSocket() throws Exception { //HttpClient is configured for BasicAuthentication with the XmlHttpClientProvider - URI uri = URI.create("ws://localhost:"+connector.getLocalPort()+"/secured/socket"); + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/secured/socket"); WebSocketContainer clientContainer = ContainerProvider.getWebSocketContainer(); ClientSocket clientEndpoint = new ClientSocket(); - try(Session session = clientContainer.connectToServer(clientEndpoint, uri)) + try (Session session = clientContainer.connectToServer(clientEndpoint, uri)) { session.getBasicRemote().sendText("hello world"); } @@ -171,11 +172,11 @@ public class WebSocketServerExamplesTest @Test public void testStreamingEchoSocket() throws Exception { - URI uri = URI.create("ws://localhost:"+connector.getLocalPort()+"/echo"); + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/echo"); WebSocketContainer clientContainer = ContainerProvider.getWebSocketContainer(); ClientSocket clientEndpoint = new ClientSocket(); - try(Session session = clientContainer.connectToServer(clientEndpoint, uri)) + try (Session session = clientContainer.connectToServer(clientEndpoint, uri)) { session.getBasicRemote().sendText("hello world"); } @@ -188,11 +189,11 @@ public class WebSocketServerExamplesTest @Test public void testGetHttpSessionSocket() throws Exception { - URI uri = URI.create("ws://localhost:"+connector.getLocalPort()+"/example"); + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/example"); WebSocketContainer clientContainer = ContainerProvider.getWebSocketContainer(); ClientSocket clientEndpoint = new ClientSocket(); - try(Session session = clientContainer.connectToServer(clientEndpoint, uri)) + try (Session session = clientContainer.connectToServer(clientEndpoint, uri)) { session.getBasicRemote().sendText("hello world"); } diff --git a/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/browser/JsrBrowserDebugTool.java b/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/browser/JsrBrowserDebugTool.java index 21563f1ed7c..ccb71039385 100644 --- a/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/browser/JsrBrowserDebugTool.java +++ b/jetty-websocket/javax-websocket-server/src/test/java/org/eclipse/jetty/websocket/javax/server/browser/JsrBrowserDebugTool.java @@ -19,14 +19,10 @@ package org.eclipse.jetty.websocket.javax.server.browser; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.Objects; -import javax.servlet.ServletException; -import javax.websocket.DeploymentException; - import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; @@ -37,7 +33,6 @@ import org.eclipse.jetty.servlet.ServletHolder; 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.websocket.javax.server.JavaxWebSocketServerContainer; import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer; /** @@ -68,7 +63,6 @@ public class JsrBrowserDebugTool JsrBrowserDebugTool tool = new JsrBrowserDebugTool(); tool.setupServer(port); tool.server.start(); - tool.server.dumpStdErr(); LOG.info("Server available at {}", tool.server.getURI()); tool.server.join(); } @@ -80,13 +74,10 @@ public class JsrBrowserDebugTool private Server server; - private JavaxWebSocketServerContainer setupServer(int port) - throws DeploymentException, ServletException, URISyntaxException, MalformedURLException, IOException + private void setupServer(int port) throws URISyntaxException, IOException { server = new Server(); - server.setDumpAfterStart(true); - HttpConfiguration httpConf = new HttpConfiguration(); httpConf.setSendServerVersion(true); @@ -107,10 +98,9 @@ public class JsrBrowserDebugTool holder.setInitParameter("dirAllowed", "true"); server.setHandler(context); - JavaxWebSocketServerContainer container = JavaxWebSocketServletContainerInitializer.configureContext(context); - container.addEndpoint(JsrBrowserSocket.class); + JavaxWebSocketServletContainerInitializer.configure(context, + (servletContext, container) -> container.addEndpoint(JsrBrowserSocket.class)); LOG.info("{} setup on port {}", this.getClass().getName(), port); - return container; } } diff --git a/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java b/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java index 3c14f340cda..9d0042dfb67 100644 --- a/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java +++ b/jetty-websocket/javax-websocket-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/LocalServer.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.javax.tests; import java.net.URI; import java.util.Map; import java.util.function.BiConsumer; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.OnMessage; @@ -168,7 +167,7 @@ public class LocalServer extends ContainerLifeCycle implements LocalFuzzer.Provi { servletContextHandler = new ServletContextHandler(server, "/", true, false); servletContextHandler.setContextPath("/"); - serverContainer = JavaxWebSocketServletContainerInitializer.configureContext(servletContextHandler); + JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, null); serverContainer.addSessionListener(trackingListener); configureServletContextHandler(servletContextHandler); return servletContextHandler; @@ -283,6 +282,15 @@ public class LocalServer extends ContainerLifeCycle implements LocalFuzzer.Provi public ServerContainer getServerContainer() { + if (!servletContextHandler.isRunning()) + { + throw new IllegalStateException("Cannot access ServerContainer when ServletContextHandler isn't running"); + } + + if (serverContainer == null) + { + serverContainer = (JavaxWebSocketServerContainer)servletContextHandler.getAttribute(JavaxWebSocketServerContainer.JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE); + } return serverContainer; } diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/quotes/QuotesDecoderTextStreamTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/quotes/QuotesDecoderTextStreamTest.java index 8c2112de515..aa95b86edb3 100644 --- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/quotes/QuotesDecoderTextStreamTest.java +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/quotes/QuotesDecoderTextStreamTest.java @@ -21,16 +21,12 @@ package org.eclipse.jetty.websocket.javax.tests.quotes; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; - import javax.websocket.OnMessage; -import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; -import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; -import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.javax.tests.Fuzzer; import org.eclipse.jetty.websocket.javax.tests.LocalServer; import org.junit.jupiter.api.AfterAll; @@ -69,16 +65,9 @@ public class QuotesDecoderTextStreamTest @BeforeAll public static void startServer() throws Exception { - server = new LocalServer() - { - @Override - protected void configureServletContextHandler(ServletContextHandler context) throws Exception - { - ServerContainer container = JavaxWebSocketServletContainerInitializer.configureContext(context); - container.addEndpoint(QuotesEchoStringSocket.class); - } - }; + server = new LocalServer(); server.start(); + server.getServerContainer().addEndpoint(QuotesEchoStringSocket.class); } @AfterAll diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java index f9d78ffd3e9..563f25e847b 100644 --- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.javax.tests.server; import java.util.HashMap; import java.util.Map; - import javax.websocket.EndpointConfig; import org.eclipse.jetty.server.Server; @@ -45,14 +44,14 @@ public abstract class AbstractJavaxWebSocketServerFrameHandlerTest server = new Server(); context = new ServletContextHandler(); server.setHandler(context); - container = JavaxWebSocketServletContainerInitializer.configureContext(context); + JavaxWebSocketServletContainerInitializer.configure(context, null); server.start(); + container = (JavaxWebSocketServerContainer)context.getServletContext().getAttribute(JavaxWebSocketServerContainer.JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE); } @AfterAll public static void stopContainer() throws Exception { - container.stop(); server.stop(); } diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/DeploymentExceptionTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/DeploymentExceptionTest.java index ca16d869bed..052d8f87e3f 100644 --- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/DeploymentExceptionTest.java +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/DeploymentExceptionTest.java @@ -21,8 +21,8 @@ package org.eclipse.jetty.websocket.javax.tests.server; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; - import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; import org.eclipse.jetty.server.Handler; @@ -30,7 +30,6 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.websocket.javax.common.util.InvalidSignatureException; -import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainer; import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidCloseIntSocket; import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidErrorErrorSocket; @@ -98,14 +97,14 @@ public class DeploymentExceptionTest { ServletContextHandler context = new ServletContextHandler(); context.setServer(server); - JavaxWebSocketServerContainer container = JavaxWebSocketServletContainerInitializer.configureContext(context); - context.addBean(container); + JavaxWebSocketServletContainerInitializer.configure(context, null); contexts.addHandler(context); try { context.start(); - Exception e = assertThrows(DeploymentException.class, () -> container.addEndpoint(pojo)); + ServerContainer serverContainer = (ServerContainer)context.getServletContext().getAttribute(ServerContainer.class.getName()); + Exception e = assertThrows(DeploymentException.class, () -> serverContainer.addEndpoint(pojo)); assertThat(e.getCause(), instanceOf(InvalidSignatureException.class)); } finally diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/MemoryUsageTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/MemoryUsageTest.java index b47cb19030b..cfe1ff2c2df 100644 --- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/MemoryUsageTest.java +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/MemoryUsageTest.java @@ -24,14 +24,12 @@ import java.lang.management.MemoryUsage; import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import javax.websocket.WebSocketContainer; -import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpointConfig; import org.eclipse.jetty.server.Server; @@ -79,10 +77,12 @@ public class MemoryUsageTest connector = new ServerConnector(server); server.addConnector(connector); - ServletContextHandler context = new ServletContextHandler(server, "/", true, false); - ServerContainer container = JavaxWebSocketServletContainerInitializer.configureContext(context); - ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEndpoint.class, "/").build(); - container.addEndpoint(config); + ServletContextHandler contextHandler = new ServletContextHandler(server, "/", true, false); + JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + { + ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEndpoint.class, "/").build(); + container.addEndpoint(config); + }); server.start(); diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PartialEchoTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PartialEchoTest.java index b0aaa08d5ad..68f1b1fb8c9 100644 --- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PartialEchoTest.java +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/PartialEchoTest.java @@ -21,21 +21,17 @@ package org.eclipse.jetty.websocket.javax.tests.server; import java.io.IOException; import java.util.ArrayList; import java.util.List; - import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; -import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; -import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; -import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.javax.tests.Fuzzer; import org.eclipse.jetty.websocket.javax.tests.LocalServer; import org.junit.jupiter.api.AfterAll; @@ -111,17 +107,10 @@ public class PartialEchoTest @BeforeAll public static void startServer() throws Exception { - server = new LocalServer() - { - @Override - protected void configureServletContextHandler(ServletContextHandler context) throws Exception - { - ServerContainer container = JavaxWebSocketServletContainerInitializer.configureContext(context); - container.addEndpoint(PartialTextSocket.class); - container.addEndpoint(PartialTextSessionSocket.class); - } - }; + server = new LocalServer(); server.start(); + server.getServerContainer().addEndpoint(PartialTextSocket.class); + server.getServerContainer().addEndpoint(PartialTextSessionSocket.class); } @AfterAll diff --git a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebSocketServerContainerExecutorTest.java b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebSocketServerContainerExecutorTest.java index ae2345ce60c..13cb8c0201c 100644 --- a/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebSocketServerContainerExecutorTest.java +++ b/jetty-websocket/javax-websocket-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/WebSocketServerContainerExecutorTest.java @@ -26,7 +26,6 @@ import java.net.HttpURLConnection; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.concurrent.Executor; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -141,6 +140,12 @@ public class WebSocketServerContainerExecutorTest } } + private Executor getJavaxServerContainerExecutor(ServletContextHandler servletContextHandler) + { + JavaxWebSocketServerContainer serverContainer = (JavaxWebSocketServerContainer)servletContextHandler.getServletContext().getAttribute(JavaxWebSocketServerContainer.JAVAX_WEBSOCKET_CONTAINER_ATTRIBUTE); + return serverContainer.getExecutor(); + } + @Test public void testClientExecutor() throws Exception { @@ -160,15 +165,16 @@ public class WebSocketServerContainerExecutorTest // Using JSR356 Server Techniques to connectToServer() contextHandler.addServlet(ServerConnectServlet.class, "/connect"); - javax.websocket.server.ServerContainer container = JavaxWebSocketServletContainerInitializer.configureContext(contextHandler); - container.addEndpoint(EchoSocket.class); + JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + container.addEndpoint(EchoSocket.class)); + try { server.start(); String response = GET(server.getURI().resolve("/connect")); assertThat("Response", response, startsWith("Connected to ws://")); - Executor containerExecutor = ((JavaxWebSocketServerContainer)container).getExecutor(); + Executor containerExecutor = getJavaxServerContainerExecutor(contextHandler); assertThat(containerExecutor, sameInstance(executor)); } finally @@ -189,15 +195,15 @@ public class WebSocketServerContainerExecutorTest // Using JSR356 Server Techniques to connectToServer() contextHandler.addServlet(ServerConnectServlet.class, "/connect"); - javax.websocket.server.ServerContainer container = JavaxWebSocketServletContainerInitializer.configureContext(contextHandler); - container.addEndpoint(EchoSocket.class); + JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + container.addEndpoint(EchoSocket.class)); try { server.start(); String response = GET(server.getURI().resolve("/connect")); assertThat("Response", response, startsWith("Connected to ws://")); - Executor containerExecutor = ((JavaxWebSocketServerContainer)container).getExecutor(); + Executor containerExecutor = getJavaxServerContainerExecutor(contextHandler); assertThat(containerExecutor, sameInstance(executor)); } finally diff --git a/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java b/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java index fe80f1978ee..6d406dc8d7c 100644 --- a/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java +++ b/jetty-websocket/jetty-websocket-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java @@ -238,6 +238,7 @@ public interface RemoteEndpoint * Get the InetSocketAddress for the established connection. * * @return the InetSocketAddress for the established connection. (or null, if there is none) + * @deprecated use {@link #getRemoteAddress()} instead */ @Deprecated InetSocketAddress getInetSocketAddress(); diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java index 6bd66a3e817..98409e3af4e 100644 --- a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java +++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java @@ -19,13 +19,12 @@ package org.eclipse.jetty.websocket.server.config; import java.util.Set; - import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; -import javax.servlet.ServletException; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.core.WebSocketComponents; @@ -40,7 +39,56 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain { private static final Logger LOG = Log.getLogger(JettyWebSocketServletContainerInitializer.class); - public static JettyWebSocketServerContainer configureContext(ServletContextHandler context) + public interface Configurator + { + void accept(ServletContext servletContext, JettyWebSocketServerContainer container); + } + + /** + * Configure the {@link ServletContextHandler} to call the {@link JettyWebSocketServletContainerInitializer} + * during the {@link ServletContext} initialization phase. + * + * @param context the context to add listener to. + * @param configurator a lambda that is called to allow the {@link WebSocketMapping} to + * be configured during {@link ServletContext} initialization phase + */ + public static void configure(ServletContextHandler context, Configurator configurator) + { + context.addEventListener( + ContainerInitializer + .asContextListener(new JettyWebSocketServletContainerInitializer()) + .afterStartup((servletContext) -> + { + if (configurator != null) + { + JettyWebSocketServerContainer container = (JettyWebSocketServerContainer)servletContext + .getAttribute(JettyWebSocketServerContainer.JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE); + configurator.accept(servletContext, container); + } + })); + } + + /** + * Immediately initialize the {@link ServletContextHandler} with the default {@link JettyWebSocketServerContainer}. + * + *

+ * This method is typically called from {@link #onStartup(Set, ServletContext)} itself or from + * another dependent {@link ServletContainerInitializer} that requires minimal setup to + * be performed. + *

+ *

+ * This method SHOULD NOT BE CALLED by users of Jetty. + * Use the {@link #configure(ServletContextHandler, Configurator)} method instead. + *

+ *

+ * This will return the default {@link JettyWebSocketServerContainer} if already initialized, + * and not create a new {@link JettyWebSocketServerContainer} each time it is called. + *

+ * + * @param context the context to work with + * @return the default {@link JettyWebSocketServerContainer} + */ + public static JettyWebSocketServerContainer initialize(ServletContextHandler context) { WebSocketComponents components = WebSocketComponents.ensureWebSocketComponents(context.getServletContext()); FilterHolder filterHolder = WebSocketUpgradeFilter.ensureFilter(context.getServletContext()); @@ -54,9 +102,9 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain } @Override - public void onStartup(Set> c, ServletContext context) throws ServletException + public void onStartup(Set> c, ServletContext context) { - ServletContextHandler contextHandler = ServletContextHandler.getServletContextHandler(context,"Jetty WebSocket SCI"); - JettyWebSocketServletContainerInitializer.configureContext(contextHandler); + ServletContextHandler contextHandler = ServletContextHandler.getServletContextHandler(context, "Jetty WebSocket SCI"); + JettyWebSocketServletContainerInitializer.initialize(contextHandler); } } diff --git a/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java b/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java index 4bbb232a59a..472649a476f 100644 --- a/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java +++ b/jetty-websocket/jetty-websocket-server/src/test/java/org/eclipse/jetty/websocket/server/browser/BrowserDebugTool.java @@ -19,12 +19,10 @@ package org.eclipse.jetty.websocket.server.browser; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.Path; import java.time.Duration; import java.util.ArrayList; import java.util.List; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -92,7 +90,8 @@ public class BrowserDebugTool return connector.getLocalPort(); } - public void prepare(int port) throws IOException, URISyntaxException { + public void prepare(int port) + { server = new Server(); connector = new ServerConnector(server); connector.setPort(port); @@ -100,7 +99,7 @@ public class BrowserDebugTool ServletContextHandler context = new ServletContextHandler(); - JettyWebSocketServletContainerInitializer.configureContext(context); + JettyWebSocketServletContainerInitializer.configure(context, null); context.setContextPath("/"); Resource staticResourceBase = findStaticResources(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnoMaxMessageEndpoint.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnoMaxMessageEndpoint.java new file mode 100644 index 00000000000..ad6d06b37d3 --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnoMaxMessageEndpoint.java @@ -0,0 +1,36 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.io.IOException; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +@SuppressWarnings("unused") +@WebSocket(maxTextMessageSize = 100*1024) +public class AnnoMaxMessageEndpoint +{ + @OnWebSocketMessage + public void onMessage(Session session, String msg) throws IOException + { + session.getRemote().sendString(msg); + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectMessageEndpoint.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectMessageEndpoint.java new file mode 100644 index 00000000000..d9ff09a4645 --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectMessageEndpoint.java @@ -0,0 +1,36 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.io.IOException; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +@SuppressWarnings("unused") +@WebSocket +public class ConnectMessageEndpoint +{ + @OnWebSocketConnect + public void onConnect(Session session) throws IOException + { + session.getRemote().sendString("Greeting from onConnect"); + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java index 602951959be..eac981f385b 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java @@ -24,6 +24,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.annotations.WebSocket; +@SuppressWarnings("unused") @WebSocket public class EchoSocket extends EventSocket { diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/GetAuthHeaderEndpoint.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/GetAuthHeaderEndpoint.java new file mode 100644 index 00000000000..8e6130841cf --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/GetAuthHeaderEndpoint.java @@ -0,0 +1,38 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.io.IOException; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +@SuppressWarnings("unused") +@WebSocket +public class GetAuthHeaderEndpoint +{ + @OnWebSocketConnect + public void onConnect(Session session) throws IOException + { + String authHeaderName = "Authorization"; + String authHeaderValue = session.getUpgradeRequest().getHeader(authHeaderName); + session.getRemote().sendString("Header[" + authHeaderName + "]=" + authHeaderValue); + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java index cf53f289c4d..d981760f999 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java @@ -36,7 +36,6 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.JettyUpgradeListener; import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -66,25 +65,26 @@ public class JettyWebSocketExtensionConfigTest contextHandler.setContextPath("/"); server.setHandler(contextHandler); - JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.configureContext(contextHandler); - container.addMapping("/", (req, resp)-> - { - assertEquals(req.getExtensions().stream().filter(e -> e.getName().equals("permessage-deflate")).count(), 1); - assertEquals(resp.getExtensions().stream().filter(e -> e.getName().equals("permessage-deflate")).count(), 1); + JettyWebSocketServletContainerInitializer.configure(contextHandler, + (context, container) -> container.addMapping("/", (req, resp) -> + { + assertEquals(req.getExtensions().stream().filter(e -> e.getName().equals("permessage-deflate")).count(), 1); + assertEquals(resp.getExtensions().stream().filter(e -> e.getName().equals("permessage-deflate")).count(), 1); - ExtensionConfig nonRequestedExtension = ExtensionConfig.parse("identity"); - assertNotNull(nonRequestedExtension); + ExtensionConfig nonRequestedExtension = ExtensionConfig.parse("identity"); + assertNotNull(nonRequestedExtension); - assertThrows(IllegalArgumentException.class, - ()->resp.setExtensions(List.of(nonRequestedExtension)), + assertThrows(IllegalArgumentException.class, + () -> resp.setExtensions(List.of(nonRequestedExtension)), "should not allow extensions not requested"); - // Check identity extension was not added because it was not requested - assertEquals(resp.getExtensions().stream().filter(config -> config.getName().equals("identity")).count(), 0); - assertEquals(resp.getExtensions().stream().filter(e -> e.getName().equals("permessage-deflate")).count(), 1); + // Check identity extension was not added because it was not requested + assertEquals(resp.getExtensions().stream().filter(config -> config.getName().equals("identity")).count(), 0); + assertEquals(resp.getExtensions().stream().filter(e -> e.getName().equals("permessage-deflate")).count(), 1); + + return new EchoSocket(); + })); - return new EchoSocket(); - }); server.start(); client = new WebSocketClient(); @@ -101,7 +101,7 @@ public class JettyWebSocketExtensionConfigTest @Test public void testJettyExtensionConfig() throws Exception { - URI uri = URI.create("ws://localhost:"+connector.getLocalPort()+"/filterPath"); + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/filterPath"); EventSocket socket = new EventSocket(); UpgradeRequest request = new ClientUpgradeRequest(); @@ -115,7 +115,7 @@ public class JettyWebSocketExtensionConfigTest { String extensions = response.getHeaders().get(HttpHeader.SEC_WEBSOCKET_EXTENSIONS); - if("permessage-deflate".equals(extensions)) + if ("permessage-deflate".equals(extensions)) correctResponseExtensions.countDown(); else throw new IllegalStateException("Unexpected Negotiated Extensions: " + extensions); @@ -123,7 +123,7 @@ public class JettyWebSocketExtensionConfigTest }; CompletableFuture connect = client.connect(socket, uri, request, listener); - try(Session session = connect.get(5, TimeUnit.SECONDS)) + try (Session session = connect.get(5, TimeUnit.SECONDS)) { session.getRemote().sendString("hello world"); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java index e90163f9211..52e1110b2c3 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketFilterTest.java @@ -27,7 +27,6 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -54,8 +53,8 @@ public class JettyWebSocketFilterTest contextHandler.setContextPath("/"); server.setHandler(contextHandler); - JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.configureContext(contextHandler); - container.addMapping("/", (req, resp)->new EchoSocket()); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + container.addMapping("/", (req, resp) -> new EchoSocket())); server.start(); client = new WebSocketClient(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketNegotiationTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketNegotiationTest.java index 827c0b32573..6222679ace6 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketNegotiationTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketNegotiationTest.java @@ -32,7 +32,6 @@ import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -75,8 +74,9 @@ public class JettyWebSocketNegotiationTest @Test public void testBadRequest() throws Exception { - JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.configureContext(contextHandler); - container.addMapping("/", (req, resp)->new EchoSocket()); + JettyWebSocketServletContainerInitializer.configure(contextHandler, + (context, container) -> container.addMapping("/", (req, resp)->new EchoSocket())); + URI uri = URI.create("ws://localhost:"+connector.getLocalPort()+"/filterPath"); EventSocket socket = new EventSocket(); @@ -93,12 +93,12 @@ public class JettyWebSocketNegotiationTest @Test public void testServerError() throws Exception { - JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.configureContext(contextHandler); - container.addMapping("/", (req, resp)-> - { - resp.setAcceptedSubProtocol("errorSubProtocol"); - return new EchoSocket(); - }); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + container.addMapping("/", (req, resp) -> + { + resp.setAcceptedSubProtocol("errorSubProtocol"); + return new EchoSocket(); + })); URI uri = URI.create("ws://localhost:"+connector.getLocalPort()+"/filterPath"); EventSocket socket = new EventSocket(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java index fcd25934e6d..746ec976ae8 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletTest.java @@ -66,7 +66,7 @@ public class JettyWebSocketServletTest contextHandler.addServlet(MyWebSocketServlet.class, "/servletPath"); - JettyWebSocketServletContainerInitializer.configureContext(contextHandler); + JettyWebSocketServletContainerInitializer.configure(contextHandler, null); server.start(); client = new WebSocketClient(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/ParamsEndpoint.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/ParamsEndpoint.java new file mode 100644 index 00000000000..bd4470465f1 --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/ParamsEndpoint.java @@ -0,0 +1,49 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; + +@SuppressWarnings("unused") +@WebSocket +public class ParamsEndpoint +{ + @OnWebSocketConnect + public void onConnect(Session session) throws IOException + { + Map> params = session.getUpgradeRequest().getParameterMap(); + StringBuilder msg = new StringBuilder(); + + for (String key : params.keySet()) + { + msg.append("Params[").append(key).append("]="); + msg.append(params.get(key).stream().collect(Collectors.joining(", ", "[", "]"))); + msg.append("\n"); + } + + session.getRemote().sendString(msg.toString()); + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SimpleStatusServlet.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SimpleStatusServlet.java new file mode 100644 index 00000000000..300439a10d4 --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SimpleStatusServlet.java @@ -0,0 +1,41 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class SimpleStatusServlet extends HttpServlet +{ + private final int statusCode; + + public SimpleStatusServlet(int statusCode) + { + this.statusCode = statusCode; + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.setStatus(this.statusCode); + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java index 8ce13faaa43..54d454ca315 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java @@ -85,7 +85,7 @@ public class SuspendResumeTest server.setHandler(contextHandler); contextHandler.addServlet(new ServletHolder(new UpgradeServlet()), "/suspend"); - JettyWebSocketServletContainerInitializer.configureContext(contextHandler); + JettyWebSocketServletContainerInitializer.configure(contextHandler, null); server.start(); client.start(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java index b3c897272eb..0990c087a25 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketServletExamplesTest.java @@ -73,7 +73,7 @@ public class WebSocketServletExamplesTest _context.addServlet(MyAdvancedEchoServlet.class, "/advancedEcho"); _context.addServlet(MyAuthedServlet.class, "/authed"); - JettyWebSocketServletContainerInitializer.configureContext(_context); + JettyWebSocketServletContainerInitializer.configure(_context, null); _server.start(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java index 0c527954a11..28aab5dfaf1 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java @@ -34,7 +34,6 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; @@ -97,7 +96,7 @@ public class WebSocketStatsTest contextHandler.addServlet(MyWebSocketServlet.class, "/testPath"); server.setHandler(contextHandler); - JettyWebSocketServletContainerInitializer.configureContext(contextHandler); + JettyWebSocketServletContainerInitializer.configure(contextHandler, null); client = new WebSocketClient(); server.start(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java index 1aba249b7b6..11f7cb431b3 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java @@ -90,7 +90,7 @@ public class BadNetworkTest handlers.addHandler(context); handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); - JettyWebSocketServletContainerInitializer.configureContext(context); + JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java index eba846f319d..d52ec3e75cc 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java @@ -143,7 +143,7 @@ public class ClientCloseTest handlers.addHandler(context); handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); - JettyWebSocketServletContainerInitializer.configureContext(context); + JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java index 1f45f214211..eac483fc5b5 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java @@ -38,7 +38,6 @@ import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.core.internal.WebSocketConnection; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.websocket.tests.EchoSocket; import org.eclipse.jetty.websocket.tests.EventSocket; @@ -83,8 +82,9 @@ public class ClientConfigTest contextHandler.setContextPath("/"); server.setHandler(contextHandler); - JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.configureContext(contextHandler); - container.addMapping("/", (req, resp)->serverSocket); + JettyWebSocketServletContainerInitializer.configure(contextHandler, + (context, container) -> container.addMapping("/", (req, resp) -> serverSocket)); + server.start(); client = new WebSocketClient(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java new file mode 100644 index 00000000000..514c043152b --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java @@ -0,0 +1,399 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.client; + +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.time.Duration; +import java.util.EnumSet; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.UpgradeException; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; +import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint; +import org.eclipse.jetty.websocket.tests.EchoSocket; +import org.eclipse.jetty.websocket.tests.GetAuthHeaderEndpoint; +import org.eclipse.jetty.websocket.tests.SimpleStatusServlet; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Various connect condition testing + */ +@SuppressWarnings("Duplicates") +public class ClientConnectTest +{ + private Server server; + private WebSocketClient client; + + @SuppressWarnings("unchecked") + private E assertExpectedError(ExecutionException e, CloseTrackingEndpoint wsocket, Matcher errorMatcher) + { + // Validate thrown cause + Throwable cause = e.getCause(); + + assertThat("ExecutionException.cause", cause, errorMatcher); + + // Validate websocket captured cause + Throwable capcause = wsocket.error.get(); + assertThat("Error", capcause, notNullValue()); + assertThat("Error", capcause, errorMatcher); + + // Validate that websocket didn't see an open event + assertThat("Open Latch", wsocket.openLatch.getCount(), is(1L)); + + // Return the captured cause + return (E)capcause; + } + + @BeforeEach + public void startClient() throws Exception + { + client = new WebSocketClient(); + client.setConnectTimeout(TimeUnit.SECONDS.toMillis(3)); + client.setIdleTimeout(Duration.ofSeconds(10)); + client.start(); + } + + @BeforeEach + public void startServer() throws Exception + { + server = new Server(); + + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + + JettyWebSocketServletContainerInitializer.configure(context, + (servletContext, configuration) -> + { + configuration.setIdleTimeout(Duration.ofSeconds(10)); + configuration.addMapping("/echo", (req, resp) -> + { + if (req.hasSubProtocol("echo")) + resp.setAcceptedSubProtocol("echo"); + return new EchoSocket(); + }); + configuration.addMapping("/get-auth-header", (req, resp) -> new GetAuthHeaderEndpoint()); + }); + + context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + context.addServlet(new ServletHolder(new SimpleStatusServlet(404)), "/bogus"); + context.addServlet(new ServletHolder(new SimpleStatusServlet(200)), "/a-okay"); + context.addServlet(new ServletHolder(new InvalidUpgradeServlet()), "/invalid-upgrade/*"); + + server.setHandler(context); + + server.start(); + } + + @AfterEach + public void stopClient() throws Exception + { + client.stop(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testUpgradeRequest() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo")); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.setSubProtocols("echo"); + Future future = client.connect(cliSock, wsUri); + + try (Session sess = future.get(30, TimeUnit.SECONDS)) + { + assertThat("Connect.UpgradeRequest", sess.getUpgradeRequest(), notNullValue()); + assertThat("Connect.UpgradeResponse", sess.getUpgradeResponse(), notNullValue()); + } + } + + @Test + public void testUpgradeWithAuthorizationHeader() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/get-auth-header")); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + // actual value for this test is irrelevant, its important that this + // header actually be sent with a value (the value specified) + String authHeaderValue = "Basic YWxhZGRpbjpvcGVuc2VzYW1l"; + request.setHeader("Authorization", authHeaderValue); + Future future = client.connect(cliSock, wsUri, request); + + try (Session sess = future.get(5, TimeUnit.SECONDS)) + { + // Test client side + String cliAuthValue = sess.getUpgradeRequest().getHeader("Authorization"); + assertThat("Client Request Authorization Value", cliAuthValue, is(authHeaderValue)); + + // wait for response from server + String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); + assertThat("Message", received, containsString("Header[Authorization]=" + authHeaderValue)); + } + } + + @Test + public void testBadHandshake() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/bogus")); + Future future = client.connect(cliSock, wsUri); + + // The attempt to get upgrade response future should throw error + ExecutionException e = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + + UpgradeException ue = assertExpectedError(e, cliSock, instanceOf(UpgradeException.class)); + assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue()); + assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString())); + assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(404)); + } + + @Test + public void testBadHandshake_GetOK() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/a-okay")); + Future future = client.connect(cliSock, wsUri); + + // The attempt to get upgrade response future should throw error + ExecutionException e = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + + UpgradeException ue = assertExpectedError(e, cliSock, instanceOf(UpgradeException.class)); + assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue()); + assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString())); + assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(200)); + } + + @Test + public void testBadHandshake_GetOK_WithSecWebSocketAccept() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/invalid-upgrade/only-accept")); + Future future = client.connect(cliSock, wsUri); + + // The attempt to get upgrade response future should throw error + ExecutionException e = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + + UpgradeException ue = assertExpectedError(e, cliSock, instanceOf(UpgradeException.class)); + assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue()); + assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString())); + assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(200)); + } + + @Test + public void testBadHandshake_SwitchingProtocols_InvalidConnectionHeader() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/invalid-upgrade/close-connection")); + Future future = client.connect(cliSock, wsUri); + + // The attempt to get upgrade response future should throw error + ExecutionException e = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + + UpgradeException ue = assertExpectedError(e, cliSock, instanceOf(UpgradeException.class)); + assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue()); + assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString())); + assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(101)); + } + + @Test + public void testBadHandshake_SwitchingProtocols_NoConnectionHeader() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/invalid-upgrade/missing-connection")); + Future future = client.connect(cliSock, wsUri); + + // The attempt to get upgrade response future should throw error + ExecutionException e = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + + UpgradeException ue = assertExpectedError(e, cliSock, instanceOf(UpgradeException.class)); + assertThat("UpgradeException.requestURI", ue.getRequestURI(), notNullValue()); + assertThat("UpgradeException.requestURI", ue.getRequestURI().toASCIIString(), is(wsUri.toASCIIString())); + assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(101)); + } + + @Test + public void testBadUpgrade() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/invalid-upgrade/rubbish-accept")); + Future future = client.connect(cliSock, wsUri); + + // The attempt to get upgrade response future should throw error + ExecutionException e = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + + UpgradeException ue = assertExpectedError(e, cliSock, instanceOf(UpgradeException.class)); + assertThat("UpgradeException.responseStatusCode", ue.getResponseStatusCode(), is(101)); + } + + @Test + public void testConnectionNotAccepted() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + try (ServerSocket serverSocket = new ServerSocket()) + { + InetAddress addr = InetAddress.getByName("localhost"); + InetSocketAddress endpoint = new InetSocketAddress(addr, 0); + serverSocket.bind(endpoint, 1); + int port = serverSocket.getLocalPort(); + + URI wsUri = URI.create(String.format("ws://%s:%d/", addr.getHostAddress(), port)); + Future future = client.connect(cliSock, wsUri); + + // Intentionally not accept incoming socket. + // serverSocket.accept(); + + try + { + future.get(8, TimeUnit.SECONDS); + fail("Should have Timed Out"); + } + catch (ExecutionException e) + { + // Passing Path (active session wait timeout) + assertExpectedError(e, cliSock, instanceOf(UpgradeException.class)); + } + catch (TimeoutException e) + { + // Passing Path + } + } + } + + @Test + public void testConnectionRefused() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + // Intentionally bad port with nothing listening on it + URI wsUri = new URI("ws://127.0.0.1:1"); + + try + { + Future future = client.connect(cliSock, wsUri); + + // The attempt to get upgrade response future should throw error + future.get(5, TimeUnit.SECONDS); + fail("Expected ExecutionException -> ConnectException"); + } + catch (ConnectException e) + { + Throwable t = cliSock.error.get(); + assertThat("Error Queue[0]", t, instanceOf(ConnectException.class)); + } + catch (ExecutionException e) + { + assertExpectedError(e, cliSock, + anyOf( + instanceOf(UpgradeException.class), + instanceOf(SocketTimeoutException.class), + instanceOf(ConnectException.class))); + } + } + + @Test + public void testConnectionTimeout_Concurrent() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + try (ServerSocket serverSocket = new ServerSocket()) + { + InetAddress addr = InetAddress.getByName("localhost"); + InetSocketAddress endpoint = new InetSocketAddress(addr, 0); + serverSocket.bind(endpoint, 1); + int port = serverSocket.getLocalPort(); + URI wsUri = URI.create(String.format("ws://%s:%d/", addr.getHostAddress(), port)); + Future future = client.connect(cliSock, wsUri); + + // Accept the connection, but do nothing on it (no response, no upgrade, etc) + serverSocket.accept(); + + // The attempt to get upgrade response future should throw error + Exception e = assertThrows(Exception.class, + () -> future.get(5, TimeUnit.SECONDS)); + + if (e instanceof ExecutionException) + { + assertExpectedError((ExecutionException)e, cliSock, anyOf( + instanceOf(ConnectException.class), + instanceOf(UpgradeException.class) + )); + } + else + { + assertThat("Should have been a TimeoutException", e, instanceOf(TimeoutException.class)); + } + } + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java index 87a0675e06e..bf1eae3cdd7 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java @@ -83,7 +83,7 @@ public class ClientSessionsTest handlers.addHandler(context); handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); - JettyWebSocketServletContainerInitializer.configureContext(context); + JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/InvalidUpgradeServlet.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/InvalidUpgradeServlet.java new file mode 100644 index 00000000000..1b4fc2d642b --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/InvalidUpgradeServlet.java @@ -0,0 +1,68 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.client; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.websocket.core.internal.WebSocketCore; + +public class InvalidUpgradeServlet extends HttpServlet +{ + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + { + String pathInfo = req.getPathInfo(); + if (pathInfo.contains("only-accept")) + { + // Force 200 response, no response body content, incomplete websocket response headers, no actual upgrade for this test + resp.setStatus(HttpServletResponse.SC_OK); + String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString()); + resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), WebSocketCore.hashKey(key)); + } + else if (pathInfo.contains("close-connection")) + { + // Force 101 response, with invalid Connection header, invalid handshake + resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS); + String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString()); + resp.setHeader(HttpHeader.CONNECTION.toString(), "close"); + resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), WebSocketCore.hashKey(key)); + } + else if (pathInfo.contains("missing-connection")) + { + // Force 101 response, with no Connection header, invalid handshake + resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS); + String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString()); + // Intentionally leave out Connection header + resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), WebSocketCore.hashKey(key)); + } + else if (pathInfo.contains("rubbish-accept")) + { + // Force 101 response, with no Connection header, invalid handshake + resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS); + resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), "rubbish"); + } + else + { + resp.setStatus(500); + } + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java index ed7bacb39f6..8848364f852 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java @@ -86,7 +86,7 @@ public class SlowClientTest handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); - JettyWebSocketServletContainerInitializer.configureContext(context); + JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java new file mode 100644 index 00000000000..e96b46d3ab6 --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java @@ -0,0 +1,333 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests.client; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import javax.servlet.DispatcherType; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.UpgradeRequest; +import org.eclipse.jetty.websocket.api.util.WSURI; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter; +import org.eclipse.jetty.websocket.tests.AnnoMaxMessageEndpoint; +import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint; +import org.eclipse.jetty.websocket.tests.ConnectMessageEndpoint; +import org.eclipse.jetty.websocket.tests.EchoSocket; +import org.eclipse.jetty.websocket.tests.ParamsEndpoint; +import org.eclipse.jetty.websocket.tests.util.FutureWriteCallback; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class WebSocketClientTest +{ + private Server server; + private WebSocketClient client; + + @BeforeEach + public void startClient() throws Exception + { + client = new WebSocketClient(); + client.start(); + } + + @BeforeEach + public void startServer() throws Exception + { + server = new Server(); + + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + + JettyWebSocketServletContainerInitializer.configure(context, + (servletContext, configuration) -> + { + configuration.setIdleTimeout(Duration.ofSeconds(10)); + configuration.addMapping("/echo", (req, resp) -> + { + if (req.hasSubProtocol("echo")) + resp.setAcceptedSubProtocol("echo"); + return new EchoSocket(); + }); + configuration.addMapping("/anno-max-message", (req, resp) -> new AnnoMaxMessageEndpoint()); + configuration.addMapping("/connect-msg", (req, resp) -> new ConnectMessageEndpoint()); + configuration.addMapping("/get-params", (req, resp) -> new ParamsEndpoint()); + }); + + context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + server.setHandler(context); + + server.start(); + } + + @AfterEach + public void stopClient() throws Exception + { + client.stop(); + } + + @AfterEach + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testAddExtension_NotInstalled() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo")); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.setSubProtocols("echo"); + request.addExtensions("x-bad"); + + assertThrows(IllegalArgumentException.class, () -> + { + // Should trigger failure on bad extension + client.connect(cliSock, wsUri, request); + }); + } + + @Test + public void testBasicEcho_FromClient() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo")); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.setSubProtocols("echo"); + Future future = client.connect(cliSock, wsUri, request); + + try (Session sess = future.get(30, TimeUnit.SECONDS)) + { + assertThat("Session", sess, notNullValue()); + assertThat("Session.open", sess.isOpen(), is(true)); + assertThat("Session.upgradeRequest", sess.getUpgradeRequest(), notNullValue()); + assertThat("Session.upgradeResponse", sess.getUpgradeResponse(), notNullValue()); + + Collection sessions = client.getOpenSessions(); + assertThat("client.sessions.size", sessions.size(), is(1)); + + RemoteEndpoint remote = cliSock.getSession().getRemote(); + remote.sendString("Hello World!"); + + // wait for response from server + String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); + assertThat("Message", received, containsString("Hello World")); + } + } + + @Test + public void testBasicEcho_UsingCallback() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo")); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.setSubProtocols("echo"); + Future future = client.connect(cliSock, wsUri, request); + + try (Session sess = future.get(5, TimeUnit.SECONDS)) + { + assertThat("Session", sess, notNullValue()); + assertThat("Session.open", sess.isOpen(), is(true)); + assertThat("Session.upgradeRequest", sess.getUpgradeRequest(), notNullValue()); + assertThat("Session.upgradeResponse", sess.getUpgradeResponse(), notNullValue()); + + Collection sessions = client.getOpenSessions(); + assertThat("client.sessions.size", sessions.size(), is(1)); + + FutureWriteCallback callback = new FutureWriteCallback(); + + cliSock.getSession().getRemote().sendString("Hello World!", callback); + callback.get(5, TimeUnit.SECONDS); + + // wait for response from server + String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); + assertThat("Message", received, containsString("Hello World")); + } + } + + @Test + public void testBasicEcho_FromServer() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/connect-msg")); + Future future = client.connect(cliSock, wsUri); + + try (Session sess = future.get(5, TimeUnit.SECONDS)) + { + // Validate connect + assertThat("Session", sess, notNullValue()); + assertThat("Session.open", sess.isOpen(), is(true)); + assertThat("Session.upgradeRequest", sess.getUpgradeRequest(), notNullValue()); + assertThat("Session.upgradeResponse", sess.getUpgradeResponse(), notNullValue()); + + // wait for message from server + String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); + assertThat("Message", received, containsString("Greeting from onConnect")); + } + } + + @Test + public void testLocalRemoteAddress() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo")); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.setSubProtocols("echo"); + Future future = client.connect(cliSock, wsUri, request); + + try (Session sess = future.get(5, TimeUnit.SECONDS)) + { + Assertions.assertTrue(cliSock.openLatch.await(1, TimeUnit.SECONDS)); + + InetSocketAddress local = (InetSocketAddress)cliSock.getSession().getLocalAddress(); + InetSocketAddress remote = (InetSocketAddress)cliSock.getSession().getRemoteAddress(); + + assertThat("Local Socket Address", local, notNullValue()); + assertThat("Remote Socket Address", remote, notNullValue()); + + // Hard to validate (in a portable unit test) the local address that was used/bound in the low level Jetty Endpoint + assertThat("Local Socket Address / Host", local.getAddress().getHostAddress(), notNullValue()); + assertThat("Local Socket Address / Port", local.getPort(), greaterThan(0)); + + String uriHostAddress = InetAddress.getByName(wsUri.getHost()).getHostAddress(); + assertThat("Remote Socket Address / Host", remote.getAddress().getHostAddress(), is(uriHostAddress)); + assertThat("Remote Socket Address / Port", remote.getPort(), greaterThan(0)); + } + } + + /** + * Ensure that @WebSocket(maxTextMessageSize = 100*1024) behaves as expected. + * + * @throws Exception on test failure + */ + @Test + public void testMaxMessageSize() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + client.setMaxTextMessageSize(100 * 1024); + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/anno-max-message")); + Future future = client.connect(cliSock, wsUri); + + try (Session sess = future.get(5, TimeUnit.SECONDS)) + { + assertThat("Session", sess, notNullValue()); + assertThat("Session.open", sess.isOpen(), is(true)); + + // Create string that is larger than default size of 64k + // but smaller than maxMessageSize of 100k + int size = 80 * 1024; + byte buf[] = new byte[size]; + Arrays.fill(buf,(byte)'x'); + String msg = StringUtil.toUTF8String(buf,0,buf.length); + + sess.getRemote().sendString(msg); + + // wait for message from server + String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); + assertThat("Message", received.length(), is(size)); + } + } + + @Test + public void testParameterMap() throws Exception + { + CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint(); + + client.setMaxTextMessageSize(100 * 1024); + client.setIdleTimeout(Duration.ofSeconds(10)); + + URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/get-params?snack=cashews&amount=handful&brand=off")); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + Future future = client.connect(cliSock, wsUri, request); + + try (Session sess = future.get(5, TimeUnit.SECONDS)) + { + UpgradeRequest req = sess.getUpgradeRequest(); + assertThat("Upgrade Request", req, notNullValue()); + + Map> parameterMap = req.getParameterMap(); + assertThat("Parameter Map", parameterMap, notNullValue()); + + assertThat("Parameter[snack]", parameterMap.get("snack"), is(Arrays.asList(new String[]{"cashews"}))); + assertThat("Parameter[amount]", parameterMap.get("amount"), is(Arrays.asList(new String[]{"handful"}))); + assertThat("Parameter[brand]", parameterMap.get("brand"), is(Arrays.asList(new String[]{"off"}))); + + assertThat("Parameter[cost]", parameterMap.get("cost"), nullValue()); + + // wait for message from server indicating what it sees + String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); + assertThat("Parameter[snack]", received, containsString("Params[snack]=[cashews]")); + assertThat("Parameter[amount]", received, containsString("Params[amount]=[handful]")); + assertThat("Parameter[brand]", received, containsString("Params[brand]=[off]")); + assertThat("Parameter[cost]", received, not(containsString("Params[cost]="))); + } + } +} diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java index 12647f2c3ba..5a7c7a6ae5d 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java @@ -89,7 +89,7 @@ public class ServerCloseTest handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); - JettyWebSocketServletContainerInitializer.configureContext(context); + JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java index 9f8ba9a1ccf..6a743890e0c 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java @@ -42,7 +42,6 @@ import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.core.internal.WebSocketConnection; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; @@ -187,12 +186,14 @@ public class ServerConfigTest contextHandler.addServlet(new ServletHolder(new WebSocketSessionConfigServlet()), "/sessionConfig"); server.setHandler(contextHandler); - JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.configureContext(contextHandler); - container.setIdleTimeout(Duration.ofMillis(idleTimeout)); - container.setMaxTextMessageSize(maxMessageSize); - container.setMaxBinaryMessageSize(maxMessageSize); - container.setInputBufferSize(inputBufferSize); - container.addMapping("/containerConfig", (req, resp)->standardEndpoint); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> { + container.setIdleTimeout(Duration.ofMillis(idleTimeout)); + container.setMaxTextMessageSize(maxMessageSize); + container.setMaxBinaryMessageSize(maxMessageSize); + container.setInputBufferSize(inputBufferSize); + container.addMapping("/containerConfig", (req, resp)->standardEndpoint); + }); + server.start(); client = new WebSocketClient(); diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java index e1976b8f9c0..ba63acdda4c 100644 --- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java @@ -86,7 +86,7 @@ public class SlowServerTest handlers.addHandler(new DefaultHandler()); server.setHandler(handlers); - JettyWebSocketServletContainerInitializer.configureContext(context); + JettyWebSocketServletContainerInitializer.configure(context, null); server.start(); } diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureWriteCallback.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureWriteCallback.java new file mode 100644 index 00000000000..7c8af9a8d2c --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureWriteCallback.java @@ -0,0 +1,32 @@ +package org.eclipse.jetty.websocket.tests.util; + +import java.util.concurrent.Future; + +import org.eclipse.jetty.util.FutureCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.WriteCallback; + +/** + * Allows events to a {@link WriteCallback} to drive a {@link Future} for the user. + */ +public class FutureWriteCallback extends FutureCallback implements WriteCallback +{ + private static final Logger LOG = Log.getLogger(FutureWriteCallback.class); + + @Override + public void writeFailed(Throwable cause) + { + if (LOG.isDebugEnabled()) + LOG.debug(".writeFailed",cause); + failed(cause); + } + + @Override + public void writeSuccess() + { + if (LOG.isDebugEnabled()) + LOG.debug(".writeSuccess"); + succeeded(); + } +} diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java index 368a4b3b47d..bdc8eb09208 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketUpgradeFilter.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.servlet; import java.io.IOException; import java.time.Duration; import java.util.EnumSet; - import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -90,6 +89,22 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable return null; } + /** + * Configure the default WebSocketUpgradeFilter. + * + *

+ * This will return the default {@link WebSocketUpgradeFilter} on the + * provided {@link ServletContext}, creating the filter if necessary. + *

+ *

+ * The default {@link WebSocketUpgradeFilter} is also available via + * the {@link ServletContext} attribute named {@code org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter} + *

+ * + * @param servletContext the {@link ServletContext} to use + * @return the configured default {@link WebSocketUpgradeFilter} instance + * @throws ServletException if the filer cannot be configured + */ public static FilterHolder ensureFilter(ServletContext servletContext) { FilterHolder existingFilter = WebSocketUpgradeFilter.getFilter(servletContext);