Changed `JettyWebSocketFrameHandlerFactory` to use an application MethodHandle.Lookup (rather than a server one) when creating listener metadata. This fixes the JPMS problem so that now JPMS applications do not need any additional configuration to invoke endpoints. The (acceptable) downside is that anonymous inner classes (that are not public) cannot be used as listener endpoints. This change affects core and EE10 WebSocket; EE9 and EE8 WebSocket have not been fixed (so they allow anonymous inner classes but are broken in JPMS). Renamed "connectLatch" fields to "openLatch" in test classes. Signed-off-by: Simone Bordet <simone.bordet@gmail.com> Signed-off-by: Olivier Lamy <olamy@apache.org> Co-authored-by: Olivier Lamy <olamy@apache.org>
This commit is contained in:
parent
ef75595e8e
commit
b5d0fd6f2f
|
@ -59,6 +59,9 @@ xref:pg-websocket-endpoints-listener[Listener endpoints] are notified of events
|
|||
|
||||
xref:pg-websocket-endpoints-annotated[Annotated endpoints] are notified of events by invoking the correspondent method annotated with the correspondent annotation from the `+org.eclipse.jetty.websocket.api.annotations.*+` package.
|
||||
|
||||
Jetty uses ``MethodHandle``s to instantiate WebSocket endpoints and invoke WebSocket event methods, so WebSocket endpoint classes and WebSocket event methods must be `public`.
|
||||
This guarantees that WebSocket endpoints can be accessed by the Jetty implementation without additional configuration also when your application uses the Java Module System (JPMS).
|
||||
|
||||
For both types of WebSocket endpoints, only one thread at a time will be delivering frame or message events to the corresponding methods; the next frame or message event will not be delivered until the previous call to the corresponding method has exited, and if there is xref:pg-websocket-endpoints-demand[demand] for it.
|
||||
Endpoints will always be notified of message events in the same order they were received over the network.
|
||||
|
||||
|
@ -152,29 +155,6 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=s
|
|||
|
||||
A WebSocket endpoint may annotate methods with `+org.eclipse.jetty.websocket.api.annotations.*+` annotations to receive WebSocket events.
|
||||
|
||||
Jetty uses ``MethodHandle``s to instantiate WebSocket endpoints and invoke WebSocket event methods, so WebSocket endpoint classes and WebSocket event methods must be `public`.
|
||||
|
||||
When using JPMS, you must ensure that the Jetty JPMS module `org.eclipse.jetty.websocket.common` can _read_ (in JPMS terms) the WebSocket endpoint classes in your JPMS module.
|
||||
|
||||
For example, your application may use the Jetty WebSocket client so that the JPMS module that contains your WebSocket endpoint classes looks like this:
|
||||
|
||||
[source,java]
|
||||
.module-info.java
|
||||
----
|
||||
module com.acme.websocket
|
||||
{
|
||||
// The Jetty WebSocket client dependency.
|
||||
requires org.eclipse.jetty.websocket.client;
|
||||
}
|
||||
----
|
||||
|
||||
To ensure that Jetty _reads_ your JPMS module, you must start the JVM with the following option:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ java --add-reads org.eclipse.jetty.websocket.common=com.acme.websocket ...
|
||||
----
|
||||
|
||||
Each annotated event method may take an optional `Session` argument as its first parameter:
|
||||
|
||||
[source,java,indent=0]
|
||||
|
|
|
@ -162,7 +162,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
|
|||
JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata();
|
||||
metadata.setAutoDemand(Session.Listener.AutoDemanding.class.isAssignableFrom(endpointClass));
|
||||
|
||||
MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup();
|
||||
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
|
||||
|
||||
Method openMethod = findMethod(endpointClass, "onWebSocketOpen", Session.class);
|
||||
if (openMethod != null)
|
||||
|
@ -244,19 +244,14 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
|
|||
Method method = ReflectUtils.findMethod(klass, name, parameters);
|
||||
if (method == null)
|
||||
return null;
|
||||
if (!isOverridden(method))
|
||||
return null;
|
||||
// The method is overridden, but it may be declared in a non-public
|
||||
// class, for example an anonymous class, where it won't be accessible,
|
||||
// therefore replace it with the accessible version from Session.Listener.
|
||||
if (!Modifier.isPublic(klass.getModifiers()))
|
||||
method = ReflectUtils.findMethod(Session.Listener.class, name, parameters);
|
||||
return method;
|
||||
if (isOverridden(method))
|
||||
return method;
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isOverridden(Method method)
|
||||
{
|
||||
return method != null && method.getDeclaringClass() != Session.Listener.class;
|
||||
return method.getDeclaringClass() != Session.Listener.class;
|
||||
}
|
||||
|
||||
private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket anno, Class<?> endpointClass)
|
||||
|
@ -265,10 +260,9 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
|
|||
metadata.setAutoDemand(anno.autoDemand());
|
||||
|
||||
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
|
||||
Method onmethod;
|
||||
|
||||
// OnWebSocketOpen [0..1]
|
||||
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketOpen.class);
|
||||
Method onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketOpen.class);
|
||||
if (onmethod != null)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onmethod, OnWebSocketOpen.class);
|
||||
|
|
|
@ -44,7 +44,7 @@ public class CloseTrackingEndpoint extends Session.Listener.AbstractAutoDemandin
|
|||
public String closeReason = null;
|
||||
public CountDownLatch closeLatch = new CountDownLatch(1);
|
||||
public AtomicInteger closeCount = new AtomicInteger(0);
|
||||
public CountDownLatch connectLatch = new CountDownLatch(1);
|
||||
public CountDownLatch openLatch = new CountDownLatch(1);
|
||||
public CountDownLatch errorLatch = new CountDownLatch(1);
|
||||
|
||||
public LinkedBlockingQueue<String> messageQueue = new LinkedBlockingQueue<>();
|
||||
|
@ -94,7 +94,7 @@ public class CloseTrackingEndpoint extends Session.Listener.AbstractAutoDemandin
|
|||
{
|
||||
super.onWebSocketOpen(session);
|
||||
LOG.debug("onWebSocketOpen({})", session);
|
||||
connectLatch.countDown();
|
||||
openLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -89,7 +89,7 @@ public class ClientConnectTest
|
|||
assertThat("Error", capcause, errorMatcher);
|
||||
|
||||
// Validate that websocket didn't see an open event
|
||||
assertThat("Open Latch", wsocket.connectLatch.getCount(), is(1L));
|
||||
assertThat("Open Latch", wsocket.openLatch.getCount(), is(1L));
|
||||
|
||||
// Return the captured cause
|
||||
return (E)capcause;
|
||||
|
|
|
@ -113,7 +113,7 @@ public class ConnectFutureTest
|
|||
assertTrue(connect.cancel(true));
|
||||
assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS));
|
||||
exitCreator.countDown();
|
||||
assertFalse(clientSocket.connectLatch.await(1, TimeUnit.SECONDS));
|
||||
assertFalse(clientSocket.openLatch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
Throwable error = clientSocket.error.get();
|
||||
assertThat(error, instanceOf(UpgradeException.class));
|
||||
|
@ -155,7 +155,7 @@ public class ConnectFutureTest
|
|||
assertTrue(connect.cancel(true));
|
||||
assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS));
|
||||
exitListener.countDown();
|
||||
assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
|
||||
assertThat(clientSocket.error.get(), instanceOf(CancellationException.class));
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ public class ConnectFutureTest
|
|||
assertTrue(connect.cancel(true));
|
||||
assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS));
|
||||
exitListener.countDown();
|
||||
assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
|
||||
assertThat(clientSocket.error.get(), instanceOf(CancellationException.class));
|
||||
}
|
||||
|
@ -205,29 +205,14 @@ public class ConnectFutureTest
|
|||
start(wsHandler ->
|
||||
wsHandler.getServerWebSocketContainer().addMapping("/", (upgradeRequest, upgradeResponse, callback) -> new EchoSocket()));
|
||||
|
||||
CountDownLatch exitOnConnect = new CountDownLatch(1);
|
||||
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint()
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketOpen(Session session)
|
||||
{
|
||||
try
|
||||
{
|
||||
super.onWebSocketOpen(session);
|
||||
exitOnConnect.await();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
CountDownLatch exitOnOpen = new CountDownLatch(1);
|
||||
AwaitOnOpen clientSocket = new AwaitOnOpen(exitOnOpen);
|
||||
|
||||
// Abort during the call to onOpened. This is after the connection upgrade, but before future completion.
|
||||
Future<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
|
||||
assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(connect.cancel(true));
|
||||
exitOnConnect.countDown();
|
||||
exitOnOpen.countDown();
|
||||
|
||||
// We got an error on the WebSocket endpoint and an error from the future.
|
||||
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
|
||||
|
@ -245,7 +230,7 @@ public class ConnectFutureTest
|
|||
Session session = connect.get(5, TimeUnit.SECONDS);
|
||||
|
||||
// If we can send and receive messages the future has been completed.
|
||||
assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
|
||||
Session session1 = clientSocket.getSession();
|
||||
session1.sendText("hello", Callback.NOOP);
|
||||
assertThat(clientSocket.messageQueue.poll(5, TimeUnit.SECONDS), Matchers.is("hello"));
|
||||
|
@ -339,29 +324,14 @@ public class ConnectFutureTest
|
|||
start(wsHandler ->
|
||||
wsHandler.getServerWebSocketContainer().addMapping("/", (upgradeRequest, upgradeResponse, callback) -> new EchoSocket()));
|
||||
|
||||
CountDownLatch exitOnConnect = new CountDownLatch(1);
|
||||
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint()
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketOpen(Session session)
|
||||
{
|
||||
try
|
||||
{
|
||||
super.onWebSocketOpen(session);
|
||||
exitOnConnect.await();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
CountDownLatch exitOnOpen = new CountDownLatch(1);
|
||||
AwaitOnOpen clientSocket = new AwaitOnOpen(exitOnOpen);
|
||||
|
||||
// Complete the CompletableFuture with an exception the during the call to onOpened.
|
||||
CompletableFuture<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
|
||||
assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(connect.completeExceptionally(new WebSocketException("custom exception")));
|
||||
exitOnConnect.countDown();
|
||||
exitOnOpen.countDown();
|
||||
|
||||
// Exception from the future is correct.
|
||||
ExecutionException futureError = assertThrows(ExecutionException.class, () -> connect.get(5, TimeUnit.SECONDS));
|
||||
|
@ -375,4 +345,28 @@ public class ConnectFutureTest
|
|||
assertThat(endpointError, instanceOf(WebSocketException.class));
|
||||
assertThat(endpointError.getMessage(), is("custom exception"));
|
||||
}
|
||||
|
||||
public static class AwaitOnOpen extends CloseTrackingEndpoint
|
||||
{
|
||||
private final CountDownLatch exitOnOpen;
|
||||
|
||||
public AwaitOnOpen(CountDownLatch latch)
|
||||
{
|
||||
exitOnOpen = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketOpen(Session session)
|
||||
{
|
||||
try
|
||||
{
|
||||
super.onWebSocketOpen(session);
|
||||
exitOnOpen.await();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -385,7 +385,7 @@ public class WebSocketClientTest
|
|||
|
||||
try (Session ignored = future.get(5, TimeUnit.SECONDS))
|
||||
{
|
||||
Assertions.assertTrue(cliSock.connectLatch.await(1, TimeUnit.SECONDS));
|
||||
Assertions.assertTrue(cliSock.openLatch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
InetSocketAddress local = (InetSocketAddress)cliSock.getSession().getLocalSocketAddress();
|
||||
InetSocketAddress remote = (InetSocketAddress)cliSock.getSession().getRemoteSocketAddress();
|
||||
|
|
|
@ -16,8 +16,6 @@ package org.eclipse.jetty.websocket.tests.listeners;
|
|||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -25,7 +23,6 @@ import java.util.stream.Stream;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.websocket.api.Callback;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
@ -42,7 +39,9 @@ import org.junit.jupiter.params.provider.Arguments;
|
|||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class WebSocketListenerTest
|
||||
|
@ -130,44 +129,18 @@ public class WebSocketListenerTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAnonymousListener() throws Exception
|
||||
public void testAnonymousListener()
|
||||
{
|
||||
CountDownLatch openLatch = new CountDownLatch(1);
|
||||
CountDownLatch closeLatch = new CountDownLatch(1);
|
||||
BlockingQueue<String> textMessages = new BlockingArrayQueue<>();
|
||||
Session.Listener clientEndpoint = new Session.Listener.AutoDemanding()
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketOpen(Session session)
|
||||
{
|
||||
openLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
textMessages.add(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason)
|
||||
{
|
||||
closeLatch.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
Session session = client.connect(clientEndpoint, serverUri.resolve("/echo")).get(5, TimeUnit.SECONDS);
|
||||
assertTrue(openLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
// Send and receive echo on client.
|
||||
String payload = "hello world";
|
||||
session.sendText(payload, Callback.NOOP);
|
||||
String echoMessage = textMessages.poll(5, TimeUnit.SECONDS);
|
||||
assertThat(echoMessage, is(payload));
|
||||
|
||||
// Close normally.
|
||||
session.close(StatusCode.NORMAL, "standard close", Callback.NOOP);
|
||||
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
|
||||
Exception failure = assertThrows(Exception.class, () -> client.connect(clientEndpoint, serverUri.resolve("/echo")).get(5, TimeUnit.SECONDS));
|
||||
// The endpoint class is not public.
|
||||
assertThat(failure.getCause(), instanceOf(IllegalAccessException.class));
|
||||
}
|
||||
|
||||
private List<Class<?>> getClassListFromArguments(Stream<Arguments> stream)
|
||||
|
|
|
@ -29,7 +29,7 @@ import static org.hamcrest.Matchers.nullValue;
|
|||
public abstract class AbstractCloseEndpoint extends Session.Listener.AbstractAutoDemanding
|
||||
{
|
||||
public final Logger log;
|
||||
public CountDownLatch connectLatch = new CountDownLatch(1);
|
||||
public CountDownLatch openLatch = new CountDownLatch(1);
|
||||
public CountDownLatch closeLatch = new CountDownLatch(1);
|
||||
public String closeReason = null;
|
||||
public int closeStatusCode = -1;
|
||||
|
@ -45,7 +45,7 @@ public abstract class AbstractCloseEndpoint extends Session.Listener.AbstractAut
|
|||
{
|
||||
super.onWebSocketOpen(sess);
|
||||
log.debug("onWebSocketOpen({})", sess);
|
||||
connectLatch.countDown();
|
||||
openLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -203,7 +203,7 @@ public class DirectUpgradeTest
|
|||
assertEquals("HELLO", response.getContentAsString());
|
||||
}
|
||||
|
||||
private static class EchoListener implements Session.Listener
|
||||
public static class EchoListener implements Session.Listener
|
||||
{
|
||||
private Session session;
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@ public class ServerCloseTest
|
|||
|
||||
// Hard close from the server. Server onClosed() will try to close again which should be a NOOP.
|
||||
AbstractCloseEndpoint serverEndpoint = serverEndpointCreator.pollLastCreated();
|
||||
assertTrue(serverEndpoint.connectLatch.await(5, SECONDS));
|
||||
assertTrue(serverEndpoint.openLatch.await(5, SECONDS));
|
||||
Session session = serverEndpoint.getSession();
|
||||
session.close(StatusCode.SHUTDOWN, "SHUTDOWN hard close", Callback.NOOP);
|
||||
|
||||
|
@ -294,7 +294,7 @@ public class ServerCloseTest
|
|||
|
||||
// Hard close from the server. Server onClosed() will try to close again which should be a NOOP.
|
||||
AbstractCloseEndpoint serverEndpoint = serverEndpointCreator.pollLastCreated();
|
||||
assertTrue(serverEndpoint.connectLatch.await(5, SECONDS));
|
||||
assertTrue(serverEndpoint.openLatch.await(5, SECONDS));
|
||||
Session session = serverEndpoint.getSession();
|
||||
session.close(StatusCode.SHUTDOWN, "SHUTDOWN hard close", Callback.NOOP);
|
||||
|
||||
|
|
|
@ -73,7 +73,8 @@ class MavenHelper
|
|||
|
||||
private static RemoteRepository newCentralRepository()
|
||||
{
|
||||
return new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build();
|
||||
String centralRepository = System.getProperty("maven.repo.uri", "https://repo.maven.apache.org/maven2/");
|
||||
return new RemoteRepository.Builder("central", "default", centralRepository).build();
|
||||
}
|
||||
|
||||
private static class LogTransferListener extends AbstractTransferListener
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<module>jetty-test-session-common</module>
|
||||
<module>test-distribution</module>
|
||||
<module>test-integration</module>
|
||||
<!--<module>test-jpms</module>-->
|
||||
<module>test-jpms</module>
|
||||
</modules>
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
|
|
|
@ -4,31 +4,44 @@
|
|||
<parent>
|
||||
<groupId>org.eclipse.jetty.tests</groupId>
|
||||
<artifactId>tests</artifactId>
|
||||
<version>12.0.5-SNAPSHOT</version>
|
||||
<version>12.0.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>test-jpms</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Tests :: JPMS</name>
|
||||
<modules>
|
||||
<module>test-jpms-websocket-core</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>org.eclipse.jetty.tests</groupId>
|
||||
<artifactId>jetty-testers</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>jetty-websocket-jetty-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>jetty-websocket-jetty-server</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<mavenRepoPath>${session.repositorySession.localRepository.basedir.absolutePath}</mavenRepoPath>
|
||||
<jettyVersion>${project.version}</jettyVersion>
|
||||
<slf4jVersion>${slf4j.version}</slf4jVersion>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 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.tests.jpms;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.tests.testers.JPMSTester;
|
||||
import org.eclipse.jetty.tests.testers.Tester;
|
||||
import org.eclipse.jetty.toolchain.test.MavenPaths;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||
import org.eclipse.jetty.websocket.api.Callback;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ExtendWith(WorkDirExtension.class)
|
||||
public class JPMSWebSocketTest
|
||||
{
|
||||
@Test
|
||||
public void testJPMSWebSocket(WorkDir workDir) throws Exception
|
||||
{
|
||||
String jettyVersion = System.getProperty("jettyVersion");
|
||||
String slf4jVersion = System.getProperty("slf4jVersion");
|
||||
|
||||
int port = Tester.freePort();
|
||||
try (JPMSTester server = new JPMSTester.Builder(workDir.getPath())
|
||||
// .jvmArgs("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005")
|
||||
.classesDirectory(MavenPaths.targetDir().resolve("test-classes"))
|
||||
.moduleInfo("""
|
||||
module app.server
|
||||
{
|
||||
requires org.eclipse.jetty.websocket.server;
|
||||
exports org.eclipse.jetty.tests.jpms;
|
||||
}
|
||||
""")
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-jetty-server:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-jetty-api:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-server:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-http:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-io:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-util:" + jettyVersion)
|
||||
.addToModulePath("org.slf4j:slf4j-api:" + slf4jVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-slf4j-impl:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-jetty-common:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-core-server:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-core-common:" + jettyVersion)
|
||||
.mainClass(ServerMain.class)
|
||||
.args(String.valueOf(port))
|
||||
.build())
|
||||
{
|
||||
assertTrue(server.awaitConsoleLogsFor("Started oejs.Server@", Duration.ofSeconds(10)));
|
||||
|
||||
try (JPMSTester client = new JPMSTester.Builder(workDir.getPath())
|
||||
.classesDirectory(MavenPaths.targetDir().resolve("test-classes"))
|
||||
.moduleInfo("""
|
||||
module app.client
|
||||
{
|
||||
requires org.eclipse.jetty.websocket.client;
|
||||
exports org.eclipse.jetty.tests.jpms;
|
||||
}
|
||||
""")
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-jetty-client:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-jetty-api:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-client:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-alpn-client:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-http:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-io:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-util:" + jettyVersion)
|
||||
.addToModulePath("org.slf4j:slf4j-api:" + slf4jVersion)
|
||||
.addToModulePath("org.eclipse.jetty:jetty-slf4j-impl:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-jetty-common:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-core-client:" + jettyVersion)
|
||||
.addToModulePath("org.eclipse.jetty.websocket:jetty-websocket-core-common:" + jettyVersion)
|
||||
.mainClass(ClientMain.class)
|
||||
.args(String.valueOf(port))
|
||||
.build())
|
||||
{
|
||||
assertTrue(client.awaitConsoleLogsFor("SUCCESS", Duration.ofSeconds(10)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerMain
|
||||
{
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setPort(Integer.parseInt(args[0]));
|
||||
server.addConnector(connector);
|
||||
WebSocketUpgradeHandler wsHandler = WebSocketUpgradeHandler.from(server, container ->
|
||||
container.addMapping("/echo", (request, response, callback) -> new AnnotatedServerEndPoint())
|
||||
);
|
||||
server.setHandler(wsHandler);
|
||||
server.start();
|
||||
}
|
||||
}
|
||||
|
||||
@WebSocket
|
||||
public static class AnnotatedServerEndPoint
|
||||
{
|
||||
@OnWebSocketMessage
|
||||
public void onText(Session session, String text)
|
||||
{
|
||||
session.sendText(text, Callback.NOOP);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientMain
|
||||
{
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
|
||||
int port = Integer.parseInt(args[0]);
|
||||
ListenerClientEndPoint endPoint = new ListenerClientEndPoint();
|
||||
Session session = client.connect(endPoint, URI.create("ws://localhost:" + port + "/echo")).get(5, TimeUnit.SECONDS);
|
||||
session.sendText("hello", Callback.NOOP);
|
||||
endPoint.thenRun(() -> System.err.println("SUCCESS"));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListenerClientEndPoint extends CompletableFuture<Void> implements Session.Listener.AutoDemanding
|
||||
{
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
complete(null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.eclipse.jetty.LEVEL=INFO
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>test-jpms</artifactId>
|
||||
<groupId>org.eclipse.jetty.tests</groupId>
|
||||
<version>12.0.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>test-jpms-websocket-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Tests :: JPMS :: Core WebSocket</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>jetty-websocket-core-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>jetty-websocket-core-client</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,22 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 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
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
/**
|
||||
* This module-info.java exists so that the tests can be run in JPMS mode,
|
||||
* therefore testing the JPMS module descriptors of the dependencies involved.
|
||||
*/
|
||||
module org.eclipse.jetty.websocket.core.tests
|
||||
{
|
||||
requires org.eclipse.jetty.websocket.core.client;
|
||||
requires org.eclipse.jetty.websocket.core.server;
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 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
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
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.FrameHandler;
|
||||
import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
|
||||
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
|
||||
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class WebSocketCoreJPMSTest
|
||||
{
|
||||
private Server _server;
|
||||
private ServerConnector _serverConnector;
|
||||
private WebSocketCoreClient _client;
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws Exception
|
||||
{
|
||||
_server = new Server();
|
||||
_serverConnector = new ServerConnector(_server);
|
||||
_server.addConnector(_serverConnector);
|
||||
|
||||
WebSocketUpgradeHandler webSocketUpgradeHandler = new WebSocketUpgradeHandler();
|
||||
FrameHandler myFrameHandler = new TestFrameHandler("Server");
|
||||
webSocketUpgradeHandler.addMapping("/ws", WebSocketNegotiator.from(negotiation -> myFrameHandler));
|
||||
|
||||
_server.setHandler(webSocketUpgradeHandler);
|
||||
_server.start();
|
||||
|
||||
_client = new WebSocketCoreClient();
|
||||
_client.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception
|
||||
{
|
||||
_client.stop();
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleEcho() throws Exception
|
||||
{
|
||||
TestFrameHandler frameHandler = new TestFrameHandler("Client");
|
||||
URI uri = URI.create("ws://localhost:" + _serverConnector.getLocalPort() + "/ws");
|
||||
CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(_client, uri, frameHandler);
|
||||
upgradeRequest.addExtensions("permessage-deflate");
|
||||
CoreSession coreSession = _client.connect(upgradeRequest).get(5, TimeUnit.SECONDS);
|
||||
coreSession.close(Callback.NOOP);
|
||||
}
|
||||
|
||||
public static class TestFrameHandler implements FrameHandler
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TestFrameHandler.class);
|
||||
|
||||
private final String _id;
|
||||
|
||||
public TestFrameHandler(String id)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(CoreSession coreSession, Callback callback)
|
||||
{
|
||||
LOG.info(_id + " onOpen");
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrame(Frame frame, Callback callback)
|
||||
{
|
||||
LOG.info(_id + " onFrame");
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable cause, Callback callback)
|
||||
{
|
||||
LOG.info(_id + " onError");
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(CloseStatus closeStatus, Callback callback)
|
||||
{
|
||||
LOG.info(_id + " onClosed");
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue