如何将 HttpServlet 和 WebSocketServlet 放在 Jetty 9 的同一个 WAR 文件中?

How to put HttpServlet and WebSocketServlet in same WAR file for Jetty 9?

这是我的自定义 HttpServlet 实现:

public class AdminServlet extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        response.setContentType("text/html");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("<h1>Hello Servlet</h1>");
    }
}

这是我的自定义 WebSocketServlet 实现:

public class WsServlet extends WebSocketServlet {
    @Override
    public void configure(WebSocketServletFactory factory) {
        factory.register(MyListener.class);
    }
}

这是 WEB-INF/web.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<web-app metadata-complete="false" version="3.1" 
         xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

    <servlet>
        <servlet-name>WsServlet</servlet-name>
        <servlet-class>de.afarber.websockets.WsServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>WsServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>AdminServlet</servlet-name>
        <servlet-class>de.afarber.websockets.AdminServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AdminServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

这里是 $JETTY_BASE/webapps/admin.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" 
    "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">

    <Set name="contextPath">/admin</Set>
    <Set name="war"><SystemProperty name="jetty.base"/>/webapps/ws-servlet-0.1-SNAPSHOT.war</Set>

</Configure>

最后,这里是 $JETTY_BASE/webapps/ws.xml 文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" 
        "http://www.eclipse.org/jetty/configure_9_0.dtd">
    <Configure class="org.eclipse.jetty.webapp.WebAppContext">

        <Set name="contextPath">/ws</Set>
        <Set name="war"><SystemProperty name="jetty.base"/>/webapps/ws-servlet-0.1-SNAPSHOT.war</Set>

    </Configure>

当我启动独立的 Jetty 9(deploy 和其他模块处于活动状态)时,我收到错误消息:

2016-11-28 09:59:40.994:INFO:oejs.Server:main: jetty-9.3.12.v20160915
2016-11-28 09:59:41.023:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///C:/Users/U299FAV/slova/jetty.base/webapps/] at interval 1
2016-11-28 09:59:41.152:INFO:oejsh.ContextHandler:main: Started o.e.j.s.h.ContextHandler@783e6358{/words,null,AVAILABLE}
2016-11-28 09:59:41.165:INFO:oejsh.ContextHandler:main: Started o.e.j.s.h.ContextHandler@7dc7cbad{/twentythirteen-child,null,AVAILABLE}
2016-11-28 09:59:41.950:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=307ms
2016-11-28 09:59:41.970:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /ws, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-11-28 09:59:41.992:WARN:oejw.WebAppContext:main: Failed startup of context o.e.j.w.WebAppContext@5f2108b5{/ws,file:///C:/Users/U299FAV/AppData/Local/Temp/jetty-0.0.0.0-8080-ws-servlet-0.1-SNAPSHOT.war-_ws-any-239390
3927144372923.dir/webapp/,UNAVAILABLE}{/ws-servlet-0.1-SNAPSHOT.war}
java.lang.IllegalStateException: Multiple servlets map to path: /: AdminServlet,WsServlet
        at org.eclipse.jetty.servlet.ServletHandler.updateMappings(ServletHandler.java:1504)
        at org.eclipse.jetty.servlet.ServletHandler.doStart(ServletHandler.java:156)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:105)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:61)
        at org.eclipse.jetty.security.SecurityHandler.doStart(SecurityHandler.java:361)
        at org.eclipse.jetty.security.ConstraintSecurityHandler.doStart(ConstraintSecurityHandler.java:448)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:105)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:61)
        at org.eclipse.jetty.server.handler.ScopedHandler.doStart(ScopedHandler.java:120)
        at org.eclipse.jetty.server.session.SessionHandler.doStart(SessionHandler.java:116)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:105)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:61)
        at org.eclipse.jetty.server.handler.ScopedHandler.doStart(ScopedHandler.java:120)
        at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:809)
        at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:345)
        at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1404)
        at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1366)
        at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:778)
        at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:262)
        at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:520)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.deploy.bindings.StandardStarter.processBinding(StandardStarter.java:41)
        at org.eclipse.jetty.deploy.AppLifeCycle.runBindings(AppLifeCycle.java:188)
        at org.eclipse.jetty.deploy.DeploymentManager.requestAppGoal(DeploymentManager.java:499)
        at org.eclipse.jetty.deploy.DeploymentManager.addApp(DeploymentManager.java:147)
        at org.eclipse.jetty.deploy.providers.ScanningAppProvider.fileAdded(ScanningAppProvider.java:180)
        at org.eclipse.jetty.deploy.providers.WebAppProvider.fileAdded(WebAppProvider.java:458)
        at org.eclipse.jetty.deploy.providers.ScanningAppProvider.fileAdded(ScanningAppProvider.java:64)
        at org.eclipse.jetty.util.Scanner.reportAddition(Scanner.java:610)
        at org.eclipse.jetty.util.Scanner.reportDifferences(Scanner.java:529)
        at org.eclipse.jetty.util.Scanner.scan(Scanner.java:392)
        at org.eclipse.jetty.util.Scanner.doStart(Scanner.java:313)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.deploy.providers.ScanningAppProvider.doStart(ScanningAppProvider.java:150)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.deploy.DeploymentManager.startAppProvider(DeploymentManager.java:561)
        at org.eclipse.jetty.deploy.DeploymentManager.doStart(DeploymentManager.java:236)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
        at org.eclipse.jetty.server.Server.start(Server.java:411)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:113)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:61)
        at org.eclipse.jetty.server.Server.doStart(Server.java:378)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.xml.XmlConfiguration.run(XmlConfiguration.java:1516)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.eclipse.jetty.xml.XmlConfiguration.main(XmlConfiguration.java:1441)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.eclipse.jetty.start.Main.invokeMain(Main.java:214)
        at org.eclipse.jetty.start.Main.start(Main.java:457)
        at org.eclipse.jetty.start.Main.main(Main.java:75)

