Search code examples
websocketembedded-jettyjetty-9

Accessing HttpSession inside an annotated @WebSocket class on Embedded Jetty 9


How can I access a HttpSession object inside an annotated @WebSocket class in Jetty 9?

I found how to do it using @ServerEndpoint annotation, like here: HttpSession from @ServerEndpoint

Using the @WebSocket annotation, like in the class bellow, how can I do it?

@WebSocket
public class AuctionWebSocket {

    // NEED TO ACCESS HttpSession OBJECT INSIDE THESE METHODS:

    @OnWebSocketConnect
    public void onConnect(Session session) {
        System.out.println("onConnect...");        
    }

    @OnWebSocketMessage
    public void onMessage(String message) {        
        System.out.println("Message: " + message);
    }    

    @OnWebSocketClose
    public void onClose(int statusCode, String reason) {
        System.out.println("onClose...");        
    }

    @OnWebSocketError
    public void onError(Throwable t) {
        System.out.println("onError...");        
    }    
}

Inside the method onConnect(Session session), I tried to call session.getUpgradeRequest().getSession() which always returns null.

For sake of information, here is how I start embedded Jetty 9:

public class Main {

    public static void main(String[] args) throws Exception {

        String webPort = System.getenv("PORT");
        if (webPort == null || webPort.isEmpty()) {
            webPort = "8080";
        }

        Server server = new Server(Integer.parseInt(webPort));

        ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server);
        classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
                "org.eclipse.jetty.annotations.AnnotationConfiguration");

        WebAppContext wac = new WebAppContext();
        String webappDirLocation = "./src/main/webapp/";

        wac.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*/classes/.*");
        wac.setDescriptor(webappDirLocation + "/WEB-INF/web.xml");
        wac.setBaseResource(new ResourceCollection(new String[]{webappDirLocation, "./target"}));
        wac.setResourceAlias("/WEB-INF/classes/", "/classes/");
        wac.setContextPath("/");
        wac.setParentLoaderPriority(true);

        /*
         * WebSocket handler.
         */
        WebSocketHandler wsh = new WebSocketHandler() {

            @Override
            public void configure(WebSocketServletFactory wssf) {
                wssf.register(AuctionWebSocket.class);
            }
        };

        ContextHandler wsc = new ContextHandler();
        wsc.setContextPath("/auction-notifications");
        wsc.setHandler(wsh);

        ContextHandlerCollection chc = new ContextHandlerCollection();
        chc.setHandlers(new Handler[]{wac, wsc});

        server.setHandler(chc);

        server.start();
        server.join();
    }

}

Let me know if you need more information.

Any help will be appreciated.

Thanks in advance.


Solution

  • You'll want to use the WebSocketCreator concepts.

    First you set the WebSocketCreator of your choice in the WebSocketServletFactory that you configure in your WebSocketServlet

    public class MySessionSocketServlet extends WebSocketServlet
    {
        @Override
        public void configure(WebSocketServletFactory factory)
        {
            factory.getPolicy().setIdleTimeout(30000);
            factory.setCreator(new MySessionSocketCreator());
        }
    }
    

    Next, you'll want to grab the HttpSession during the upgrade and pass it into the WebSocket object that you are creating.

    public class MySessionSocketCreator implements WebSocketCreator
    {
        @Override
        public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
        {
            HttpSession httpSession = req.getSession();
            return new MySessionSocket(httpSession);
        }
    }
    

    Finally, just keep track of that HttpSession in your own WebSocket.

    @WebSocket
    public class MySessionSocket
    {
        private HttpSession httpSession;
        private Session wsSession;
    
        public MySessionSocket(HttpSession httpSession)
        {
            this.httpSession = httpSession;
        }
    
        @OnWebSocketConnect
        public void onOpen(Session wsSession)
        {
            this.wsSession = wsSession;
        }
    }
    

    Of note: the HttpSession can expire and be scavenged and cleaned up while a WebSocket is active. Also, the HttpSession contents at this point are not guaranteed to be kept in sync with changes from other web actions (this mostly depends on what Session storage / caching technology you use on the server side)

    And one more note: resist the urge to store / track the ServletUpgradeRequest object in your Socket instance, as this object is recycled and cleaned up aggressively by Jetty proper.