Accessing ServletContext and HttpSession in @OnMessage of a JSR-356 @ServerEndpoint
Asked Answered
W

4

30

I need to get the ServletContext from inside a @ServerEndpoint in order to find Spring ApplicationContext and lookup for a Bean.

For the moment my best approach is to bind that bean in the JNDI naming context and lookup it in the Endpoint. Any better solution is welcome.

I'm also looking for a reasonable way to sync servlet's HttpSession with websocket's Session.

Wentz answered 19/2, 2014 at 17:52 Comment(3)
I am also trying to access ServletContext from inside a websocket endpoint. Trying to do this in Tomcat.Kalmar
See #22880555Pungy
Do you have to lookup the bean? Why can you not inject it? It should be injectable if you use configurator = org.springframework.web.socket.server.endpoint.SpringConfigurator.class in your @ServerEndpoint annotation.Censurable
T
45

The servlet HttpSession is in JSR-356 available by HandshakeRequest#getHttpSession() which is in turn available when a handshake request is made right before @OnOpen of a @ServerEndpoint. The ServletContext is in turn just available via HttpSession#getServletContext(). That's two birds with one stone.

In order to capture the handshake request, implement a ServerEndpointConfig.Configurator and override the modifyHandshake() method. The HandshakeRequest is here available as method argument. You can put the HttpSession into EndpointConfig#getUserProperties(). The EndpointConfig is in turn available as method argument @OnOpen.

Here's a kickoff example of the ServerEndpointConfig.Configurator implementation:

public class ServletAwareConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        config.getUserProperties().put("httpSession", httpSession);
    }

}

Here's how you can use it, note the configurator attribute of the @ServerEndpoint:

@ServerEndpoint(value="/your_socket", configurator=ServletAwareConfig.class)
public class YourSocket {

    private EndpointConfig config;

    @OnOpen
    public void onOpen(Session websocketSession, EndpointConfig config) {
        this.config = config;
    }

    @OnMessage
    public void onMessage(String message) {
        HttpSession httpSession = (HttpSession) config.getUserProperties().get("httpSession");
        ServletContext servletContext = httpSession.getServletContext();
        // ...
    }

}

As a design hint, it's the best to keep your @ServerEndpoint fully free of servlet API dependencies. You'd in the modifyHandshake() implementation better immediately extract exactly that information (usually a mutable Javabean) you need from the servlet session or context and put them in the user properties map instead. If you don't do that, then you should keep in mind that a websocket session can live longer than the HTTP session. So when you still carry around HttpSession into the endpoint, then you may run into IllegalStateException when you try to access it while it's being expired.

In case you happen to have CDI (and perhaps JSF) at hands, you may get inspiration from the source code of OmniFaces <o:socket> (links are at very bottom of showcase).

See also:

Toadeater answered 1/5, 2014 at 10:19 Comment(6)
Thanks for this explanation and especially for the hint to keep Endpoints free of servlet API dependencies.Redivivus
Instead inject the servlet context as user property i inserted The spring context; it's not like to autowire the bean, but it works and solved my issue.Wentz
for websocket it seems like the code in Omniface example is using an HTTP request to increment the counter. What am I missing here , doesn't it defies the point?Manatarms
@Ced: it just triggers the push.Toadeater
If anyone is getting null while getting HttpSession, here is the solution : #20241091Radian
enforcing session for each request may not be acceptable in some situations (cookies disabled, users explicitly refusing any data storage). I've submitted a PR to the API to add getServletContext() to HandshakeRequest: please give it thumbs-up :)Wristband
R
5

Updated code for BalusC's answer, the onOpen method needs to be decorated with @OnOpen. Then there is no need anymore to extend the Endpoint class:

@ServerEndpoint(value="/your_socket", configurator=ServletAwareConfig.class)
public class YourSocket {

    private EndpointConfig config;

    @OnOpen
    public void onOpen(Session websocketSession, EndpointConfig config) {
        this.config = config;
    }

    @OnMessage
    public void onMessage(String message) {
        HttpSession httpSession = (HttpSession) config.getUserProperties().get("httpSession");
        ServletContext servletContext = httpSession.getServletContext();
        // ...
    }

}
Redivivus answered 18/5, 2014 at 12:10 Comment(0)
E
1

I tried out BalusC's answer on Tomcat (Versions 7.0.56 and 8.0.14). On both containers, the modifyHandshake's request parameter does not contain a HttpSession (and thus no servletContext). As I needed the servlet context only to access "global" variables (web-application global, that is), I just stored these variables in an ordinary static field of a holder class. This is inelegant, but it worked.

That ooks like a bug in this specific tomcat versions - has anyone out there also seen this?

Eskill answered 21/11, 2014 at 8:50 Comment(3)
No, it's not a bug. It indicates that a httpSession is simply not created. You could create it yourself beforehand. See #20241091 for more info.Sweep
Thank you - this is a solution I didn't think of (I thought the Session was always created by the infrastructure). Alas, as I wanted to use this only for application global data, I still will stick with the hand crafted solution, as it works now.Eskill
(This shouldn't have been posted as an answer) Behaviour is the same in Jetty. As @Sweep points out; just use a HttpFilter or ServletRequestListener and call request.getSession(true) - shame there's no similar overload on HandshakeRequest.getHttpSession()Robin
A
0

Somtimes we can't get session with above ServletAwareConfig of BalusC, this is because that the session is still not created. since we are not seek for session but servletContext, in tomcat we can do as below:

public static class ServletAwareConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
        try {
            Field reqfld = request.getClass().getDeclaredField("request");
            reqfld.setAccessible(true);
            HttpServletRequest req = (HttpServletRequest) reqfld.get(request);
            ServletContext ctxt = req.getServletContext();
            Map<String, Object> up = config.getUserProperties();
            up.put("servletContext", ctxt);
        } catch (NoSuchFieldException e) {
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
    }

}

if we want init session immediately, we can call request.getSession().

Ref: Websocket - httpSession returns null

Alver answered 29/11, 2018 at 7:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.