我的问题是:

如何使 AdminServletWsServlet 在同一个 WAR 文件中共存?

当我评论两者之一时,一切都按预期工作。

如果您查看 servlet-mapping 部分,您的两个 servlet 都映射到相同的 URL。如果是这样,容器就不知道应该将您的请求路由到给定 URL(在您的情况下为 / - root)的哪个 servlet。

<url-pattern>/</url-pattern> 的 URL 更改为至少一个 servlet 的另一个 URL,例如,如果您将后者更改为

<servlet-mapping>
    <servlet-name>AdminServlet</servlet-name>
    <url-pattern>/AdminServlet</url-pattern>
</servlet-mapping>

例如,您的应用程序应该启动并且两个 servlet 都可用,但在不同的 URLs 上。

在你的扩展WebSocketServlet中实现doGet方法即可,毕竟只是一个普通的HttpServlet,有WebSocket升级知识。

这是一个工作示例...

package jetty.websocket;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
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.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

public class MultiPurposeServletExample
{
    public static void main(String[] args)
    {
        try
        {
            Server server = new Server(8080);

            ServletContextHandler context = new ServletContextHandler();
            context.setContextPath("/");
            context.addServlet(MultiPurposeServlet.class, "/multi");

            server.setHandler(context);

            server.start();
            server.join();
        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
    }

    @WebSocket
    public static class MultiPurposeSocket
    {
        @OnWebSocketMessage
        public void onMessage(Session session, String msg)
        {
            session.getRemote().sendStringByFuture("'" + msg + "' echo from " + this.getClass().getName());
        }
    }

    public static class MultiPurposeServlet extends WebSocketServlet
    {
        @Override
        public void configure(WebSocketServletFactory factory)
        {
            factory.register(MultiPurposeSocket.class);
        }

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setContentType("text/plain");
            resp.getWriter().println("Hello from " + this.getClass().getName());
        }
    }
}

如果您使用 HTTP 请求点击它,您会得到...

$ curl http://localhost:8080/multi
Hello from jetty.websocket.MultiPurposeServletExample$MultiPurposeServlet

如果您从 websocket 客户端连接到它并发送消息,您会收到...

'Rock it with HTML5 WebSocket' echo from jetty.websocket.MultiPurposeServletExample$MultiPurposeSocket

提示:使用http://websocket.org/echo.html并使用ws://localhost:8080/multi的"Location"来测试websocket连接。