34

I have a servlet which handles a multipart form post. The post is actually being made by a Flash file upload component embedded in the page. In some browsers, the Flash-generated POST doesn't include the JSESSIONID which is making it impossible for me to load certain information from the session during the post.

The flash upload component does include cookie and session information within a special form field. Using this form field, I can actually retrieve the JSESSIONID value. The problem is, I don't know how to use this JSESSIONID value to manually load that specific session.

Edit - Based on ChssPly76's solution, I created the following HttpSessionListener implementation:

    @Override
    public void sessionCreated(final HttpSessionEvent se) {
        final HttpSession session = se.getSession();
        final ServletContext context = session.getServletContext();
        context.setAttribute(session.getId(), session);
    }

    @Override
    public void sessionDestroyed(final HttpSessionEvent se) {
        final HttpSession session = se.getSession();
        final ServletContext context = session.getServletContext();
        context.removeAttribute(session.getId());
    }

Which adds all sessions to the ServletContext as attributes mapped by their unique ids. I could put a Map of sessions in the context instead, but it seems redundant. Please post any thoughts on this decision. Next, I add the following method to my servlet to resolve the session by id:

    private HttpSession getSession(final String sessionId) {
        final ServletContext context = getServletContext();
        final HttpSession session = (HttpSession) context.getAttribute(sessionId);
        return session;
    }
Eyal
  • 3,412
  • 1
  • 44
  • 60
Robert Campbell
  • 6,848
  • 12
  • 63
  • 93

5 Answers5

30

There is no API to retrieve session by id.

What you can do, however, is implement a session listener in your web application and manually maintain a map of sessions keyed by id (session id is retrievable via session.getId()). You will then be able to retrieve any session you want (as opposed to tricking container into replacing your current session with it as others suggested)

ChssPly76
  • 99,456
  • 24
  • 206
  • 195
  • 1
    My only concern with this is the human error portion where you will have a developer introduce some code using request.getSession() rather than using the map of session id's. I believe the "simpler" solution is to construct the URL appropriately. – Taylor Leese Sep 30 '09 at 17:48
  • Two ways I can imagine the servlet asking the listener for the session map: listener is a singleton, or placing the map in the ServletContext – Robert Campbell Sep 30 '09 at 18:04
  • Your listener is going to be a singleton without you having to hold on to its instance in a static variable - simply because you'll only ever declare it once in the web.xml. How do you communicate between said listener and your servlet is up to you - you can make it a "true" singleton; you can inject it into session during event handling or you can use some shared space (servlet context won't help as it's not accessible from the listener) – ChssPly76 Sep 30 '09 at 19:11
  • How about using a Filter to wrap every ServletRequest and replace getSession with an implementation that checks for a session id supplied by Flash, looks up the session from the map built by the SessionListener, and defers to the wrapped request's implementation if either the Flash parameter or the referenced session isn't present. – Andrew Duffy Oct 01 '09 at 09:25
  • 2
    It works, but what about session replication in a clustered scenario? From what I know, the servlet context is not replicated. – Victor Ionescu Sep 21 '11 at 13:18
  • @VictorIonescu you might need to use Sticky session to land on the specific container that originated the Session. – Gautam May 08 '23 at 09:34
3

A safe way to do it is to set the jsession id in the cookie - this is much safer than setting it in the url.

Once it is set as a cookie, then you can retrieve the session in the normal way using

request.getSession();

method.setRequestHeader("Cookie", "JSESSIONID=88640D6279B80F3E34B9A529D9494E09");
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
vanval
  • 997
  • 1
  • 9
  • 19
3

If you are using Tomcat, you ask tomcat directly (but it's ugly). I bet there are other hacky solutions for other web servers.

It uses an instance of the "Manager" interface to manage the sessions. What makes it ugly is that I haven't found a nice public interface to be able to hook into, so we have to use reflection to get the manager.

Below is a context listener that grabs that manager on context startup, and then can be used to get the Tomcat Session.

public class SessionManagerShim implements ServletContextListener {
    static Manager manager;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            manager = getManagerFromServletContextEvent(sce);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        manager = null;
    }

    private static Manager getManagerFromServletContextEvent(ServletContextEvent sce) throws NoSuchFieldException, IllegalAccessException {
        // Step one - get the ApplicationContextFacade (Tomcat loves facades)
        ApplicationContextFacade contextFacade = (ApplicationContextFacade)sce.getSource();

        // Step two - get the ApplicationContext the facade wraps
        Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
        appContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext)
                appContextField.get(contextFacade);

        // Step three - get the Context (a tomcat context class) from the facade
        Field contextField = ApplicationContext.class.getDeclaredField("context");
        contextField.setAccessible(true);
        Context context = (Context) contextField.get(applicationContext);

        // Step four - get the Manager. This is the class Tomcat uses to manage sessions
        return context.getManager();
    }

    public static Session getSession(String sessionID) throws IOException {
        return manager.findSession(sessionID);
    }
}

You can add this as a listener in your web.xml and it should work.

Then you could do this to get a session.

Sam Sieber
  • 490
  • 6
  • 12
1

There is no way within the servlet spec, but you could try:

  • manually setting the cookie in the request made by Flash

  • or doing as Taylor L just suggested as I was typing and adding the jsessionid parameter the path of the URI.

Both methods will tie your app to running on a servlet container that behaves like Tomcat; I think most of them do. Both will also require your Flash applet asking the page for its cookies, which may impose a JavaScript dependency.

Andrew Duffy
  • 6,800
  • 2
  • 23
  • 17
0

This is a really good post. One potential issue I see with using the session listener to keep adding sessions to the context is that it can get quite fat depending on the number of concurrent sessions you have. And then all the additional work for the web server configuration for the listener.

So how about this for a much simpler solution. I did implement this and it works quite well. So on the page that loads the flash upload object, store the session and sessionid as a key-value pair in the application object then pass that session id to the upload page as a post parameter. The on the upload page, see if that sessionid is already in the application, is so use that session, otherwise, get the one from the request. Also, then go ahead and remove that key from the application to keep everything clean.

On the swf page:

application.setAttribute(session.getId(), session);

Then on the upload page:

String sessid = request.getAttribute("JSESSIONID");
HttpSession sess = application.getAttribute(sessid) == null ?
        request.getSession() :
        (HttpSession)application.getAttribute(sessid);
application.removeAttribute(sessid);

Very nice solution guys. Thanks for this.

Garfield
  • 21
  • 8
  • 6
    "quite fat"? Have you measured it? Are you familiar with Java? It's just a reference, not a copy of the value... – BalusC Aug 12 '12 at 00:47
  • Oops, you are right. Didn't think that through. Still, easier to implement that the original suggestion. Also, since I have fully tested this code now, I have found the following. If the original poster was referring specifically to SWFUpload, then when you upload more than one file, SWFUpload will post each file individually, not all in one post. So by removing the session form the application object after the first upload, all the others failed. FYI. – Garfield Aug 12 '12 at 12:18