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..b1eb8fe9124
--- /dev/null
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java
@@ -0,0 +1,185 @@
+//
+// ========================================================================
+// 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 SCIAsContextListener#addClasses(Class[])
+ * @see SCIAsContextListener#addClasses(String...)
+ */
+ public static SCIAsContextListener asContextListener(ServletContainerInitializer sci)
+ {
+ return new SCIAsContextListener(sci);
+ }
+
+ public static class SCIAsContextListener implements ServletContextListener
+ {
+ private final ServletContainerInitializer sci;
+ private Set classNames;
+ private Set> classes = new HashSet<>();
+ private Consumer postOnStartupConsumer;
+
+ public SCIAsContextListener(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 SCIAsContextListener} instance.
+ */
+ public SCIAsContextListener 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 SCIAsContextListener} instance.
+ */
+ public SCIAsContextListener 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 SCIAsContextListener} instance.
+ */
+ public SCIAsContextListener setPostOnStartupConsumer(Consumer consumer)
+ {
+ this.postOnStartupConsumer = consumer;
+ return this;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ ServletContext servletContext = sce.getServletContext();
+ try
+ {
+ sci.onStartup(getClasses(), servletContext);
+ if (postOnStartupConsumer != null)
+ {
+ postOnStartupConsumer.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/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java
index fabb5b82cbc..02bcc59b607 100644
--- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java
+++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java
@@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.jsr356.server.deploy;
import java.util.HashSet;
import java.util.Set;
-
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
@@ -34,8 +33,10 @@ import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
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;
@@ -53,7 +54,8 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
public static final String ADD_DYNAMIC_FILTER_KEY = "org.eclipse.jetty.websocket.jsr356.addDynamicFilter";
private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
public static final String HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.jsr356.HttpClient";
-
+ public static final String ATTR_JAVAX_SERVER_CONTAINER = javax.websocket.server.ServerContainer.class.getName();
+
/**
* DestroyListener
*/
@@ -129,61 +131,145 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
return defValue;
}
-
+
+ public interface Configurator
+ {
+ void accept(ServletContext servletContext, ServerContainer serverContainer) throws DeploymentException;
+ }
+
/**
- * Embedded Jetty approach for non-bytecode scanning.
* @param context the {@link ServletContextHandler} to use
* @return a configured {@link ServerContainer} instance
* @throws ServletException if the {@link WebSocketUpgradeFilter} cannot be configured
+ * @deprecated use {@link #configure(ServletContextHandler, Configurator)} instead
+ * @see #configure(ServletContextHandler, Configurator)
*/
+ @Deprecated
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
{
- // Create Basic components
- NativeWebSocketConfiguration nativeWebSocketConfiguration = NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext());
-
- // Build HttpClient
- HttpClient httpClient = (HttpClient) context.getServletContext().getAttribute(HTTPCLIENT_ATTRIBUTE);
- if ((httpClient == null) && (context.getServer() != null))
- {
- httpClient = (HttpClient) context.getServer().getAttribute(HTTPCLIENT_ATTRIBUTE);
- }
-
- // Create the Jetty ServerContainer implementation
- ServerContainer jettyContainer = new ServerContainer(nativeWebSocketConfiguration, httpClient);
- context.addBean(jettyContainer);
-
- // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
- context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
-
- // Create Filter
- if(isEnabledViaContext(context.getServletContext(), ADD_DYNAMIC_FILTER_KEY, true))
- {
- String instanceKey = WebSocketUpgradeFilter.class.getName() + ".SCI";
- if(context.getAttribute(instanceKey) == null)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Dynamic filter add to support JSR356/javax.websocket.server: {}", WebSocketUpgradeFilter.class.getName());
- WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context);
- context.setAttribute(instanceKey, wsuf);
- }
- }
-
- return jettyContainer;
+ ServletContext servletContext = context.getServletContext();
+ initialize(servletContext);
+ return (ServerContainer)servletContext.getAttribute(ATTR_JAVAX_SERVER_CONTAINER);
}
-
+
/**
- * @deprecated use {@link #configureContext(ServletContextHandler)} instead
* @param context not used
* @param jettyContext the {@link ServletContextHandler} to use
* @return a configured {@link ServerContainer} instance
* @throws ServletException if the {@link WebSocketUpgradeFilter} cannot be configured
+ * @deprecated use {@link #configure(ServletContextHandler, Configurator)} instead
+ * @see #configure(ServletContextHandler, Configurator)
*/
@Deprecated
public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
{
- return configureContext(jettyContext);
+ initialize(context);
+ return (ServerContainer)context.getAttribute(ATTR_JAVAX_SERVER_CONTAINER);
}
+ /**
+ * Initialize the {@link ServletContext} with the default (and empty) {@link ServerContainer}.
+ *
+ *
+ * This performs a subset of the behaviors that {@link #onStartup(Set, ServletContext)} does.
+ * 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
+ */
+ public static void initialize(ServletContext context) throws ServletException
+ {
+ // Create Basic components
+ NativeWebSocketServletContainerInitializer.initialize(context);
+ NativeWebSocketConfiguration nativeWebSocketConfiguration = (NativeWebSocketConfiguration)context.getAttribute(NativeWebSocketServletContainerInitializer.ATTR_KEY);
+
+ ContextHandler contextHandler = null;
+ // Attach default configuration to context lifecycle
+ if (context instanceof ContextHandler.Context)
+ {
+ contextHandler = ((ContextHandler.Context)context).getContextHandler();
+ }
+
+ // Obtain HttpClient
+ HttpClient httpClient = (HttpClient)context.getAttribute(HTTPCLIENT_ATTRIBUTE);
+ if ((httpClient == null) && (contextHandler != null))
+ {
+ Server server = contextHandler.getServer();
+ if (server != null)
+ {
+ httpClient = (HttpClient)server.getAttribute(HTTPCLIENT_ATTRIBUTE);
+ }
+ }
+
+ // Create the Jetty ServerContainer implementation
+ ServerContainer jettyContainer = new ServerContainer(nativeWebSocketConfiguration, httpClient);
+ contextHandler.addBean(jettyContainer);
+
+ // Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
+ context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
+
+ if(contextHandler instanceof ServletContextHandler)
+ {
+ ServletContextHandler servletContextHandler = (ServletContextHandler)contextHandler;
+ // Create Filter
+ if(isEnabledViaContext(context, ADD_DYNAMIC_FILTER_KEY, true))
+ {
+ String instanceKey = WebSocketUpgradeFilter.class.getName() + ".SCI";
+ if(context.getAttribute(instanceKey) == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Dynamic filter add to support JSR356/javax.websocket.server: {}", WebSocketUpgradeFilter.class.getName());
+ WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(servletContextHandler);
+ context.setAttribute(instanceKey, wsuf);
+ }
+ }
+ }
+ }
+
+ /**
+ * Configure the {@link ServletContextHandler} to call {@link WebSocketServerContainerInitializer#onStartup(Set, ServletContext)}
+ * during the {@link ServletContext} initialization phase.
+ *
+ * @param context the context to add listener to
+ */
+ public static void configure(ServletContextHandler context)
+ {
+ context.addEventListener(ContainerInitializer.asContextListener(new WebSocketServerContainerInitializer()));
+ }
+
+ /**
+ * Configure the {@link ServletContextHandler} to call {@link WebSocketServerContainerInitializer#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);
+
+ context.addEventListener(
+ ContainerInitializer.asContextListener(new WebSocketServerContainerInitializer())
+ .setPostOnStartupConsumer((servletContext) ->
+ {
+ ServerContainer serverContainer = (ServerContainer)servletContext.getAttribute(ATTR_JAVAX_SERVER_CONTAINER);
+ try
+ {
+ configurator.accept(servletContext, serverContainer);
+ }
+ catch (DeploymentException e)
+ {
+ throw new RuntimeException("Failed to deploy WebSocket Endpoint", e);
+ }
+ }));
+ }
+
@Override
public void onStartup(Set> c, ServletContext context) throws ServletException
{
@@ -205,12 +291,11 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support unavailable");
}
- ServletContextHandler jettyContext = (ServletContextHandler)handler;
-
try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader()))
{
// Create the Jetty ServerContainer implementation
- ServerContainer jettyContainer = configureContext(jettyContext);
+ initialize(context);
+ ServerContainer jettyContainer = (ServerContainer)context.getAttribute(ATTR_JAVAX_SERVER_CONTAINER);
context.addListener(new ContextDestroyListener()); // make sure context is cleaned up when the context stops
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BinaryStreamTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BinaryStreamTest.java
index 0018d9ec0eb..bd2075f90e7 100644
--- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BinaryStreamTest.java
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BinaryStreamTest.java
@@ -18,9 +18,6 @@
package org.eclipse.jetty.websocket.jsr356.server;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -29,7 +26,6 @@ import java.net.URI;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.OnMessage;
@@ -43,10 +39,12 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
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.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
public class BinaryStreamTest
{
private static final String PATH = "/echo";
@@ -63,9 +61,11 @@ public class BinaryStreamTest
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
- ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
- ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerBinaryStreamer.class, PATH).build();
- container.addEndpoint(config);
+ WebSocketServerContainerInitializer.configure(context, (servletContext, container) ->
+ {
+ ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerBinaryStreamer.class, PATH).build();
+ container.addEndpoint(config);
+ });
server.start();
diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java
index ea45a3d2fda..21c8e52115c 100644
--- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java
+++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java
@@ -19,14 +19,10 @@
package org.eclipse.jetty.websocket.jsr356.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.jsr356.server.ServerContainer;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
/**
@@ -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,12 +74,10 @@ public class JsrBrowserDebugTool
private Server server;
- private ServerContainer 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);
@@ -106,10 +98,9 @@ public class JsrBrowserDebugTool
holder.setInitParameter("dirAllowed","true");
server.setHandler(context);
- ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
- container.addEndpoint(JsrBrowserSocket.class);
+ WebSocketServerContainerInitializer.configure(context,
+ (servletContext, container) -> container.addEndpoint(JsrBrowserSocket.class));
LOG.info("{} setup on port {}",this.getClass().getName(),port);
- return container;
}
}
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 4908c6cb29c..ce2d9f651af 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 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
public class EchoSocket
{
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/InvalidUpgradeServlet.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/InvalidUpgradeServlet.java
new file mode 100644
index 00000000000..44b49eeef3f
--- /dev/null
+++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/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;
+
+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.common.AcceptHash;
+
+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(), AcceptHash.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(), AcceptHash.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(), AcceptHash.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/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/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..343ec19e095
--- /dev/null
+++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java
@@ -0,0 +1,436 @@
+//
+// ========================================================================
+// 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.util.EnumSet;
+import java.util.concurrent.CompletableFuture;
+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.client.HttpClient;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+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.client.WebSocketUpgradeRequest;
+import org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.server.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.InvalidUpgradeServlet;
+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
+{
+ public ByteBufferPool bufferPool = new MappedByteBufferPool();
+
+ 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.setBufferPool(bufferPool);
+ client.setConnectTimeout(TimeUnit.SECONDS.toMillis(3));
+ client.setMaxIdleTimeout(TimeUnit.SECONDS.toMillis(3));
+ client.getPolicy().setIdleTimeout(TimeUnit.SECONDS.toMillis(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("/");
+
+ NativeWebSocketServletContainerInitializer.configure(context,
+ (servletContext, configuration) ->
+ {
+ configuration.getPolicy().setIdleTimeout(10000);
+ 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.getPolicy().setIdleTimeout(10000);
+
+ 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 testAltConnect() throws Exception
+ {
+ CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint();
+ URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo"));
+ HttpClient httpClient = new HttpClient();
+ try
+ {
+ httpClient.start();
+
+ WebSocketUpgradeRequest req = new WebSocketUpgradeRequest(new WebSocketClient(), httpClient, wsUri, cliSock);
+ req.header("X-Foo", "Req");
+ CompletableFuture sess = req.sendAsync();
+
+ sess.thenAccept((s) ->
+ {
+ System.out.printf("Session: %s%n", s);
+ s.close();
+ assertThat("Connect.UpgradeRequest", s.getUpgradeRequest(), notNullValue());
+ assertThat("Connect.UpgradeResponse", s.getUpgradeResponse(), notNullValue());
+ });
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
+ @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/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..fd8ecddf230
--- /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.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.common.WebSocketSession;
+import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
+import org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.server.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.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("/");
+
+ NativeWebSocketServletContainerInitializer.configure(context,
+ (servletContext, configuration) ->
+ {
+ configuration.getPolicy().setIdleTimeout(10000);
+ 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.getPolicy().setIdleTimeout(10000);
+
+ 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.getPolicy().setIdleTimeout(10000);
+
+ 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.getPolicy().setIdleTimeout(10000);
+
+ 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.getPolicy().setIdleTimeout(10000);
+
+ 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.getPolicy().setIdleTimeout(10000);
+
+ 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 = cliSock.getSession().getLocalAddress();
+ InetSocketAddress remote = 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.getPolicy().setMaxTextMessageSize(100 * 1024);
+ client.getPolicy().setIdleTimeout(10000);
+
+ 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.getPolicy().setMaxTextMessageSize(100 * 1024);
+ client.getPolicy().setIdleTimeout(10000);
+
+ 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/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java
deleted file mode 100644
index 811718de27d..00000000000
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientConnectTest.java
+++ /dev/null
@@ -1,463 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.client;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.anyOf;
-import static org.hamcrest.Matchers.greaterThanOrEqualTo;
-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;
-
-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.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.MappedByteBufferPool;
-import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.UpgradeException;
-import org.eclipse.jetty.websocket.common.AcceptHash;
-import org.eclipse.jetty.websocket.common.test.BlockheadConnection;
-import org.eclipse.jetty.websocket.common.test.BlockheadServer;
-import org.eclipse.jetty.websocket.common.test.Timeouts;
-import org.hamcrest.Matcher;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-/**
- * Various connect condition testing
- */
-@SuppressWarnings("Duplicates")
-public class ClientConnectTest
-{
- public ByteBufferPool bufferPool = new MappedByteBufferPool();
-
- private static BlockheadServer server;
- private WebSocketClient client;
-
- @SuppressWarnings("unchecked")
- private E assertExpectedError(ExecutionException e, JettyTrackingSocket wsocket, Matcher errorMatcher)
- {
- // Validate thrown cause
- Throwable cause = e.getCause();
-
- assertThat("ExecutionException.cause",cause,errorMatcher);
-
- // Validate websocket captured cause
- assertThat("Error Queue Length",wsocket.errorQueue.size(),greaterThanOrEqualTo(1));
- Throwable capcause = wsocket.errorQueue.poll();
- assertThat("Error Queue[0]",capcause,notNullValue());
- assertThat("Error Queue[0]",capcause,errorMatcher);
-
- // Validate that websocket didn't see an open event
- wsocket.assertNotOpened();
-
- // Return the captured cause
- return (E)capcause;
- }
-
- @BeforeEach
- public void startClient() throws Exception
- {
- client = new WebSocketClient();
- client.setBufferPool(bufferPool);
- client.setConnectTimeout(Timeouts.CONNECT_UNIT.toMillis(Timeouts.CONNECT));
- client.start();
- }
-
- @BeforeAll
- public static void startServer() throws Exception
- {
- server = new BlockheadServer();
- server.start();
- }
-
- @BeforeEach
- public void resetServerHandler()
- {
- // for each test, reset the server request handling to default
- server.resetRequestHandling();
- }
-
- @AfterEach
- public void stopClient() throws Exception
- {
- client.stop();
- }
-
- @AfterAll
- public static void stopServer() throws Exception
- {
- server.stop();
- }
-
- @Test
- public void testUpgradeRequest() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- Session sess = future.get(30,TimeUnit.SECONDS);
-
- wsocket.waitForConnected();
-
- assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
- assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
-
- sess.close();
- }
-
- @Test
- public void testAltConnect() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
- URI wsUri = server.getWsUri();
-
- HttpClient httpClient = new HttpClient();
- try
- {
- httpClient.start();
-
- WebSocketUpgradeRequest req = new WebSocketUpgradeRequest(new WebSocketClient(), httpClient, wsUri, wsocket);
- req.header("X-Foo", "Req");
- CompletableFuture sess = req.sendAsync();
-
- sess.thenAccept((s) -> {
- System.out.printf("Session: %s%n", s);
- s.close();
- assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
- assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
- });
- }
- finally
- {
- httpClient.stop();
- }
- }
-
- @Test
- public void testUpgradeWithAuthorizationHeader() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Hook into server connection creation
- CompletableFuture serverConnFut = new CompletableFuture<>();
- server.addConnectFuture(serverConnFut);
-
- URI wsUri = server.getWsUri();
- ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
- // actual value for this test is irrelevant, its important that this
- // header actually be sent with a value (the value specified)
- upgradeRequest.setHeader("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l");
- Future future = client.connect(wsocket,wsUri,upgradeRequest);
-
- try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
- {
- HttpFields upgradeRequestHeaders = serverConn.getUpgradeRequestHeaders();
-
- Session sess = future.get(30, TimeUnit.SECONDS);
-
- HttpField authHeader = upgradeRequestHeaders.getField(HttpHeader.AUTHORIZATION);
- assertThat("Server Request Authorization Header", authHeader, is(notNullValue()));
- assertThat("Server Request Authorization Value", authHeader.getValue(), is("Basic YWxhZGRpbjpvcGVuc2VzYW1l"));
- assertThat("Connect.UpgradeRequest", wsocket.connectUpgradeRequest, notNullValue());
- assertThat("Connect.UpgradeResponse", wsocket.connectUpgradeResponse, notNullValue());
-
- sess.close();
- }
- }
-
- @Test
- public void testBadHandshake() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Force 404 response, no upgrade for this test
- server.setRequestHandling((req, resp) -> {
- resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
- return true;
- });
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- // The attempt to get upgrade response future should throw error
- ExecutionException e = assertThrows(ExecutionException.class,
- ()-> future.get(30,TimeUnit.SECONDS));
-
- UpgradeException ue = assertExpectedError(e,wsocket,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
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Force 200 response, no response body content, no upgrade for this test
- server.setRequestHandling((req, resp) -> {
- resp.setStatus(HttpServletResponse.SC_OK);
- return true;
- });
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- // The attempt to get upgrade response future should throw error
- ExecutionException e = assertThrows(ExecutionException.class,
- ()-> future.get(30,TimeUnit.SECONDS));
-
- UpgradeException ue = assertExpectedError(e,wsocket,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
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Force 200 response, no response body content, incomplete websocket response headers, no actual upgrade for this test
- server.setRequestHandling((req, resp) -> {
- String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString());
- resp.setStatus(HttpServletResponse.SC_OK);
- resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), AcceptHash.hashKey(key));
- return true;
- });
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- // The attempt to get upgrade response future should throw error
- ExecutionException e = assertThrows(ExecutionException.class,
- ()-> future.get(30,TimeUnit.SECONDS));
-
- UpgradeException ue = assertExpectedError(e,wsocket,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
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Force 101 response, with invalid Connection header, invalid handshake
- server.setRequestHandling((req, resp) -> {
- String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString());
- resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
- resp.setHeader(HttpHeader.CONNECTION.toString(), "close");
- resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), AcceptHash.hashKey(key));
- return true;
- });
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- // The attempt to get upgrade response future should throw error
- ExecutionException e = assertThrows(ExecutionException.class,
- ()-> future.get(30,TimeUnit.SECONDS));
-
- UpgradeException ue = assertExpectedError(e,wsocket,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
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Force 101 response, with no Connection header, invalid handshake
- server.setRequestHandling((req, resp) -> {
- String key = req.getHeader(HttpHeader.SEC_WEBSOCKET_KEY.toString());
- resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
- // Intentionally leave out Connection header
- resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), AcceptHash.hashKey(key));
- return true;
- });
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- // The attempt to get upgrade response future should throw error
- ExecutionException e = assertThrows(ExecutionException.class,
- ()-> future.get(30,TimeUnit.SECONDS));
-
- UpgradeException ue = assertExpectedError(e,wsocket,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
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Force 101 response, with invalid response accept header
- server.setRequestHandling((req, resp) -> {
- resp.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
- resp.setHeader(HttpHeader.SEC_WEBSOCKET_ACCEPT.toString(), "rubbish");
- return true;
- });
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- // The attempt to get upgrade response future should throw error
- ExecutionException e = assertThrows(ExecutionException.class,
- ()-> future.get(30,TimeUnit.SECONDS));
-
- UpgradeException ue = assertExpectedError(e,wsocket,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 testConnectionNotAccepted() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- 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(wsocket, wsUri);
-
- // Intentionally not accept incoming socket.
- // serverSocket.accept();
-
- try
- {
- future.get(3, TimeUnit.SECONDS);
- fail("Should have Timed Out");
- }
- catch (ExecutionException e)
- {
- assertExpectedError(e, wsocket, instanceOf(UpgradeException.class));
- // Possible Passing Path (active session wait timeout)
- wsocket.assertNotOpened();
- }
- catch (TimeoutException e)
- {
- // Possible Passing Path (concurrency timeout)
- wsocket.assertNotOpened();
- }
- }
- }
-
- @Test
- public void testConnectionRefused() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- // Intentionally bad port with nothing listening on it
- URI wsUri = new URI("ws://127.0.0.1:1");
-
- try
- {
- Future future = client.connect(wsocket,wsUri);
-
- // The attempt to get upgrade response future should throw error
- future.get(3,TimeUnit.SECONDS);
- fail("Expected ExecutionException -> ConnectException");
- }
- catch (ConnectException e)
- {
- Throwable t = wsocket.errorQueue.remove();
- assertThat("Error Queue[0]",t,instanceOf(ConnectException.class));
- wsocket.assertNotOpened();
- }
- catch (ExecutionException e)
- {
- assertExpectedError(e, wsocket,
- anyOf(
- instanceOf(UpgradeException.class),
- instanceOf(SocketTimeoutException.class),
- instanceOf(ConnectException.class)));
- }
- }
-
- @Test
- public void testConnectionTimeout_Concurrent() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- 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(wsocket, 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(3, TimeUnit.SECONDS));
-
- if (e instanceof ExecutionException)
- {
- assertExpectedError((ExecutionException) e, wsocket, anyOf(
- instanceOf(ConnectException.class),
- instanceOf(UpgradeException.class)
- ));
- }
- else
- {
- assertThat("Should have been a TimeoutException", e, instanceOf(TimeoutException.class));
- }
- }
- }
-}
diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java
deleted file mode 100644
index adee6b543d4..00000000000
--- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.websocket.client;
-
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.websocket.api.BatchMode;
-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.common.WebSocketFrame;
-import org.eclipse.jetty.websocket.common.WebSocketSession;
-import org.eclipse.jetty.websocket.common.frames.TextFrame;
-import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
-import org.eclipse.jetty.websocket.common.test.BlockheadConnection;
-import org.eclipse.jetty.websocket.common.test.BlockheadServer;
-import org.eclipse.jetty.websocket.common.test.Timeouts;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-public class WebSocketClientTest
-{
- private static BlockheadServer server;
- private WebSocketClient client;
-
- @BeforeEach
- public void startClient() throws Exception
- {
- client = new WebSocketClient();
- client.start();
- }
-
- @BeforeAll
- public static void startServer() throws Exception
- {
- server = new BlockheadServer();
- server.getPolicy().setMaxTextMessageSize(200 * 1024);
- server.getPolicy().setMaxBinaryMessageSize(200 * 1024);
- server.start();
- }
-
- @AfterEach
- public void stopClient() throws Exception
- {
- client.stop();
- }
-
- @AfterAll
- public static void stopServer() throws Exception
- {
- server.stop();
- }
-
- @Test
- public void testAddExtension_NotInstalled() throws Exception
- {
- JettyTrackingSocket cliSock = new JettyTrackingSocket();
-
- client.getPolicy().setIdleTimeout(10000);
-
- URI wsUri = server.getWsUri();
- 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
- {
- JettyTrackingSocket cliSock = new JettyTrackingSocket();
-
- client.getPolicy().setIdleTimeout(10000);
-
- // Hook into server connection creation
- CompletableFuture serverConnFut = new CompletableFuture<>();
- server.addConnectFuture(serverConnFut);
-
- URI wsUri = server.getWsUri();
- ClientUpgradeRequest request = new ClientUpgradeRequest();
- request.setSubProtocols("echo");
- Future future = client.connect(cliSock,wsUri,request);
-
- try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
- {
- // Setup echo of frames on server side
- serverConn.setIncomingFrameConsumer((frame)->{
- WebSocketFrame copy = WebSocketFrame.copy(frame);
- copy.setMask(null); // strip client mask (if present)
- serverConn.write(copy);
- });
-
- 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());
-
- cliSock.assertWasOpened();
- cliSock.assertNotClosed();
-
- Collection sessions = client.getOpenSessions();
- assertThat("client.connectionManager.sessions.size",sessions.size(),is(1));
-
- RemoteEndpoint remote = cliSock.getSession().getRemote();
- remote.sendStringByFuture("Hello World!");
- if (remote.getBatchMode() == BatchMode.ON)
- remote.flush();
-
- // wait for response from server
- String received = cliSock.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
- assertThat("Message", received, containsString("Hello World"));
- }
- }
-
- @Test
- public void testBasicEcho_UsingCallback() throws Exception
- {
- client.setMaxIdleTimeout(160000);
- JettyTrackingSocket cliSock = new JettyTrackingSocket();
-
- // Hook into server connection creation
- CompletableFuture serverConnFut = new CompletableFuture<>();
- server.addConnectFuture(serverConnFut);
-
- URI wsUri = server.getWsUri();
- ClientUpgradeRequest request = new ClientUpgradeRequest();
- request.setSubProtocols("echo");
- Future future = client.connect(cliSock,wsUri,request);
-
- try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
- {
- 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());
-
- cliSock.assertWasOpened();
- cliSock.assertNotClosed();
-
- Collection sessions = client.getBeans(WebSocketSession.class);
- assertThat("client.connectionManager.sessions.size", sessions.size(), is(1));
-
- FutureWriteCallback callback = new FutureWriteCallback();
-
- cliSock.getSession().getRemote().sendString("Hello World!", callback);
- callback.get(1, TimeUnit.SECONDS);
- }
- }
-
- @Test
- public void testBasicEcho_FromServer() throws Exception
- {
- // Hook into server connection creation
- CompletableFuture serverConnFut = new CompletableFuture<>();
- server.addConnectFuture(serverConnFut);
-
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
- Future future = client.connect(wsocket,server.getWsUri());
-
- try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
- {
- // Validate connect
- 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());
-
- // Have server send initial message
- serverConn.write(new TextFrame().setPayload("Hello World"));
-
- // Verify connect
- future.get(30, TimeUnit.SECONDS);
- wsocket.assertWasOpened();
-
- String received = wsocket.messageQueue.poll(Timeouts.POLL_EVENT, Timeouts.POLL_EVENT_UNIT);
- assertThat("Message", received, containsString("Hello World"));
- }
- }
-
- @Test
- public void testLocalRemoteAddress() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- future.get(30,TimeUnit.SECONDS);
-
- assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
-
- InetSocketAddress local = wsocket.getSession().getLocalAddress();
- InetSocketAddress remote = wsocket.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
- {
- MaxMessageSocket wsocket = new MaxMessageSocket();
-
- // Hook into server connection creation
- CompletableFuture serverConnFut = new CompletableFuture<>();
- server.addConnectFuture(serverConnFut);
-
- URI wsUri = server.getWsUri();
- Future future = client.connect(wsocket,wsUri);
-
- try (BlockheadConnection serverConn = serverConnFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT))
- {
- // Setup echo of frames on server side
- serverConn.setIncomingFrameConsumer((frame)->{
- WebSocketFrame copy = WebSocketFrame.copy(frame);
- copy.setMask(null); // strip client mask (if present)
- serverConn.write(copy);
- });
-
- wsocket.awaitConnect(1,TimeUnit.SECONDS);
-
- Session sess = future.get(30,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
- byte buf[] = new byte[80 * 1024];
- Arrays.fill(buf,(byte)'x');
- String msg = StringUtil.toUTF8String(buf,0,buf.length);
-
- wsocket.getSession().getRemote().sendStringByFuture(msg);
-
- // wait for response from server
- wsocket.waitForMessage(1, TimeUnit.SECONDS);
-
- wsocket.assertMessage(msg);
-
- assertTrue(wsocket.dataLatch.await(2, TimeUnit.SECONDS));
- }
- }
-
- @Test
- public void testParameterMap() throws Exception
- {
- JettyTrackingSocket wsocket = new JettyTrackingSocket();
-
- URI wsUri = server.getWsUri().resolve("/test?snack=cashews&amount=handful&brand=off");
- Future future = client.connect(wsocket,wsUri);
-
- future.get(30,TimeUnit.SECONDS);
-
- assertTrue(wsocket.openLatch.await(1,TimeUnit.SECONDS));
-
- Session session = wsocket.getSession();
- UpgradeRequest req = session.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());
- }
-}
diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java
index 4559ae1fdb7..7853b548fc3 100644
--- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java
+++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/NativeWebSocketServletContainerInitializer.java
@@ -19,40 +19,98 @@
package org.eclipse.jetty.websocket.server;
import java.util.Set;
-
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.listener.ContainerInitializer;
public class NativeWebSocketServletContainerInitializer implements ServletContainerInitializer
{
+ public static final String ATTR_KEY = NativeWebSocketConfiguration.class.getName();
+
+ public interface Configurator
+ {
+ void accept(ServletContext servletContext, NativeWebSocketConfiguration nativeWebSocketConfiguration);
+ }
+
+ /**
+ * Initialize the ServletContext with the default (and empty) {@link NativeWebSocketConfiguration}
+ *
+ * @param context the context to work with
+ */
+ public static void initialize(ServletContext context)
+ {
+ NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration)context.getAttribute(ATTR_KEY);
+ if (configuration != null)
+ return; // it exists.
+
+ // Not provided to us, create a new default one.
+ configuration = new NativeWebSocketConfiguration(context);
+ context.setAttribute(ATTR_KEY, configuration);
+
+ // Attach default configuration to context lifecycle
+ if (context instanceof ContextHandler.Context)
+ {
+ ContextHandler handler = ((ContextHandler.Context)context).getContextHandler();
+ // Let ContextHandler handle configuration lifecycle
+ handler.addManaged(configuration);
+ }
+ }
+
+ /**
+ * Configure the {@link ServletContextHandler} to call the {@link NativeWebSocketServletContainerInitializer}
+ * during the {@link ServletContext} initialization phase.
+ *
+ * @param context the context to add listener to.
+ */
+ public static void configure(ServletContextHandler context)
+ {
+ context.addEventListener(ContainerInitializer.asContextListener(new NativeWebSocketServletContainerInitializer()));
+ }
+
+ /**
+ * Configure the {@link ServletContextHandler} to call the {@link NativeWebSocketServletContainerInitializer}
+ * 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 NativeWebSocketConfiguration} to
+ * be configured during {@link ServletContext} initialization phase
+ */
+ public static void configure(ServletContextHandler context, Configurator configurator)
+ {
+ context.addEventListener(
+ ContainerInitializer
+ .asContextListener(new NativeWebSocketServletContainerInitializer())
+ .setPostOnStartupConsumer((servletContext) ->
+ {
+ NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration)servletContext.getAttribute(ATTR_KEY);
+ configurator.accept(servletContext, configuration);
+ }));
+ }
+
+ /**
+ * Obtain the default {@link NativeWebSocketConfiguration} from the {@link ServletContext}
+ *
+ * @param context the context to work with
+ * @return the default {@link NativeWebSocketConfiguration}
+ * @see #initialize(ServletContext)
+ * @see #configure(ServletContextHandler)
+ * @see #configure(ServletContextHandler, Configurator)
+ * @deprecated use {@link #configure(ServletContextHandler, Configurator)} instead
+ */
+ @Deprecated
public static NativeWebSocketConfiguration getDefaultFrom(ServletContext context)
{
- final String KEY = NativeWebSocketConfiguration.class.getName();
-
- NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getAttribute(KEY);
- if (configuration == null)
- {
- // Not provided to us, create a new default one.
- configuration = new NativeWebSocketConfiguration(context);
- context.setAttribute(KEY, configuration);
-
- // Attach default configuration to context lifecycle
- if (context instanceof ContextHandler.Context)
- {
- ContextHandler handler = ((ContextHandler.Context)context).getContextHandler();
- // Let ContextHandler handle configuration lifecycle
- handler.addManaged(configuration);
- }
- }
- return configuration;
+ initialize(context);
+ return (NativeWebSocketConfiguration)context.getAttribute(ATTR_KEY);
}
-
+
@Override
public void onStartup(Set> c, ServletContext ctx)
{
// initialize
- getDefaultFrom(ctx);
+ initialize(ctx);
}
}