Issue #3698 - Reworking WebSocket SCI's to be more flexible in embedded
+ Added new SCIOnStartupListener that allows for manual wiring up of ServletContainerInitializers (with no bytecode scanning) in embedded jetty usages + Introduced .initialized(context) and .configure(context) to both of the websocket SCIs + Moved ClientConnectTest to jetty-websocket-tests + Moved WebSocketClientTest to jetty-websocket-tests + Made moved tests not use BlockheadServer + Made moved tests use new .configure(context, lambda) methods + Also allowing javax.websocket.server SCI implementation to add ContextDestroyListener during the execution of another ServletContextListener. Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
parent
ad3be199e8
commit
54888d2541
|
@ -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.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* ServletContextHandler context = new ServletContextHandler();
|
||||||
|
* ServletContainerInitializer corpSci = new MyCorporateSCI();
|
||||||
|
* context.addEventListener(ContainerInitializer.asContextListener(corpSci));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The {@link ServletContainerInitializer} will have its {@link ServletContainerInitializer#onStartup(Set, ServletContext)}
|
||||||
|
* method called with the manually configured list of {@code Set<Class<?>> c} set.
|
||||||
|
* In other words, this usage does not perform bytecode or annotation scanning against the classes in
|
||||||
|
* your {@code ServletContextHandler} or {@code WebAppContext}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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<String> classNames;
|
||||||
|
private Set<Class<?>> classes = new HashSet<>();
|
||||||
|
private Consumer<ServletContext> postOnStartupConsumer;
|
||||||
|
|
||||||
|
public SCIAsContextListener(ServletContainerInitializer sci)
|
||||||
|
{
|
||||||
|
this.sci = sci;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add classes to be passed to the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} call.
|
||||||
|
* <p>
|
||||||
|
* Note that these classes will be loaded using the context classloader for the ServletContext
|
||||||
|
* initialization phase.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This consumer is typically used for Embedded Jetty users to configure Jetty for their specific needs.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param consumer the consumer to execute after the SCI has executed
|
||||||
|
* @return this configured {@link SCIAsContextListener} instance.
|
||||||
|
*/
|
||||||
|
public SCIAsContextListener setPostOnStartupConsumer(Consumer<ServletContext> 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<Class<?>> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.jsr356.server.deploy;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.ServletContainerInitializer;
|
import javax.servlet.ServletContainerInitializer;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
|
@ -34,8 +33,10 @@ import javax.websocket.server.ServerEndpoint;
|
||||||
import javax.websocket.server.ServerEndpointConfig;
|
import javax.websocket.server.ServerEndpointConfig;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.listener.ContainerInitializer;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
@ -53,6 +54,7 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
|
||||||
public static final String ADD_DYNAMIC_FILTER_KEY = "org.eclipse.jetty.websocket.jsr356.addDynamicFilter";
|
public static final String ADD_DYNAMIC_FILTER_KEY = "org.eclipse.jetty.websocket.jsr356.addDynamicFilter";
|
||||||
private static final Logger LOG = Log.getLogger(WebSocketServerContainerInitializer.class);
|
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 HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.jsr356.HttpClient";
|
||||||
|
public static final String ATTR_JAVAX_SERVER_CONTAINER = javax.websocket.server.ServerContainer.class.getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DestroyListener
|
* DestroyListener
|
||||||
|
@ -130,58 +132,142 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
|
||||||
return defValue;
|
return defValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public interface Configurator
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
|
|
||||||
{
|
{
|
||||||
// Create Basic components
|
void accept(ServletContext servletContext, ServerContainer serverContainer) throws DeploymentException;
|
||||||
NativeWebSocketConfiguration nativeWebSocketConfiguration = NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext());
|
}
|
||||||
|
|
||||||
// Build HttpClient
|
/**
|
||||||
HttpClient httpClient = (HttpClient) context.getServletContext().getAttribute(HTTPCLIENT_ATTRIBUTE);
|
* @param context the {@link ServletContextHandler} to use
|
||||||
if ((httpClient == null) && (context.getServer() != null))
|
* @return a configured {@link ServerContainer} instance
|
||||||
{
|
* @throws ServletException if the {@link WebSocketUpgradeFilter} cannot be configured
|
||||||
httpClient = (HttpClient) context.getServer().getAttribute(HTTPCLIENT_ATTRIBUTE);
|
* @deprecated use {@link #configure(ServletContextHandler, Configurator)} instead
|
||||||
}
|
* @see #configure(ServletContextHandler, Configurator)
|
||||||
|
*/
|
||||||
// Create the Jetty ServerContainer implementation
|
@Deprecated
|
||||||
ServerContainer jettyContainer = new ServerContainer(nativeWebSocketConfiguration, httpClient);
|
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
|
||||||
context.addBean(jettyContainer);
|
{
|
||||||
|
ServletContext servletContext = context.getServletContext();
|
||||||
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
|
initialize(servletContext);
|
||||||
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
|
return (ServerContainer)servletContext.getAttribute(ATTR_JAVAX_SERVER_CONTAINER);
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use {@link #configureContext(ServletContextHandler)} instead
|
|
||||||
* @param context not used
|
* @param context not used
|
||||||
* @param jettyContext the {@link ServletContextHandler} to use
|
* @param jettyContext the {@link ServletContextHandler} to use
|
||||||
* @return a configured {@link ServerContainer} instance
|
* @return a configured {@link ServerContainer} instance
|
||||||
* @throws ServletException if the {@link WebSocketUpgradeFilter} cannot be configured
|
* @throws ServletException if the {@link WebSocketUpgradeFilter} cannot be configured
|
||||||
|
* @deprecated use {@link #configure(ServletContextHandler, Configurator)} instead
|
||||||
|
* @see #configure(ServletContextHandler, Configurator)
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
|
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}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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
|
@Override
|
||||||
|
@ -205,12 +291,11 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
|
||||||
throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support unavailable");
|
throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support unavailable");
|
||||||
}
|
}
|
||||||
|
|
||||||
ServletContextHandler jettyContext = (ServletContextHandler)handler;
|
|
||||||
|
|
||||||
try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader()))
|
try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader()))
|
||||||
{
|
{
|
||||||
// Create the Jetty ServerContainer implementation
|
// 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
|
context.addListener(new ContextDestroyListener()); // make sure context is cleaned up when the context stops
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.jsr356.server;
|
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.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -29,7 +26,6 @@ import java.net.URI;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.websocket.ClientEndpoint;
|
import javax.websocket.ClientEndpoint;
|
||||||
import javax.websocket.ContainerProvider;
|
import javax.websocket.ContainerProvider;
|
||||||
import javax.websocket.OnMessage;
|
import javax.websocket.OnMessage;
|
||||||
|
@ -43,10 +39,12 @@ import org.eclipse.jetty.server.ServerConnector;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
|
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
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
|
public class BinaryStreamTest
|
||||||
{
|
{
|
||||||
private static final String PATH = "/echo";
|
private static final String PATH = "/echo";
|
||||||
|
@ -63,9 +61,11 @@ public class BinaryStreamTest
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
|
||||||
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
|
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
|
||||||
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
|
WebSocketServerContainerInitializer.configure(context, (servletContext, container) ->
|
||||||
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerBinaryStreamer.class, PATH).build();
|
{
|
||||||
container.addEndpoint(config);
|
ServerEndpointConfig config = ServerEndpointConfig.Builder.create(ServerBinaryStreamer.class, PATH).build();
|
||||||
|
container.addEndpoint(config);
|
||||||
|
});
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,10 @@
|
||||||
package org.eclipse.jetty.websocket.jsr356.server.browser;
|
package org.eclipse.jetty.websocket.jsr356.server.browser;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.websocket.DeploymentException;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Server;
|
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.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
|
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
|
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +63,6 @@ public class JsrBrowserDebugTool
|
||||||
JsrBrowserDebugTool tool = new JsrBrowserDebugTool();
|
JsrBrowserDebugTool tool = new JsrBrowserDebugTool();
|
||||||
tool.setupServer(port);
|
tool.setupServer(port);
|
||||||
tool.server.start();
|
tool.server.start();
|
||||||
tool.server.dumpStdErr();
|
|
||||||
LOG.info("Server available at {}", tool.server.getURI());
|
LOG.info("Server available at {}", tool.server.getURI());
|
||||||
tool.server.join();
|
tool.server.join();
|
||||||
}
|
}
|
||||||
|
@ -80,12 +74,10 @@ public class JsrBrowserDebugTool
|
||||||
|
|
||||||
private Server server;
|
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 = new Server();
|
||||||
|
|
||||||
server.setDumpAfterStart(true);
|
|
||||||
|
|
||||||
HttpConfiguration httpConf = new HttpConfiguration();
|
HttpConfiguration httpConf = new HttpConfiguration();
|
||||||
httpConf.setSendServerVersion(true);
|
httpConf.setSendServerVersion(true);
|
||||||
|
|
||||||
|
@ -106,10 +98,9 @@ public class JsrBrowserDebugTool
|
||||||
holder.setInitParameter("dirAllowed","true");
|
holder.setInitParameter("dirAllowed","true");
|
||||||
server.setHandler(context);
|
server.setHandler(context);
|
||||||
|
|
||||||
ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
|
WebSocketServerContainerInitializer.configure(context,
|
||||||
container.addEndpoint(JsrBrowserSocket.class);
|
(servletContext, container) -> container.addEndpoint(JsrBrowserSocket.class));
|
||||||
|
|
||||||
LOG.info("{} setup on port {}",this.getClass().getName(),port);
|
LOG.info("{} setup on port {}",this.getClass().getName(),port);
|
||||||
return container;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.OnWebSocketMessage;
|
||||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@WebSocket
|
@WebSocket
|
||||||
public class EchoSocket
|
public class EchoSocket
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, List<String>> 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 extends Throwable> E assertExpectedError(ExecutionException e, CloseTrackingEndpoint wsocket, Matcher<Throwable> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Session> 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<WebSocketSession> 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<Session> 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<WebSocketSession> 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<Session> 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<Session> 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 <code>@WebSocket(maxTextMessageSize = 100*1024)</code> 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<Session> 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<Session> future = client.connect(cliSock, wsUri, request);
|
||||||
|
|
||||||
|
try (Session sess = future.get(5, TimeUnit.SECONDS))
|
||||||
|
{
|
||||||
|
UpgradeRequest req = sess.getUpgradeRequest();
|
||||||
|
assertThat("Upgrade Request", req, notNullValue());
|
||||||
|
|
||||||
|
Map<String, List<String>> 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]=")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 extends Throwable> E assertExpectedError(ExecutionException e, JettyTrackingSocket wsocket, Matcher<Throwable> 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<Session> 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<Session> 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<BlockheadConnection> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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<Session> 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
|
||||||
server.addConnectFuture(serverConnFut);
|
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
|
||||||
request.setSubProtocols("echo");
|
|
||||||
Future<Session> 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<WebSocketSession> 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<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
|
||||||
server.addConnectFuture(serverConnFut);
|
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
|
||||||
request.setSubProtocols("echo");
|
|
||||||
Future<Session> 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<WebSocketSession> 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<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
|
||||||
server.addConnectFuture(serverConnFut);
|
|
||||||
|
|
||||||
JettyTrackingSocket wsocket = new JettyTrackingSocket();
|
|
||||||
Future<Session> 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<Session> 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 <code>@WebSocket(maxTextMessageSize = 100*1024)</code> behaves as expected.
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
* on test failure
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testMaxMessageSize() throws Exception
|
|
||||||
{
|
|
||||||
MaxMessageSocket wsocket = new MaxMessageSocket();
|
|
||||||
|
|
||||||
// Hook into server connection creation
|
|
||||||
CompletableFuture<BlockheadConnection> serverConnFut = new CompletableFuture<>();
|
|
||||||
server.addConnectFuture(serverConnFut);
|
|
||||||
|
|
||||||
URI wsUri = server.getWsUri();
|
|
||||||
Future<Session> 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<Session> 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<String, List<String>> 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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,40 +19,98 @@
|
||||||
package org.eclipse.jetty.websocket.server;
|
package org.eclipse.jetty.websocket.server;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.ServletContainerInitializer;
|
import javax.servlet.ServletContainerInitializer;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
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 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)
|
public static NativeWebSocketConfiguration getDefaultFrom(ServletContext context)
|
||||||
{
|
{
|
||||||
final String KEY = NativeWebSocketConfiguration.class.getName();
|
initialize(context);
|
||||||
|
return (NativeWebSocketConfiguration)context.getAttribute(ATTR_KEY);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartup(Set<Class<?>> c, ServletContext ctx)
|
public void onStartup(Set<Class<?>> c, ServletContext ctx)
|
||||||
{
|
{
|
||||||
// initialize
|
// initialize
|
||||||
getDefaultFrom(ctx);
|
initialize(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue