Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Lachlan Roberts 2021-04-30 14:45:16 +10:00
commit 409ad3b100
34 changed files with 849 additions and 135 deletions

View File

@ -40,6 +40,7 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.core.Behavior;
@ -291,7 +292,12 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re
headers(headers -> headers.add(HttpHeader.SEC_WEBSOCKET_EXTENSIONS, extensionString));
// Notify the listener which may change the headers directly.
notifyUpgradeListeners((listener) -> listener.onHandshakeRequest(this));
Exception listenerError = notifyUpgradeListeners((listener) -> listener.onHandshakeRequest(this));
if (listenerError != null)
{
abort(listenerError);
return;
}
// Check if extensions were set in the headers from the upgrade listener.
String extsAfterListener = String.join(",", getHeaders().getCSV(HttpHeader.SEC_WEBSOCKET_EXTENSIONS, true));
@ -306,8 +312,9 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re
}
}
private void notifyUpgradeListeners(Consumer<UpgradeListener> action)
private Exception notifyUpgradeListeners(Consumer<UpgradeListener> action)
{
MultiException multiException = null;
for (UpgradeListener listener : upgradeListeners)
{
try
@ -317,8 +324,13 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re
catch (Throwable t)
{
LOG.info("Exception while invoking listener {}", listener, t);
if (multiException == null)
multiException = new MultiException();
multiException.add(t);
}
}
return multiException;
}
public void upgrade(HttpResponse response, EndPoint endPoint)
@ -437,7 +449,9 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re
WebSocketConnection wsConnection = new WebSocketConnection(endPoint, httpClient.getExecutor(), httpClient.getScheduler(), bufferPool, coreSession);
wsClient.getEventListeners().forEach(wsConnection::addEventListener);
coreSession.setWebSocketConnection(wsConnection);
notifyUpgradeListeners((listener) -> listener.onHandshakeResponse(this, response));
Exception listenerError = notifyUpgradeListeners((listener) -> listener.onHandshakeResponse(this, response));
if (listenerError != null)
throw new WebSocketException("onHandshakeResponse error", listenerError);
// Now swap out the connection
try

View File

@ -39,9 +39,9 @@ public class WebSocketServerComponents extends WebSocketComponents
public static final String WEBSOCKET_DEFLATER_POOL_ATTRIBUTE = "jetty.websocket.deflater";
public static final String WEBSOCKET_BUFFER_POOL_ATTRIBUTE = "jetty.websocket.bufferPool";
WebSocketServerComponents(InflaterPool inflaterPool, DeflaterPool deflaterPool, ByteBufferPool bufferPool)
WebSocketServerComponents(InflaterPool inflaterPool, DeflaterPool deflaterPool, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory)
{
super(null, null, bufferPool, inflaterPool, deflaterPool);
super(null, objectFactory, bufferPool, inflaterPool, deflaterPool);
}
/**
@ -78,7 +78,10 @@ public class WebSocketServerComponents extends WebSocketComponents
if (bufferPool == null)
bufferPool = server.getBean(ByteBufferPool.class);
WebSocketComponents serverComponents = new WebSocketServerComponents(inflaterPool, deflaterPool, bufferPool);
DecoratedObjectFactory objectFactory = (DecoratedObjectFactory)servletContext.getAttribute(DecoratedObjectFactory.ATTR);
WebSocketComponents serverComponents = new WebSocketServerComponents(inflaterPool, deflaterPool, bufferPool, objectFactory);
if (objectFactory != null)
serverComponents.unmanage(objectFactory);
// These components may be managed by the server but not yet started.
// In this case we don't want them to be managed by the components as well.

View File

@ -18,17 +18,18 @@ import java.util.List;
import jakarta.websocket.ClientEndpoint;
import jakarta.websocket.ClientEndpointConfig;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.jakarta.common.ClientEndpointConfigWrapper;
public class AnnotatedClientEndpointConfig extends ClientEndpointConfigWrapper
{
public AnnotatedClientEndpointConfig(ClientEndpoint anno)
public AnnotatedClientEndpointConfig(ClientEndpoint anno, WebSocketComponents components)
{
Configurator configurator;
try
{
configurator = anno.configurator().getDeclaredConstructor().newInstance();
configurator = components.getObjectFactory().createInstance(anno.configurator());
}
catch (Exception e)
{

View File

@ -229,9 +229,16 @@ public class JakartaWebSocketClientContainer extends JakartaWebSocketContainer i
@Override
public Session connectToServer(final Endpoint endpoint, final ClientEndpointConfig providedConfig, final URI path) throws DeploymentException, IOException
{
ClientEndpointConfig config = providedConfig;
if (config == null)
ClientEndpointConfig config;
if (providedConfig == null)
{
config = new BasicClientEndpointConfig();
}
else
{
config = providedConfig;
components.getObjectFactory().decorate(providedConfig.getConfigurator());
}
ConfiguredEndpoint instance = new ConfiguredEndpoint(endpoint, config);
return connect(instance, path);
@ -240,6 +247,7 @@ public class JakartaWebSocketClientContainer extends JakartaWebSocketContainer i
@Override
public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException
{
// The Configurator will be decorated when it is created in the getAnnotatedConfig method.
ClientEndpointConfig config = getAnnotatedConfig(endpoint);
ConfiguredEndpoint instance = new ConfiguredEndpoint(endpoint, config);
return connect(instance, path);
@ -275,7 +283,7 @@ public class JakartaWebSocketClientContainer extends JakartaWebSocketContainer i
if (anno == null)
throw new DeploymentException("Could not get ClientEndpoint annotation for " + endpoint.getClass().getName());
return new AnnotatedClientEndpointConfig(anno);
return new AnnotatedClientEndpointConfig(anno, components);
}
@Override

View File

@ -47,7 +47,7 @@ public class JakartaWebSocketClientFrameHandlerFactory extends JakartaWebSocketF
if (endpointClass.getAnnotation(ClientEndpoint.class) == null)
return null;
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig, components);
return discoverJakartaFrameHandlerMetadata(endpointClass, metadata);
}
}

View File

@ -66,6 +66,11 @@ public abstract class JakartaWebSocketContainer extends ContainerLifeCycle imple
return components.getObjectFactory();
}
public WebSocketComponents getWebSocketComponents()
{
return components;
}
public long getDefaultAsyncSendTimeout()
{
return defaultCustomizer.getWriteTimeout().toMillis();

View File

@ -38,6 +38,7 @@ import jakarta.websocket.PongMessage;
import jakarta.websocket.Session;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.core.internal.messages.MessageSink;
@ -102,10 +103,12 @@ public abstract class JakartaWebSocketFrameHandlerFactory
protected final JakartaWebSocketContainer container;
protected final InvokerUtils.ParamIdentifier paramIdentifier;
protected final WebSocketComponents components;
public JakartaWebSocketFrameHandlerFactory(JakartaWebSocketContainer container, InvokerUtils.ParamIdentifier paramIdentifier)
{
this.container = container;
this.components = container.getWebSocketComponents();
this.paramIdentifier = paramIdentifier == null ? InvokerUtils.PARAM_IDENTITY : paramIdentifier;
}
@ -165,6 +168,9 @@ public abstract class JakartaWebSocketFrameHandlerFactory
errorHandle = InvokerUtils.bindTo(errorHandle, endpoint);
pongHandle = InvokerUtils.bindTo(pongHandle, endpoint);
// Decorate the endpointInstance while we are still upgrading for access to things like HttpSession.
components.getObjectFactory().decorate(endpoint);
return new JakartaWebSocketFrameHandler(
container,
upgradeRequest,
@ -248,7 +254,7 @@ public abstract class JakartaWebSocketFrameHandlerFactory
protected JakartaWebSocketFrameHandlerMetadata createEndpointMetadata(EndpointConfig endpointConfig)
{
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig, container.getWebSocketComponents());
MethodHandles.Lookup lookup = getServerMethodHandleLookup();
Method openMethod = ReflectUtils.findMethod(Endpoint.class, "onOpen", Session.class, EndpointConfig.class);

View File

@ -18,6 +18,7 @@ import java.lang.invoke.MethodHandle;
import jakarta.websocket.Encoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.encoders.AvailableEncoders;
@ -64,10 +65,10 @@ public class JakartaWebSocketFrameHandlerMetadata
*/
private UriTemplatePathSpec uriTemplatePathSpec;
public JakartaWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig)
public JakartaWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig, WebSocketComponents components)
{
this.availableDecoders = new AvailableDecoders(endpointConfig);
this.availableEncoders = new AvailableEncoders(endpointConfig);
this.availableDecoders = new AvailableDecoders(endpointConfig, components);
this.availableEncoders = new AvailableEncoders(endpointConfig, components);
}
public AvailableDecoders getAvailableDecoders()

View File

@ -74,8 +74,8 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
this.coreSession = coreSession;
this.frameHandler = frameHandler;
this.sessionId = UUID.randomUUID().toString();
this.availableDecoders = new AvailableDecoders(endpointConfig);
this.availableEncoders = new AvailableEncoders(endpointConfig);
this.availableDecoders = new AvailableDecoders(endpointConfig, container.getWebSocketComponents());
this.availableEncoders = new AvailableEncoders(endpointConfig, container.getWebSocketComponents());
if (endpointConfig instanceof PathParamProvider)
{

View File

@ -26,6 +26,7 @@ import java.util.stream.Stream;
import jakarta.websocket.Decoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@ -34,9 +35,12 @@ public class AvailableDecoders implements Iterable<RegisteredDecoder>, Closeable
{
private final List<RegisteredDecoder> registeredDecoders = new ArrayList<>();
private final EndpointConfig config;
private final WebSocketComponents components;
public AvailableDecoders(EndpointConfig config)
public AvailableDecoders(EndpointConfig config, WebSocketComponents components)
{
this.components = Objects.requireNonNull(components);
// Register the Config Based Decoders.
this.config = Objects.requireNonNull(config);
registerAll(config.getDecoders());
@ -73,7 +77,7 @@ public class AvailableDecoders implements Iterable<RegisteredDecoder>, Closeable
private void registerPrimitive(Class<? extends Decoder> decoderClass, Class<? extends Decoder> interfaceType, Class<?> type)
{
registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, config, true));
registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, config, components, true));
}
private void register(Class<? extends Decoder> decoder)
@ -152,7 +156,7 @@ public class AvailableDecoders implements Iterable<RegisteredDecoder>, Closeable
return;
}
registeredDecoders.add(new RegisteredDecoder(decoder, interfaceClass, objectType, config));
registeredDecoders.add(new RegisteredDecoder(decoder, interfaceClass, objectType, config, components));
}
public RegisteredDecoder getFirstRegisteredDecoder(Class<?> type)

