Search code examples
servletsjettywarjetty-9

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


Here is my custom HttpServlet implementation:

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

And here is my custom WebSocketServlet implementation:

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

Here is the WEB-INF/web.xml file:

<?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>

Here is $JETTY_BASE/webapps/admin.xml file:

<?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>

Finally, here is $JETTY_BASE/webapps/ws.xml file:

    <?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>

When I start standalone Jetty 9 (with deploy and other modules active), I get the error message:

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$1.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$1.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)

My question is please:

How to make AdminServlet and WsServlet co-exist in the same WAR-file?

When I comment one of the both, everything works as expected.


Solution

  • Just implement the doGet method in your extended WebSocketServlet, it is just a normal HttpServlet with WebSocket Upgrade knowledge after all.

    Here's a working example of this...

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

    If you hit this with an HTTP request you get ...

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

    If you connect to it from a websocket client and send a message you get ...

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

    Tip: use http://websocket.org/echo.html and use a "Location" of ws://localhost:8080/multi to test the websocket connection.