Search code examples
javawebsocketjetty

Set up websockets with Jetty 11


I am trying to migrate from Jetty 9.4 to Jetty 11 (maybe too early?) and failing in adapting the code for setting up websockets. The way I achieved this in 9.4 was as follows:

Server server = new Server();
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSendServerVersion(false);
HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfig);
ServerConnector httpConnector = new ServerConnector(server, httpFactory);
httpConnector.setPort(port);
server.setConnectors(new Connector[] { httpConnector });

// 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("/");

// Add a websocket to a specific path spec
ServletHolder holderEvents2 = new ServletHolder("websocket", EventsServlet.class);
context.addServlet(holderEvents2, "/events/*");

HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { context, new DefaultHandler() });

server.setHandler(handlers);

public class EventsServlet extends WebSocketServlet {

    @Override
    public void configure(WebSocketServletFactory factory) {
        // register a socket class as default
        factory.register(EchoSocket.class);
    }
}

public class EchoSocket implements WebSocketListener {
    // ...
}

As there is no WebSocketServlet class anymore, I fiddled around a bit and found the class JettyWebSocketServlet. According to its JavaDoc, I thought it should like as follows:

public class EventsServlet extends JettyWebSocketServlet {

    @Override
    protected void configure(JettyWebSocketServletFactory factory) {
        // register a socket class as default
//      factory.register(EchoSocket.class);
           factory.addMapping("/", (req,res)->new EchoSocket());

    }
}

but the line with addMapping is actually never executed. Also JettyWebSocketServletFactory does not have a method called setDefaultMaxFrameSize as suggested by the JavaDoc of JettyWebSocketServlet .

All I seem to be able to find on the web is for Jetty <= 9.4, even https://github.com/jetty-project/embedded-jetty-websocket-examples .

Any help would be highly appreciated.


Solution

  • I had a similar problem, though my version running under Jetty 9.4 was a bit different to yours, using WebSocketHandler rather than WebSocketServlet. I was having some problems with the old approach, since under Jetty 9.4 I had to pass my listener class as a Class object, which makes dependency injection a pain.

    I have now got this working under Jetty 11.0.0 though. I found your question a couple of days ago while I was trying to work out how to do this in Jetty 11, and it inspired me to actually get this working, so thanks!

    FWIW, my Jetty 9.4 version (for a trivial test) looked like this:

    public static void main(String[] argv) throws Exception
    {
        int serverPort = Integer.getInteger("server.port", 8080);
    
        Server server = new Server(serverPort);
        ContextHandlerCollection handlers = new ContextHandlerCollection();
    
        WebSocketHandler wsh = new WebSocketHandler.Simple (TestWebSocketListener.class);
        handlers.addHandler(createContextHandler("/ws", wsh));
    
        ResourceHandler rh = new ResourceHandler();
        rh.setDirectoriesListed(false);
        rh.setBaseResource(Resource.newClassPathResource("/WEB-STATIC/"));
        handlers.addHandler(createContextHandler("/", rh));
    
        server.setHandler(handlers);
    
        server.start();
        server.join();
    }
    
    // Convenience method to create and configure a ContextHandler.
    private static ContextHandler createContextHandler(String contextPath, Handler wrappedHandler)
    {
        ContextHandler ch = new ContextHandler (contextPath);
        ch.setHandler(wrappedHandler);
        ch.clearAliasChecks();
        ch.setAllowNullPathInfo(true);
        return ch;
    }
    

    Here, TestWebSocketListener is a trivial implementation of WebSocketListener which just implements each listener method and prints the arguments to System.err. (I did say this was a trivial test.) I also send a message back to the client in the onWebSocketText callback, just to check that this works.

    I'm not using DefaultHandler here - instead, I explicitly create a ResourceHandler which serves a few simple static resources from a resource tree stored within the classpath (under the /WEB-STATIC/ prefix).

    The version I have working under Jetty 11.0.0 just changes the main method above to this:

    public static void main(String[] argv) throws Exception
    {
        int serverPort = Integer.getInteger("server.port", 8080);
    
        Server server = new Server(serverPort);
        ContextHandlerCollection handlers = new ContextHandlerCollection();
    
        ResourceHandler rh = new ResourceHandler();
        rh.setDirectoriesListed(false);
        rh.setBaseResource(Resource.newClassPathResource("/WEB-STATIC/"));
        handlers.addHandler(createContextHandler("/", rh));
    
        Servlet websocketServlet = new JettyWebSocketServlet() {
            @Override protected void configure(JettyWebSocketServletFactory factory) {
                factory.addMapping("/", (req, res) -> new TestWebSocketListener());
            }
        };
        ServletContextHandler servletContextHandler = new ServletContextHandler();
        servletContextHandler.addServlet(new ServletHolder(websocketServlet), "/ws");
        JettyWebSocketServletContainerInitializer.configure(servletContextHandler, null);
        handlers.addHandler(servletContextHandler);
    
        server.setHandler(handlers);
    
        server.start();
        server.join();
    }
    

    The call to JettyWebSocketServletContainerInitializer.configure is important: without that I got exceptions complaining that the WebSocket components had not been initialised.

    One thing to note is that the order of the two handlers has been changed - previously, the WebSocketHandler was added before the ResourceHandler. However, when using ServletContextHandler this was returning 404s for paths that should have been handled by the ResourceHandler, so I swapped the order.

    The TestWebSocketListener is identical between the two versions. Obviously, it's a lot easier for me to add dependency injection now I control the constructor call!

    The other thing I had to change was the names of the Maven artifacts I pulled in. The websocket-server artifact no longer seems to exist in Jetty 11, so I changed this:

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>9.4.35.v20201120</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty.websocket</groupId>
        <artifactId>websocket-server</artifactId>
        <version>9.4.35.v20201120</version>
    </dependency>
    

    to this:

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>11.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty.websocket</groupId>
        <artifactId>websocket-jetty-server</artifactId>
        <version>11.0.0</version>
    </dependency>