访问JSR-356 @ServerEndpoint的@OnMessage中的ServletContext和HttpSession

时间:2014-02-19 17:52:06

标签: servlets java-ee websocket httpsession java-websocket

我需要从ServletContext中获取@ServerEndpoint才能找到Spring ApplicationContext并查找Bean。

目前,我最好的方法是在JNDI命名上下文中绑定该bean并在Endpoint中查找它。欢迎任何更好的解决方案。

我也在寻找一种合理的方法来将servlet的HttpSession与websocket的Session同步。

4 个答案:

答案 0 :(得分:38)

HandshakeRequest#getHttpSession()中的servlet HttpSession位于JSR-356中,当@OnOpen @ServerEndpoint之前发出握手请求时,它就可用。 ServletContext依次可通过HttpSession#getServletContext()获取。那是一石二鸟。

要捕获握手请求,请实施ServerEndpointConfig.Configurator并覆盖modifyHandshake()方法。 HandshakeRequest在这里可用作方法参数。您可以将HttpSession放入EndpointConfig#getUserProperties()EndpointConfig又可用作方法参数@OnOpen

以下是ServerEndpointConfig.Configurator实施的启动示例:

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);
    }

}

以下是如何使用它,请注意@ServerEndpoint的{​​{3}}属性:

@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();
        // ...
    }

}

作为设计提示,最好保持@ServerEndpoint完全不受servlet API依赖的影响。您在modifyHandshake()实现中更好地立即从servlet会话或上下文中提取您需要的 信息(通常是可变的Javabean),并将它们放在用户属性映射中。如果你不这样做,那么你应该记住,websocket会话可以比HTTP会话更长寿。因此,如果您仍然将HttpSession带入端点,那么当您尝试访问该端点时,您可能会遇到IllegalStateException

如果您碰巧有CDI(也许还有JSF),您可以从configurator的源代码中获取灵感(链接位于展示的最底部)。

另见:

答案 1 :(得分:5)

更新了BalusC的答案代码,onOpen方法需要用@OnOpen进行修饰。然后就不再需要扩展Endpoint类了:

@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();
        // ...
    }

}

答案 2 :(得分:0)

我在Tomcat(版本7.0.56和8.0.14)上尝试了BalusC的答案。在两个容器上,modifyHandshake的请求参数不包含HttpSession(因此不包含servletContext)。 因为我需要servlet上下文才能访问" global"变量(web-application global,即),我只是将这些变量存储在holder类的普通静态字段中。这是不优雅的,但它确实有效。

这就像这个特定的tomcat版本中的一个bug - 有没有人在那里看过这个?

答案 3 :(得分:0)

有时我们无法获得BalusC的session以上的ServletAwareConfig,这是因为仍未创建会话。由于我们不是在寻求会话而是servletContext,因此在tomcat中,我们可以执行以下操作:

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) {
        }
    }

}

如果我们要立即发起会话,可以调用request.getSession()。

参考:Websocket - httpSession returns null