View File

@ -17,6 +17,7 @@ import java.lang.reflect.InvocationTargetException;
import jakarta.websocket.Decoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.jakarta.common.InitException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,21 +33,23 @@ public class RegisteredDecoder
public final Class<?> objectType;
public final boolean primitive;
public final EndpointConfig config;
private final WebSocketComponents components;
private Decoder instance;
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig)
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig, WebSocketComponents components)
{
this(decoder, interfaceType, objectType, endpointConfig, false);
this(decoder, interfaceType, objectType, endpointConfig, components, false);
}
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig, boolean primitive)
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig, WebSocketComponents components, boolean primitive)
{
this.decoder = decoder;
this.interfaceType = interfaceType;
this.objectType = objectType;
this.primitive = primitive;
this.config = endpointConfig;
this.components = components;
}
public boolean implementsInterface(Class<? extends Decoder> type)
@ -65,7 +68,7 @@ public class RegisteredDecoder
{
try
{
instance = decoder.getConstructor().newInstance();
instance = components.getObjectFactory().createInstance(decoder);
instance.init(config);
return (T)instance;
}

View File

@ -25,6 +25,7 @@ import java.util.stream.Collectors;
import jakarta.websocket.Encoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@ -36,79 +37,15 @@ public class AvailableEncoders implements Predicate<Class<?>>, Closeable
{
private static final Logger LOG = LoggerFactory.getLogger(AvailableEncoders.class);
public static class RegisteredEncoder
{
public final Class<? extends Encoder> encoder;
public final Class<? extends Encoder> interfaceType;
public final Class<?> objectType;
public final boolean primitive;
public Encoder instance;
public RegisteredEncoder(Class<? extends Encoder> encoder, Class<? extends Encoder> interfaceType, Class<?> objectType)
{
this(encoder, interfaceType, objectType, false);
}
public RegisteredEncoder(Class<? extends Encoder> encoder, Class<? extends Encoder> interfaceType, Class<?> objectType, boolean primitive)
{
this.encoder = encoder;
this.interfaceType = interfaceType;
this.objectType = objectType;
this.primitive = primitive;
}
public boolean implementsInterface(Class<? extends Encoder> type)
{
return interfaceType.isAssignableFrom(type);
}
public boolean isType(Class<?> type)
{
return objectType.isAssignableFrom(type);
}
public void destroyInstance()
{
if (instance != null)
{
try
{
instance.destroy();
}
catch (Throwable t)
{
LOG.warn("Error destroying Decoder", t);
}
instance = null;
}
}
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append(AvailableEncoders.RegisteredEncoder.class.getSimpleName());
str.append('[').append(encoder.getName());
str.append(',').append(interfaceType.getName());
str.append(',').append(objectType.getName());
if (primitive)
{
str.append(",PRIMITIVE");
}
str.append(']');
return str.toString();
}
}
private final EndpointConfig config;
private LinkedList<RegisteredEncoder> registeredEncoders;
private final WebSocketComponents components;
private final LinkedList<RegisteredEncoder> registeredEncoders;
public AvailableEncoders(EndpointConfig config)
public AvailableEncoders(EndpointConfig config, WebSocketComponents components)
{
Objects.requireNonNull(config);
this.config = config;
registeredEncoders = new LinkedList<>();
this.config = Objects.requireNonNull(config);
this.components = Objects.requireNonNull(components);
this.registeredEncoders = new LinkedList<>();
// TEXT based [via Class reference]
registerPrimitive(BooleanEncoder.class, Encoder.Text.class, Boolean.class);
@ -289,7 +226,7 @@ public class AvailableEncoders implements Predicate<Class<?>>, Closeable
return registeredEncoder.instance;
}
registeredEncoder.instance = registeredEncoder.encoder.getConstructor().newInstance();
registeredEncoder.instance = components.getObjectFactory().createInstance(registeredEncoder.encoder);
registeredEncoder.instance.init(this.config);
return registeredEncoder.instance;
}

View File

