与容器无关的 Websocket 使用嵌入式 Jetty 抛出 NPE

Container-Agnostic Websocket throws NPE with Embedded Jetty

我正在编写一个与容器无关的 Websocket 服务器。我的开发环境使用带有嵌入式 Jetty 版本的 IntelliJ IDEA 9.3.11.v20160721(下面嵌入代码)。

但是,我的代码使用 Tomcat Websocket 库,tomcat-websocket-apitomcat-websocket,版本 8.5.4

我想用 ServletContextListener 注册 Websocket EndPoint。根据 Tyrus project 中的一些示例,以及 Joakim Erdfelt (@joakim-erdfelt) 在 SO 和 Jetty 邮件列表中的一些答案,我编写了以下实现并通过 listener 添加了它web.xml.

代码进入 contextInitialized() 方法,因此该部分似乎可以正常工作,但不幸的是,ServletContext 不包含属性 javax.websocket.server.ServerContainer,因此它 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(); }
}

唯一可用的属性是 javax.servlet.context.tempdirorg.eclipse.jetty.util.DecoratedObjectFactoryorg.eclipse.jetty.tldsorg.eclipse.jetty.resourcesorg.eclipse.jetty.server.Executororg.eclipse.jetty.webFragments

我做错了什么?这严格来说是一个嵌入问题吗?它会在标准 Jetty 中正确 运行(即,未嵌入)吗?

嵌入代码如下:

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

JSR ServerContainer 没有在 embedded-jetty 中自动初始化。

使用 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();

注意:WebSocketServerContainerInitializer 是一个 javax.servlet.ServerContainerInitializer,您可以将 embedded-jetty 设置为自动执行 ServerContainerInitializer

如果您严重依赖注释的字节码扫描,或者有一个 javax.websocket.server.ServerApplicationConfig 实现,那么您会希望 ServerContainerInitializer 自动执行。

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.

要进行设置,请执行以下操作:

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