Search code examples
servletswebsocketjettyembedded-jettyjava-websocket

Container-Agnostic Websocket throws NPE with Embedded Jetty


I'm writing a container-agnostic Websocket server. My development environment uses IntelliJ IDEA with an embedded Jetty version 9.3.11.v20160721 (embed code below).

My code, however, uses the Tomcat Websocket libraries, tomcat-websocket-api and tomcat-websocket, version 8.5.4.

I want to register the Websocket EndPoint with a ServletContextListener. Following some examples from the Tyrus project, as well as some answers on SO and in the Jetty mailing list by Joakim Erdfelt (@joakim-erdfelt), I wrote the following implementation and added it via a listener in web.xml.

The code enters the contextInitialized() method so that part seems to work properly, but unfortunately the ServletContext does not contain the attribute javax.websocket.server.ServerContainer and therefore it returns null.

@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {

    final ServerContainer serverContainer = (ServerContainer) servletContextEvent
            .getServletContext()
            .getAttribute("javax.websocket.server.ServerContainer");

    // *** serverContainer is null ***

    try {
        serverContainer.addEndpoint(ChatAnnotation.class);
    } 
    catch (DeploymentException e) { e.printStackTrace(); }
}

The only available attributes are javax.servlet.context.tempdir, org.eclipse.jetty.util.DecoratedObjectFactory, org.eclipse.jetty.tlds, org.eclipse.jetty.resources, org.eclipse.jetty.server.Executor, org.eclipse.jetty.webFragments

What am I doing wrong? Is this strictly an embedding-issue? Will it run properly in standard Jetty (i.e., not embedded)?

The embed code is below:

WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setResourceBase(webroot);   
webapp.setDescriptor(webroot + "/WEB-INF/web.xml");

Server server = new Server();

ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);             

server.setConnectors(new ServerConnector[] { connector });
server.setHandler(webapp);      
server.start();
server.join();

Solution

  • The JSR ServerContainer isn't initialized automatically in embedded-jetty.

    Use the WebSocketServerContainerInitializer ...

    WebAppContext webapp = new WebAppContext();
    webapp.setContextPath("/");
    webapp.setResourceBase(webroot);   
    webapp.setDescriptor(webroot + "/WEB-INF/web.xml");
    
    Server server = new Server();
    
    ServerConnector connector = new ServerConnector(server);
    connector.setPort(8080);             
    
    server.setConnectors(new ServerConnector[] { connector });
    server.setHandler(webapp);
    
    // Add this line
    WebSocketServerContainerInitializer.configureContext(webapp);
    
    server.start();
    server.join();
    

    Note: The WebSocketServerContainerInitializer is a javax.servlet.ServerContainerInitializer and you could set up your embedded-jetty to execute that ServerContainerInitializer automatically.

    If you heavily rely on bytecode scanning of annotations, or have a javax.websocket.server.ServerApplicationConfig implementation, then you'll want to have the ServerContainerInitializer execute automatically.

    Note: the javax.servlet.ServerContainerInitializer is only valid for a org.eclipse.jetty.webapp.WebAppContext.

    If you use the org.eclipse.jetty.servlet.ServletContextHandler (the more common form in embedded-jetty) then you cannot use a javax.servlet.ServerContainerInitializer.

    To set that up, do this:

    private void enableAnnotationScanning(Server server)
    {
        Configuration.ClassList classlist = Configuration.ClassList.setServerDefault(server);
        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
                "org.eclipse.jetty.plus.webapp.EnvConfiguration",
                "org.eclipse.jetty.plus.webapp.PlusConfiguration");
        classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
                "org.eclipse.jetty.annotations.AnnotationConfiguration");
    }
    
    // ... later on ...
    
    Server server = new Server();
    enableAnnotationScanning(server);