Fixes #11096 - IllegalAccessException when invoking WebSocket endpoint methods in Jetty 12 (#11229)

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:
Simone Bordet 2024-01-25 16:40:48 +01:00 committed by GitHub
parent ef75595e8e
commit b5d0fd6f2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 244 additions and 293 deletions

View File

@ -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]

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -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();

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -0,0 +1 @@
org.eclipse.jetty.LEVEL=INFO

View File

@ -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>

View File

@ -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;
}

View File

@ -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();
}
}
}