websocketjettyembedded-jettyjetty-11

Embedded Jetty WebSocket server in a different context


I'm trying to run an embedded Jetty websocket server with different content in different contexts.

The websocket part works if I put it in the / context, but I want to server other content there. The websocket part doesn't work if I put it into any other context.

Here is my code:

    public WebsocketServer(int port, Model model, SessionManager sessionManager) throws Exception {
        Server server = new Server(port);
        server.setRequestLog((request, response) -> log_.info("request {}", request));

        ContextHandlerCollection handlers = new ContextHandlerCollection();

        ServletContextHandler sch;

        sch = new ServletContextHandler();
        sch.setContextPath("/model");
        sch.addServlet(new ServletHolder(new ModelServlet(model)), "/");
        sch.setResourceBase("cfg");
        handlers.addHandler(sch);

        sch = new ServletContextHandler();
        sch.setContextPath("/web");
        sch.addServlet(DefaultServlet.class, "/*");
        sch.setResourceBase("cfg");
        sch.setWelcomeFiles(new String[]{"websocket.html"});
        handlers.addHandler(sch);

        sch = new ServletContextHandler();
        sch.setContextPath("/");     // <---- would like to change this context to /ws
        Servlet websocketServlet = new JettyWebSocketServlet() {
            @Override
            protected void configure(JettyWebSocketServletFactory factory) {
                factory.addMapping("/", (req, res) -> {
                    Session s = new Session(req);
                    sessionManager.add(s);
                    return s;
                });
            }
        };
        sch.addServlet(new ServletHolder(websocketServlet), "/");
        JettyWebSocketServletContainerInitializer.configure(sch, null);
        handlers.addHandler(sch);

        sch = new ServletContextHandler();
        sch.setContextPath("/*");
        sch.addServlet(DefaultServlet.class, "/*");
        sch.setResourceBase("cfg");
        handlers.addHandler(sch);

        server.setHandler(handlers);

        server.start();
    }

If I put my JettyWebSocketServlet in the / context, it works, but then it never gets to my DefaultServlet. I have tried changing my JettyWebSocketServlet to /ws, but then my webpage can't connect, but I do get the result from my DefaultServlet. My ModelServlet works in the /model context in either case.

My webpage tries to connect to ws://localhost:8080/ws

I am using jetty 11.0.16


Solution

  • Jetty 11 is now at End of Community Support

    See: https://github.com/jetty/jetty.project/issues/10485

    You have a few fundamental mistakes in your setup.

    Your setup also gains nothing by splitting it up into different ServletContexts.

    First, your use of DefaultServlet, the DefaultServlet is always named "default" and on the "default" servlet mapping of /.

    ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
    holderPwd.setInitParameter("dirAllowed", "true");
    scch.addServlet(holderPwd, "/"); // <-- there should always be at least one at "/"
    

    If you have a need for more than one DefaultServlet then read the past answer at https://stackoverflow.com/a/20223103/775715

    See also the up to date jetty-examples on this subject.

    Next, a ServletContextHandler cannot have a context-path with a glob symbol.

    This is bad, and will not be used unless you somehow send a "*" in the URI path itself.

    sch = new ServletContextHandler();
    sch.setContextPath("/*"); // <-- this will only map if URI path is exactly `/*`
    

    Lastly, if you want websocket to be on /, then don't use the WebSocketServlet as that will override the DefaultServlet behaviors which is not what you want.

    Use the WebSocketUpgradeFilter instead, as this will not interfere with other requests to / or /* that are not websocket requests.

    // Add the websocket filter
    JettyWebSocketServletContainerInitializer.configure(sch, (context, configurator) ->
    {
        configurator.setIdleTimeout(Duration.ofMillis(5000));
        configurator.addMapping("/", (req, res) -> {
            Session s = new Session(req);
            sessionManager.add(s);
            return s;
        });
    });
    

    See the examples at

    Lastly, your setup is overly complicated.

    You can accomplish everything you want to do, with just 1 ServletContextHandler and a mix of url-patterns.

    // This is Jetty 12 code, against the ee10 environment.
    
    ServletContextHandler sch = new ServletContextHandler();
    sch.setContextPath("/");
    Resource baseResource = ResourceFactory.of(sch).newClassLoaderResource("cfg");
    if (!Resources.isReadableDirectory(baseResource))
        throw new FileNotFoundException("Unable to find base resource");
    sch.setBaseResourceBase(baseResource);
    
    sch.setWelcomeFiles(new String[]{"websocket.html"});
    sch.addServlet(new ServletHolder(new ModelServlet(model)), "/model/*");
    
    Resource webResource = ResourceFactory.of(sch).newClassLoaderResource("web");
    if (!Resources.isReadableDirectory(webResource))
        throw new FileNotFoundException("Unable to find web resource");
    
    ServletHolder holderWeb = new ServletHolder("static-web", DefaultServlet.class);
    holderWeb.setInitParameter("baseResource", webResource.getURI().toASCIIString());
    holderWeb.setInitParameter("dirAllowed", "true");
    holderWeb.setInitParameter("pathInfoOnly", "true");
    sch.addServlet(holderWeb, "/web/*");
    
    JettyWebSocketServletContainerInitializer.configure(sch, (context, configurator) ->
    {
        configurator.setIdleTimeout(Duration.ofMillis(5000));
        configurator.addMapping("/ws", (req, res) -> {
            Session s = new Session(req);
            sessionManager.add(s);
            return s;
        });
    });
    
    server.setHandler(sch);