@ -0,0 +1,85 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.jakarta.common.encoders;
import jakarta.websocket.Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisteredEncoder
{
private static final Logger LOG = LoggerFactory.getLogger(RegisteredEncoder.class);
public final Class<? extends Encoder> encoder;
public final Class<? extends Encoder> interfaceType;
public final Class<?> objectType;
public final boolean primitive;
public Encoder instance;
public RegisteredEncoder(Class<? extends Encoder> encoder, Class<? extends Encoder> interfaceType, Class<?> objectType)
{
this(encoder, interfaceType, objectType, false);
}
public RegisteredEncoder(Class<? extends Encoder> encoder, Class<? extends Encoder> interfaceType, Class<?> objectType, boolean primitive)
{
this.encoder = encoder;
this.interfaceType = interfaceType;
this.objectType = objectType;
this.primitive = primitive;
}
public boolean implementsInterface(Class<? extends Encoder> type)
{
return interfaceType.isAssignableFrom(type);
}
public boolean isType(Class<?> type)
{
return objectType.isAssignableFrom(type);
}
public void destroyInstance()
{
if (instance != null)
{
try
{
instance.destroy();
}
catch (Throwable t)
{
LOG.warn("Error destroying Decoder", t);
}
instance = null;
}
}
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append(RegisteredEncoder.class.getSimpleName());
str.append('[').append(encoder.getName());
str.append(',').append(interfaceType.getName());
str.append(',').append(objectType.getName());
if (primitive)
{
str.append(",PRIMITIVE");
}
str.append(']');
return str.toString();
}
}

View File

@ -19,6 +19,7 @@ import java.util.Map;
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.encoders.AvailableEncoders;
import org.junit.jupiter.api.AfterAll;
@ -45,13 +46,22 @@ public abstract class AbstractJakartaWebSocketFrameHandlerTest
protected AvailableDecoders decoders;
protected Map<String, String> uriParams;
protected EndpointConfig endpointConfig;
protected CoreSession coreSession = new CoreSession.Empty();
protected CoreSession coreSession = new CoreSession.Empty()
{
private final WebSocketComponents components = new WebSocketComponents();
@Override
public WebSocketComponents getWebSocketComponents()
{
return components;
}
};
public AbstractJakartaWebSocketFrameHandlerTest()
{
endpointConfig = ClientEndpointConfig.Builder.create().build();
encoders = new AvailableEncoders(endpointConfig);
decoders = new AvailableDecoders(endpointConfig);
encoders = new AvailableEncoders(endpointConfig, coreSession.getWebSocketComponents());
decoders = new AvailableDecoders(endpointConfig, coreSession.getWebSocketComponents());
uriParams = new HashMap<>();
}

View File

@ -44,7 +44,7 @@ public class DummyFrameHandlerFactory extends JakartaWebSocketFrameHandlerFactor
return null;
}
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig, components);
return discoverJakartaFrameHandlerMetadata(endpointClass, metadata);
}
}

View File

@ -20,12 +20,15 @@ import java.util.function.Consumer;
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.Decoder;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.jakarta.common.AbstractSessionTest;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
public abstract class AbstractMessageSinkTest extends AbstractSessionTest
{
private final WebSocketComponents _components = new WebSocketComponents();
public List<RegisteredDecoder> toRegisteredDecoderList(Class<? extends Decoder> clazz, Class<?> objectType)
{
Class<? extends Decoder> interfaceType;
@ -40,7 +43,7 @@ public abstract class AbstractMessageSinkTest extends AbstractSessionTest
else
throw new IllegalStateException();
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build()));
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build(), _components));
}
public <T> MethodHandle getAcceptHandle(Consumer<T> copy, Class<T> type)

View File

@ -70,9 +70,7 @@ public class AnnotatedServerEndpointConfig extends ServerEndpointConfigWrapper
else
path = anno.value();
// Make sure all Configurators obtained are decorated.
ServerEndpointConfig.Configurator rawConfigurator = getConfigurator(baseServerConfig, anno);
ServerEndpointConfig.Configurator configurator = containerScope.getObjectFactory().decorate(rawConfigurator);
ServerEndpointConfig.Configurator configurator = getConfigurator(baseServerConfig, anno, containerScope);
// Build a ServerEndpointConfig with the Jakarta API builder to wrap.
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(endpointClass, path)
@ -90,7 +88,7 @@ public class AnnotatedServerEndpointConfig extends ServerEndpointConfigWrapper
init(endpointConfig);
}
private static Configurator getConfigurator(ServerEndpointConfig baseServerConfig, ServerEndpoint anno) throws DeploymentException
private static Configurator getConfigurator(ServerEndpointConfig baseServerConfig, ServerEndpoint anno, JakartaWebSocketContainer containerScope) throws DeploymentException
{
Configurator ret = null;
@ -115,7 +113,7 @@ public class AnnotatedServerEndpointConfig extends ServerEndpointConfigWrapper
// Instantiate the provided configurator
try
{
return anno.configurator().getConstructor().newInstance();
return containerScope.getObjectFactory().createInstance(anno.configurator());
}
catch (Exception e)
{

View File

@ -160,8 +160,6 @@ public class JakartaWebSocketCreator implements WebSocketCreator
// [JSR] Step 6: create endpoint class
Class<?> endpointClass = config.getEndpointClass();
Object endpoint = config.getConfigurator().getEndpointInstance(endpointClass);
// Do not decorate here (let the Connection and Session start first)
// This will allow CDI to see Session for injection into Endpoint classes.
return new ConfiguredEndpoint(endpoint, config);
}
catch (InstantiationException e)

View File

@ -233,6 +233,9 @@ public class JakartaWebSocketServerContainer extends JakartaWebSocketClientConta
if (isStarted() || isStarting())
{
// Decorate the provided Configurator.
components.getObjectFactory().decorate(providedConfig.getConfigurator());
// If we have annotations merge the annotated ServerEndpointConfig with the provided one.
Class<?> endpointClass = providedConfig.getEndpointClass();
ServerEndpoint anno = endpointClass.getAnnotation(ServerEndpoint.class);

View File

@ -42,7 +42,7 @@ public class JakartaWebSocketServerFrameHandlerFactory extends JakartaWebSocketC
return super.getMetadata(endpointClass, endpointConfig);
UriTemplatePathSpec templatePathSpec = new UriTemplatePathSpec(anno.value());
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig, components);
metadata.setUriTemplatePathSpec(templatePathSpec);
return discoverJakartaFrameHandlerMetadata(endpointClass, metadata);
}

