【问题标题】:Accessing ServletContext and HttpSession in @OnMessage of a JSR-356 @ServerEndpoint在 JSR-356 @ServerEndpoint 的 @OnMessage 中访问 ServletContext 和 HttpSession
【发布时间】:2014-03-20 06:02:55
【问题描述】:

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

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

我也在寻找一种合理的方式来同步 servlet 的 HttpSession 和 websocket 的 Session

【问题讨论】:

  • 我也在尝试从 websocket 端点内部访问 ServletContext。尝试在 Tomcat 中执行此操作。
  • 你需要查找 bean 吗?为什么不能注射?如果您在 @ServerEndpoint 注释中使用 configurator = org.springframework.web.socket.server.endpoint.SpringConfigurator.class,它应该是可注入的。

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


【解决方案1】:

BalusC 上面的ServletAwareConfig 有时我们不能得到session,这是因为会话还没有创建。因为我们不是在寻找会话而是寻找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

【讨论】:

    【解决方案2】:

    servlet HttpSession 在 JSR-356 中由 HandshakeRequest#getHttpSession() 提供,当在 @ServerEndpoint@OnOpen 之前发出握手请求时,它反过来可用。 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);
        }
    
    }
    

    这里是你如何使用它,注意@ServerEndpointconfigurator属性:

    @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),您可以从 OmniFaces &lt;o:socket&gt; 的源代码中获得灵感(链接在展示的最底部)。

    另见:

    【讨论】:

    • 感谢您的解释,尤其感谢您提示 Endpoints 不受 servlet API 依赖的影响。
    • 而是将 servlet 上下文作为用户属性注入我插入的 spring 上下文;它不像自动装配 bean,但它可以工作并解决了我的问题。
    • 对于 websocket,Omniface 示例中的代码似乎使用 HTTP 请求来增加计数器。我在这里错过了什么,这不是违背重点吗?
    • @Ced:它只是触发推送。
    • 如果有人在获取 HttpSession 时得到空值,解决方案如下:stackoverflow.com/questions/20240591/…
    【解决方案3】:

    我在 Tomcat(版本 7.0.56 和 8.0.14)上试用了 BalusC 的答案。在这两个容器上, modifyHandshake 的请求参数不包含 HttpSession(因此不包含 servletContext)。 由于我只需要 servlet 上下文来访问“全局”变量(即 Web 应用程序全局变量),因此我只是将这些变量存储在持有者类的普通静态字段中。这很不雅,但确实有效。

    这看起来像是这个特定 tomcat 版本中的一个错误 - 有没有人也看到过这个?

    【讨论】:

    • 不,这不是错误。它表明一个 httpSession 根本没有创建。您可以事先自己创建它。请参阅stackoverflow.com/questions/20240591/… 了解更多信息。
    • 谢谢 - 这是我没有想到的解决方案(我认为 Session 总是由基础设施创建的)。唉,因为我只想将它用于应用程序全局数据,我仍然会坚持手工制作的解决方案,因为它现在可以工作了。
    • (这不应该作为答案发布) Jetty 中的行为是相同的。正如@Kalle 指出的那样;只需使用HttpFilterServletRequestListener 并调用request.getSession(true) - 遗憾的是HandshakeRequest.getHttpSession() 上没有类似的过载
    【解决方案4】:

    更新了 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();
            // ...
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2016-03-18
      • 1970-01-01
      • 2013-07-29
      • 2015-02-28
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多