Udo Klimaschewski
Udo Klimaschewski

Reputation: 5315

Embedded jetty: display all sessions in context

I recently moved a project that uses an embedded Jetty from version 6.1 to 9.4. It was easier than I expected. Now only one feature is left to migrate: The project has an administration tool, that shows all the loaded contexts (it can host more than one web applicaton) and for each context, it lists the registered HTTP sessions, including session attributes etc. In Jetty 6, it goes something like this:

Handler[] handlers = jettyServer.getHandlers();
for (Handler h : handlers) {
  if (h instanceof ContextHandlerCollection) {
    ContextHandlerCollection ch = (ContextHandlerCollection) h;
    Handler[] contexts = ch.getHandlers();
    for (Handler context : contexts) {
      if (context instanceof WebAppContext) {
        WebAppContext wapp = (WebAppContext) context;
        // Here, I am stuck in migrating this to jetty 9:
        SessionManager sm = wapp.getSessionHandler().getSessionManager();
        if (sm instanceof HashSessionManager) {
          HashSessionManager hsm = (HashSessionManager) sm;
          Map<?,?> sessions = hsm.getSessionMap();
          if (sessions != null) {
            for (Map.Entry<?,?>  entry : sessions.entrySet()) {
              if (entry.getValue() instanceof HttpSession) {
                HttpSession s = (HttpSession) entry.getValue();
              }
            }
          }
        }
      }
    }
  }
}

The new Jetty 9 structure is a bit different, but I was able to access all the contexts. But in Jetty 9, I am unable to access the SessionManager from the context.

wapp.getSessionHandler().getSessionManager();

Does not work anymore, the getSessionManager() method on SessionHandler is not there anymore. And after searching for hours, I did not find any way to get from a WebAppContext to the HTTPSession instances in that context. Does anyone know a way to get there?

Upvotes: 4

Views: 1837

Answers (1)

Joakim Erdfelt
Joakim Erdfelt

Reputation: 49462

There's no access to the list of active HttpSession.

This change occurred to support continual improvement of Session management to handle distributed SessionDataStores and distributed servers without Session stickiness (such as many cloud offerings)

You would be better off tracking the HttpSessions yourself at the server side.

How to do this ...

Implement a custom Session Listener. Make sure it implements both javax.servlet.http.HttpSessionListener and javax.servlet.http.HttpSessionIdListener to track the Sessions that are created / destroyed / changed. (might also want to implement javax.servlet.ServletContextListener to stop tracking for sessions on destroyed contexts)

Make sure you only track the sessions by their IDs (and optionally what context they belong to) in this implementation. (don't hold a reference to the HttpSession object, or you'll mess up the GC and WebApp lifecycle).

Example:

public static class SessionTracker implements HttpSessionListener,
                    HttpSessionIdListener, ServletContextListener
{
    private final Server server;
    private final SessionHandler sessionHandler;
    private String contextPath;
    private HashSet<String> sessionIds = new HashSet<>();

    public SessionTracker(Server server, SessionHandler sessionHandler)
    {
        this.server = server;
        this.sessionHandler = sessionHandler;
        this.sessionHandler.addEventListener(this);
    }

    public String getContextPath()
    {
        return contextPath;
    }

    public SessionHandler getSessionHandler()
    {
        return sessionHandler;
    }

    public HashSet<String> getSessionIds()
    {
        return sessionIds;
    }

    @Override
    public void contextInitialized(ServletContextEvent sce)
    {
        contextPath = sce.getServletContext().getContextPath();
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce)
    {
        Collection<SessionTracker> trackers = this.server.getBeans(SessionTracker.class);
        trackers.removeIf((tracker) -> tracker.getContextPath().equals(sce.getServletContext().getContextPath()));
    }

    @Override
    public void sessionCreated(HttpSessionEvent se)
    {
        sessionIds.add(se.getSession().getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se)
    {
        sessionIds.remove(se.getSession().getId());
    }

    @Override
    public void sessionIdChanged(HttpSessionEvent event, String oldSessionId)
    {
        sessionIds.add(oldSessionId);
        sessionIds.add(event.getSession().getId());
    }
}

And the way to add this to all contexts properly ...

// After server.start() somewhere.
addSessionTracker(server.getHandler());

private static void addSessionTracker(Handler handler)
{
    if (handler == null)
    {
        return; // skip
    }

    if (handler instanceof HandlerCollection)
    {
        HandlerCollection handlers = (HandlerCollection) handler;
        for (Handler child : handlers.getHandlers())
        {
            addSessionTracker(child);
        }
    }
    else
    {
        if (handler instanceof ServletContextHandler)
        {
            ServletContextHandler context = (ServletContextHandler) handler;
            SessionHandler sessionHandler = context.getSessionHandler();
            new SessionTracker(handler.getServer(), sessionHandler);
        }
    }
}

Then to use these trackers ...

private static void doSomethingWithSessionTracker(Server server)
{
    Collection<SessionTracker> trackers = server.getBeans(SessionTracker.class);
    trackers.forEach((tracker) -> {
        tracker.getSessionIds().forEach((sessionId) -> {
            Session session = tracker.getSessionHandler().getSession(sessionId);
            // Do something with Session.
        });
    });
}

Note that the above will only show you the Session being tracked on that server, if you have multiple servers, or a distributed Session store, you wont see the Sessions from other servers. If this is important to you, consider writing your own SessionDataStore that lets you access the stored Sessions.

Upvotes: 5

Related Questions