View File

@ -26,6 +26,7 @@ import jakarta.websocket.DecodeException;
import jakarta.websocket.Decoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.toolchain.test.Hex;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.decoders.IntegerDecoder;
@ -42,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class AvailableDecodersTest
{
private AvailableDecoders availableDecoders;
private WebSocketComponents components = new WebSocketComponents();
@SafeVarargs
public final void init(Class<? extends Decoder>... decoder)
@ -49,7 +51,7 @@ public class AvailableDecodersTest
EndpointConfig testConfig = ClientEndpointConfig.Builder.create()
.decoders(Arrays.asList(decoder))
.build();
this.availableDecoders = new AvailableDecoders(testConfig);
this.availableDecoders = new AvailableDecoders(testConfig, components);
}
public <T extends Decoder> T getInstanceFor(Class<?> type)

View File

@ -25,6 +25,7 @@ import jakarta.websocket.EncodeException;
import jakarta.websocket.Encoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.toolchain.test.Hex;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.jakarta.client.internal.BasicClientEndpointConfig;
import org.eclipse.jetty.websocket.jakarta.common.encoders.AvailableEncoders;
@ -41,6 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class AvailableEncodersTest
{
private static EndpointConfig testConfig;
private final WebSocketComponents components = new WebSocketComponents();
@BeforeAll
public static void initConfig()
@ -48,7 +50,7 @@ public class AvailableEncodersTest
testConfig = new BasicClientEndpointConfig();
}
private AvailableEncoders encoders = new AvailableEncoders(testConfig);
private final AvailableEncoders encoders = new AvailableEncoders(testConfig, components);
public <T> void assertTextEncoder(Class<T> type, T value, String expectedEncoded) throws IllegalAccessException, InstantiationException, EncodeException
{

View File

@ -28,6 +28,7 @@ import jakarta.websocket.Decoder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextStreamMessageSink;
import org.eclipse.jetty.websocket.jakarta.tests.FunctionMethod;
@ -43,6 +44,8 @@ import static org.hamcrest.Matchers.notNullValue;
*/
public class DecoderTextStreamTest extends AbstractClientSessionTest
{
private final WebSocketComponents _components = new WebSocketComponents();
@Test
public void testQuotesDecoderDirect() throws Exception
{
@ -119,6 +122,6 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
else
throw new IllegalStateException();
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build()));
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build(), _components));
}
}

View File

@ -19,6 +19,7 @@ import java.util.Map;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.jakarta.client.internal.BasicClientEndpointConfig;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.encoders.AvailableEncoders;
@ -54,12 +55,13 @@ public abstract class AbstractJakartaWebSocketServerFrameHandlerTest
protected AvailableDecoders decoders;
protected Map<String, String> uriParams;
protected EndpointConfig endpointConfig;
private WebSocketComponents components = new WebSocketComponents();
public AbstractJakartaWebSocketServerFrameHandlerTest()
{
endpointConfig = new BasicClientEndpointConfig();
encoders = new AvailableEncoders(endpointConfig);
decoders = new AvailableDecoders(endpointConfig);
encoders = new AvailableEncoders(endpointConfig, components);
decoders = new AvailableDecoders(endpointConfig, components);
uriParams = new HashMap<>();
}
}

View File

@ -89,7 +89,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli
if (httpClient == null)
coreClient.getHttpClient().setName("Jetty-WebSocketClient@" + hashCode());
frameHandlerFactory = new JettyWebSocketFrameHandlerFactory(this);
frameHandlerFactory = new JettyWebSocketFrameHandlerFactory(this, components);
sessionListeners.add(sessionTracker);
addBean(sessionTracker);
}

View File

@ -46,6 +46,7 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException;
import org.eclipse.jetty.websocket.core.internal.messages.ByteArrayMessageSink;
@ -78,11 +79,18 @@ import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
{
private final WebSocketContainer container;
private Map<Class<?>, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>();
private final WebSocketComponents components;
private final Map<Class<?>, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>();
public JettyWebSocketFrameHandlerFactory(WebSocketContainer container)
public JettyWebSocketFrameHandlerFactory(WebSocketContainer container, WebSocketComponents components)
{
this.container = container;
this.components = components;
}
public WebSocketComponents getWebSocketComponents()
{
return components;
}
public JettyWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass)
@ -130,6 +138,9 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
final MethodHandle pongHandle = InvokerUtils.bindTo(metadata.getPongHandle(), endpointInstance);
BatchMode batchMode = metadata.getBatchMode();
// Decorate the endpointInstance while we are still upgrading for access to things like HttpSession.
components.getObjectFactory().decorate(endpointInstance);
return new JettyWebSocketFrameHandler(
container,
endpointInstance,

View File

@ -32,6 +32,7 @@ import org.eclipse.jetty.websocket.core.CloseStatus;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -39,6 +40,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class JettyWebSocketFrameHandlerTest
{
@ -57,20 +59,26 @@ public class JettyWebSocketFrameHandlerTest
container.stop();
}
private JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container);
private CoreSession coreSession = new CoreSession.Empty()
private final WebSocketComponents components = new WebSocketComponents();
private final JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container, components);
private final CoreSession coreSession = new CoreSession.Empty()
{
@Override
public Behavior getBehavior()
{
return Behavior.CLIENT;
}
@Override
public WebSocketComponents getWebSocketComponents()
{
return components;
}
};
private JettyWebSocketFrameHandler newLocalFrameHandler(Object wsEndpoint)
{
JettyWebSocketFrameHandler localEndpoint = endpointFactory.newJettyFrameHandler(wsEndpoint);
return localEndpoint;
return endpointFactory.newJettyFrameHandler(wsEndpoint);
}
public static class ConnectionOnly implements WebSocketConnectionListener
@ -97,7 +105,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testConnectionListener() throws Exception
public void testConnectionListener()
{
ConnectionOnly socket = new ConnectionOnly();
JettyWebSocketFrameHandler localEndpoint = newLocalFrameHandler(socket);
@ -138,7 +146,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testAnnotatedStreamedTextSingle() throws Exception
public void testAnnotatedStreamedTextSingle()
{
assertTimeout(Duration.ofMillis(1000), () ->
{
@ -160,7 +168,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testAnnotatedStreamedTextMultipleParts() throws Exception
public void testAnnotatedStreamedTextMultipleParts()
{
assertTimeout(Duration.ofMillis(1000), () ->
{
@ -177,7 +185,7 @@ public class JettyWebSocketFrameHandlerTest
localEndpoint.onFrame(CloseStatus.toFrame(StatusCode.NORMAL, "Normal"), Callback.NOOP);
// Await completion (of threads)
socket.streamLatch.await(2, TimeUnit.SECONDS);
assertTrue(socket.streamLatch.await(2, TimeUnit.SECONDS));
// Validate Events
socket.events.assertEvents("onTextStream\\(Hello World\\)");
@ -185,7 +193,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testListenerPartialSocket() throws Exception
public void testListenerPartialSocket()
{
// Setup
EndPoints.ListenerPartialSocket socket = new EndPoints.ListenerPartialSocket();
@ -216,7 +224,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testListenerBasicSocket() throws Exception
public void testListenerBasicSocket()
{
// Setup
EndPoints.ListenerBasicSocket socket = new EndPoints.ListenerBasicSocket();
@ -242,7 +250,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testListenerBasicSocketError() throws Exception
public void testListenerBasicSocketError()
{
// Setup
EndPoints.ListenerBasicSocket socket = new EndPoints.ListenerBasicSocket();
@ -262,7 +270,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testListenerFrameSocket() throws Exception
public void testListenerFrameSocket()
{
// Setup
EndPoints.ListenerFrameSocket socket = new EndPoints.ListenerFrameSocket();
@ -292,7 +300,7 @@ public class JettyWebSocketFrameHandlerTest
}
@Test
public void testListenerPingPongSocket() throws Exception
public void testListenerPingPongSocket()
{
// Setup
EndPoints.ListenerPingPongSocket socket = new EndPoints.ListenerPingPongSocket();

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.websocket.common;
import org.eclipse.jetty.websocket.api.exceptions.InvalidWebSocketException;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.DuplicateAnnotationException;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.internal.messages.ByteArrayMessageSink;
@ -51,7 +52,8 @@ public class LocalEndpointMetadataTest
container.stop();
}
private JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container);
private final WebSocketComponents components = new WebSocketComponents();
private final JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container, components);
private JettyWebSocketFrameHandlerMetadata createMetadata(Class<?> endpointClass)
{

View File

@ -110,7 +110,7 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
this.webSocketMappings = webSocketMappings;
this.components = components;
this.executor = executor;
this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this);
this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this, components);
addBean(frameHandlerFactory);
addSessionListener(sessionTracker);

View File

@ -14,10 +14,10 @@
package org.eclipse.jetty.websocket.server.internal;
import jakarta.servlet.ServletContext;
import org.eclipse.jetty.websocket.api.WebSocketContainer;
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler;
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
@ -31,9 +31,9 @@ public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFa
return (container == null) ? null : container.getBean(JettyServerFrameHandlerFactory.class);
}
public JettyServerFrameHandlerFactory(WebSocketContainer container)
public JettyServerFrameHandlerFactory(JettyWebSocketServerContainer container, WebSocketComponents components)
{
super(container);
super(container, components);
}
@Override

View File

@ -54,6 +54,24 @@
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jakarta-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jetty-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jetty-client</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>

View File

@ -0,0 +1,404 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.cdi.tests.websocket;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import jakarta.inject.Inject;
import jakarta.websocket.ClientEndpoint;
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.CloseReason;
import jakarta.websocket.Decoder;
import jakarta.websocket.Encoder;
import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.MessageHandler;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.WebSocketContainer;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpoint;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.cdi.CdiDecoratingListener;
import org.eclipse.jetty.cdi.CdiServletContainerInitializer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
import org.eclipse.jetty.websocket.jakarta.client.JakartaWebSocketClientContainerProvider;
import org.eclipse.jetty.websocket.jakarta.client.internal.JakartaWebSocketClientContainer;
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer.Configurator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class JavaxWebSocketCdiTest
{
private Server _server;
private WebSocketContainer _client;
private ServerConnector _connector;
private ServletContextHandler context;
@BeforeEach
public void before()
{
_server = new Server();
_connector = new ServerConnector(_server);
_server.addConnector(_connector);
context = new ServletContextHandler();
context.setContextPath("/");
// Enable Weld + CDI
context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE);
context.addServletContainerInitializer(new CdiServletContainerInitializer());
context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener());
// Add to Server
_server.setHandler(context);
}
public void start(Configurator configurator) throws Exception
{
// Add WebSocket endpoints
JakartaWebSocketServletContainerInitializer.configure(context, configurator);
// Start Server
_server.start();
// Configure the Client with the same DecoratedObjectFactory from the server.
WebSocketComponents components = WebSocketServerComponents.getWebSocketComponents(context.getServletContext());
_client = new JakartaWebSocketClientContainer(components);
LifeCycle.start(_client);
}
@AfterEach
public void after() throws Exception
{
JakartaWebSocketClientContainerProvider.stop(_client);
_server.stop();
}
@Test
public void testAnnotatedEndpoint() throws Exception
{
start((servletContext, wsContainer) -> wsContainer.addEndpoint(AnnotatedCdiEchoSocket.class));
// If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE.
AnnotatedCdiClientSocket clientEndpoint = new AnnotatedCdiClientSocket();
URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo");
Session session = _client.connectToServer(clientEndpoint, uri);
session.getBasicRemote().sendText("hello world");
assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("hello world"));
session.close();
assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testConfiguredEndpoint() throws Exception
{
ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(ConfiguredCdiEchoSocket.class, "/echo")
.configurator(new ServerConfigurator())
.build();
start((servletContext, wsContainer) -> wsContainer.addEndpoint(serverEndpointConfig));
// If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE.
ConfiguredCdiClientSocket clientEndpoint = new ConfiguredCdiClientSocket();
ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create()
.configurator(new ClientConfigurator())
.build();
URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo");
Session session = _client.connectToServer(clientEndpoint, clientEndpointConfig, uri);
session.getBasicRemote().sendText("hello world");
assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("hello world"));
session.close();
assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testEncoderDecoder() throws Exception
{
start((servletContext, wsContainer) -> wsContainer.addEndpoint(AnnotatedCdiEchoSocket.class));
// If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE.
AnnotatedEncoderDecoderClientSocket clientEndpoint = new AnnotatedEncoderDecoderClientSocket();
URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo");
Session session = _client.connectToServer(clientEndpoint, uri);
session.getBasicRemote().sendObject("hello world");
assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("decoded(encoded(hello world))"));
session.close();
assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS));
}
@Test
@Disabled("See issue https://github.com/eclipse/jetty.project/issues/6174")
public void testHttpSessionInjection() throws Exception
{
start((servletContext, wsContainer) -> wsContainer.addEndpoint(CdiHttpSessionSocket.class));
// If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE.
AnnotatedCdiClientSocket clientEndpoint = new AnnotatedCdiClientSocket();
URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo");
Session session = _client.connectToServer(clientEndpoint, uri);
session.getBasicRemote().sendObject("hello world");
String rcvMessage = clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS);
assertThat(rcvMessage, containsString("hello world, SessionID:"));
session.close();
assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS));
}
public static class ClientConfigurator extends ClientEndpointConfig.Configurator
{
@Inject
public Logger logger;
@Override
public void beforeRequest(Map<String, List<String>> headers)
{
logger.info("beforeRequest");
}
}
public static class ServerConfigurator extends ServerEndpointConfig.Configurator
{
@Inject
public Logger logger;
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
{
logger.info("modifyHandshake");
}
}
@ClientEndpoint(configurator = ClientConfigurator.class)
public static class AnnotatedCdiClientSocket
{
@Inject
public Logger logger;
BlockingArrayQueue<String> _textMessages = new BlockingArrayQueue<>();
CountDownLatch _closeLatch = new CountDownLatch(1);
@OnOpen
public void onOpen(Session session)
{
logger.info("onOpen: " + session);
}
@OnMessage
public void onMessage(String message)
{
_textMessages.add(message);
}
@OnError
public void onError(Throwable t)
{
t.printStackTrace();
}
@OnClose
public void onClose()
{
_closeLatch.countDown();
}
}
@ServerEndpoint(value = "/echo", configurator = ServerConfigurator.class)
public static class AnnotatedCdiEchoSocket
{
@Inject
protected Logger logger;
protected Session session;
@OnOpen
public void onOpen(Session session)
{
logger.info("onOpen() session:" + session);
this.session = session;
}
@OnMessage
public void onMessage(String message) throws IOException
{
this.session.getBasicRemote().sendText(message);
}
@OnError
public void onError(Throwable t)
{
t.printStackTrace();
}
}
public static class ConfiguredCdiClientSocket extends Endpoint implements MessageHandler.Whole<String>
{
@Inject
public Logger logger;
BlockingArrayQueue<String> _textMessages = new BlockingArrayQueue<>();
CountDownLatch _closeLatch = new CountDownLatch(1);
@Override
public void onMessage(String message)
{
_textMessages.add(message);
}
@Override
public void onOpen(Session session, EndpointConfig config)
{
logger.info("onOpen: " + session);
session.addMessageHandler(this);
}
@Override
public void onError(Session session, Throwable thr)
{
thr.printStackTrace();
}
@Override
public void onClose(Session session, CloseReason closeReason)
{
_closeLatch.countDown();
}
}
public static class ConfiguredCdiEchoSocket extends Endpoint implements MessageHandler.Whole<String>
{
@Inject
public Logger logger;
private Session session;
@Override
public void onMessage(String message)
{
try
{
session.getBasicRemote().sendText(message);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
@Override
public void onOpen(Session session, EndpointConfig config)
{
logger.info("onOpen() session:" + session);
this.session = session;
session.addMessageHandler(this);
}
@Override
public void onError(Session session, Throwable thr)
{
thr.printStackTrace();
}
}
public static class CustomEncoder implements Encoder.Text<String>
{
@Inject
public Logger logger;
@Override
public String encode(String s)
{
return "encoded(" + s + ")";
}
@Override
public void init(EndpointConfig config)
{
logger.info("init: " + config);
}
@Override
public void destroy()
{
}
}
public static class CustomDecoder implements Decoder.Text<String>
{
@Inject
public Logger logger;
@Override
public String decode(String s)
{
return "decoded(" + s + ")";
}
@Override
public void init(EndpointConfig config)
{
logger.info("init: " + config);
}
@Override
public void destroy()
{
}
@Override
public boolean willDecode(String s)
{
return true;
}
}
@ClientEndpoint(encoders = {CustomEncoder.class}, decoders = {CustomDecoder.class})
public static class AnnotatedEncoderDecoderClientSocket extends AnnotatedCdiClientSocket
{
}
@ServerEndpoint("/echo")
public static class CdiHttpSessionSocket extends AnnotatedCdiEchoSocket
{
@Inject
private jakarta.servlet.http.HttpSession httpSession;
@Override
public void onMessage(String message) throws IOException
{
session.getBasicRemote().sendText(message + ", SessionID:" + httpSession.getId());
}
}
}

View File

@ -0,0 +1,153 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.cdi.tests.websocket;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import jakarta.inject.Inject;
import org.eclipse.jetty.cdi.CdiDecoratingListener;
import org.eclipse.jetty.cdi.CdiServletContainerInitializer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class JettyWebSocketCdiTest
{
private Server _server;
private WebSocketClient _client;
private ServerConnector _connector;
@BeforeEach
public void before() throws Exception
{
_server = new Server();
_connector = new ServerConnector(_server);
_server.addConnector(_connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
// Enable Weld + CDI
context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE);
context.addServletContainerInitializer(new CdiServletContainerInitializer());
context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener());
// Add WebSocket endpoints
JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) ->
wsContainer.addMapping("/echo", CdiEchoSocket.class));
// Add to Server
_server.setHandler(context);
// Start Server
_server.start();
_client = new WebSocketClient();
_client.start();
}
@AfterEach
public void after() throws Exception
{
_client.stop();
_server.stop();
}
@WebSocket
public static class TestClientEndpoint
{
BlockingArrayQueue<String> _textMessages = new BlockingArrayQueue<>();
CountDownLatch _closeLatch = new CountDownLatch(1);
@OnWebSocketMessage
public void onMessage(String message)
{
_textMessages.add(message);
}
@OnWebSocketClose
public void onClose()
{
_closeLatch.countDown();
}
}
@Test
public void testBasicEcho() throws Exception
{
// If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE.
TestClientEndpoint clientEndpoint = new TestClientEndpoint();
URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo");
Session session = _client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS);
session.getRemote().sendString("hello world");
assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("hello world"));
session.close();
assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS));
}
@WebSocket()
public static class CdiEchoSocket
{
@Inject
public Logger logger;
private Session session;
@OnWebSocketConnect
public void onOpen(Session session)
{
logger.info("onOpen() session:" + session);
this.session = session;
}
@OnWebSocketMessage
public void onMessage(String message) throws IOException
{
this.session.getRemote().sendString(message);
}
@OnWebSocketError
public void onError(Throwable t)
{
t.printStackTrace();
}
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
logger.info("onClose() close: " + statusCode + ":" + reason);
this.session = null;
}
}
}

View File

@ -0,0 +1,30 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.cdi.tests.websocket;
import java.util.logging.Logger;
import jakarta.enterprise.inject.Default;
import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.spi.InjectionPoint;
public class LogFactory
{
@Produces
@Default
public Logger createLogger(InjectionPoint injectionPoint)
{
return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}
}