JUnit test with javax.websocket on embedded Jetty

2019-02-27 20:42发布

问题:

I'm trying to write a test case which creates a socket and connects to an embedded jetty instance. I'm using

  • Jetty: 9.2.0.RC0
  • javax.websocket-api & javax.websocket-client-api: 1.0
  • javax.websocket server & client impl: 9.1.5.v20140505

Starting the embedded jetty server with the websocket servlet seems to work fine. I took some code from this example. However this line

...
container.connectToServer(SocketClient.class, uri);
...

throws this exception

java.io.IOException: java.util.concurrent.RejectedExecutionException: org.eclipse.jetty.util.thread.NonBlockingThread@6f7476d
    at org.eclipse.jetty.websocket.client.WebSocketClient.initialiseClient(WebSocketClient.java:462)
    at org.eclipse.jetty.websocket.client.WebSocketClient.connect(WebSocketClient.java:187)
    at org.eclipse.jetty.websocket.jsr356.ClientContainer.connect(ClientContainer.java:135)
    at org.eclipse.jetty.websocket.jsr356.ClientContainer.connectToServer(ClientContainer.java:172)
    at com.playquickly.socket.SocketClient.connect(SocketClient.java:18)
    at com.playquickly.servlet.SocketServletTest.testSocket(SocketServletTest.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
Caused by: java.util.concurrent.RejectedExecutionException: org.eclipse.jetty.util.thread.NonBlockingThread@6f7476d
    at org.eclipse.jetty.util.thread.QueuedThreadPool.execute(QueuedThreadPool.java:361)
    at org.eclipse.jetty.io.SelectorManager.execute(SelectorManager.java:122)
    at org.eclipse.jetty.io.SelectorManager.doStart(SelectorManager.java:207)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:132)
    at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:114)
    at org.eclipse.jetty.websocket.client.io.ConnectionManager.doStart(ConnectionManager.java:200)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
    at org.eclipse.jetty.websocket.client.WebSocketClient.initialiseClient(WebSocketClient.java:454)
    ... 30 more

This is my wrapper for stating the embedded jetty server.

public class EmbeddedJetty {

    private final Logger LOG = Logger.getLogger(EmbeddedJetty.class.getSimpleName());

    private final int port;
    private Server server;

    public EmbeddedJetty(int port) {
        this.port = port;
    }

    public void start() throws Exception {
        server = new Server();
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(8080);
        server.addConnector(connector);

        // Setup the basic application "context" for this application at "/"
        // This is also known as the handler tree (in jetty speak)
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        server.setHandler(context);

        try {
            // Initialize javax.websocket layer
            ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);

            // Add WebSocket endpoint to javax.websocket layer
            wscontainer.addEndpoint(SocketServlet.class);
            System.out.println("Begin start");
            server.start();
            System.out.println("End start");
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
    }

    public void stop() throws Exception {
        server.stop();
        LOG.info("Jetty server stopped");
    }
}

The client endpoint

@ClientEndpoint(encoders = {MessageCoder.class}, decoders = {MessageCoder.class})
public class SocketClient {

    public static Session connect(URI uri) throws Exception {
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();

        try {
            // Attempt Connect
            return container.connectToServer(SocketClient.class, uri);
        } finally {
            // Force lifecycle stop when done with container.
            // This is to free up threads and resources that the
            // JSR-356 container allocates. But unfortunately
            // the JSR-356 spec does not handle lifecycles (yet)
            if (container instanceof LifeCycle) {
                ((LifeCycle) container).stop();
            }
        }
    }

    @OnMessage
    public void onMessage(Message msg, Session session) {
        System.out.println(session.getId() + ": " + msg.toString());
    }


}

And the test

public class SocketServletTest {

    private static EmbeddedJetty server;

    @ClassRule
    public static final ExternalResource integrationServer = new ExternalResource() {
        @Override
        protected void before() throws Throwable {
            System.out.println("Starting...");
            server = new EmbeddedJetty(8080);
            server.start();
            System.out.println("Started");
        }
    };


    @Before
    public void setUp() throws Exception {

    }

    @After
    public void shutdown() throws Exception {
        server.stop();
    }

    @Test
    public void testSocket() throws Exception {
        URI uri = server.getWebsocketUri(SocketServlet.class);

        Session s1 = SocketClient.connect(uri);
    }

}

回答1:

Don't mix versions of Jetty.

This is an unfortunate side effect of the design of the JSR-356 API. (The Client implementation is the root implementation, and the Server implementation is built on top of that)

The Client container is initialized per JVM, and each server container is initialized per webapp.

Your stacktrace is not Jetty 9.2.0.RC0 as you indicated (the line numbers are off)

https://github.com/eclipse/jetty.project/blob/jetty-9.2.0.RC0/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java#L200

They seem to be from Jetty 9.1.5.v20140505

https://github.com/eclipse/jetty.project/blob/jetty-9.1.5.v20140505/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java#L200

Use Jetty 9.2.2.v20140723 everywhere.

Also, using this version means you can get rid of the finally container.stop() hack.