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

Servlets 访问JSR-356@ServerEndpoint的@OnMessage中的ServletContext和HttpSession,servlets,jakarta-ee,websocket,httpsession,java-websocket,Servlets,Jakarta Ee,Websocket,Httpsession,Java Websocket,我需要从@ServerEndpoint内部获取ServletContext,以便找到SpringApplicationContext并查找Bean 目前,我最好的方法是在JNDI命名上下文中绑定该bean,并在端点中查找它。欢迎任何更好的解决方案 我还在寻找一种合理的方法,将servlet的HttpSession与websocket的Session同步。servlet的HttpSession位于JSR-356中,当一个握手请求在一次握手之前发出时,JSR-356就可以使用。而ServletCon

我需要从
@ServerEndpoint
内部获取
ServletContext
,以便找到Spring
ApplicationContext
并查找Bean

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


我还在寻找一种合理的方法,将servlet的
HttpSession
与websocket的
Session

同步。servlet的
HttpSession
位于JSR-356中,当一个握手请求在一次握手之前发出时,JSR-356就可以使用。而
ServletContext
则可通过。一石二鸟

为了捕获握手请求,实现并重写该方法。
HandshakeRequest
在这里作为方法参数提供。您可以将
HttpSession
放入。
EndpointConfig
又可以作为方法参数使用

下面是
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
的属性:

@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),您可能会从的源代码中获得灵感(链接位于showcase的底部)

另见:

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

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


      在这个特定的tomcat版本中,这看起来像一个bug——有人也看到了吗?

      有时我们无法通过上面的
      ServletawarEnfig
      获得BalusC的
      会话,这是因为会话仍然没有创建。由于我们不是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) {
              }
          }
      
      }
      
      公共静态类ServletawarEnfig扩展了ServerEndpointConfig.Configurator{
      @凌驾
      public void modifyHandshake(ServerEndpointConfig配置、HandshakeRequest请求、HandshakeResponse响应){
      试一试{
      Field reqfld=request.getClass().getDeclaredField(“请求”);
      可访问的需求设置(真);
      HttpServletRequest req=(HttpServletRequest)reqfld.get(request);
      ServletContext ctxt=req.getServletContext();
      Map up=config.getUserProperties();
      up.put(“servletContext”,ctxt);
      }捕获(无此字段例外){
      }捕获(安全异常e){
      }捕获(IllegalArgumentException e){
      }捕获(非法访问例外e){
      }
      }
      }
      
      如果希望立即初始化会话,可以调用request.getSession()


      Ref:

      我还试图从websocket端点内部访问ServletContext。尝试在Tomcat中执行此操作。查看是否必须查找bean?你为什么不能注射呢?如果在
      @ServerEndpoint
      注释中使用
      configurator=org.springframework.web.socket.server.endpoint.SpringConfigurator.class
      ,它应该是可注入的。感谢您的解释,特别是关于保持端点不受servlet API依赖关系的提示。请改为将servlet上下文作为用户属性i注入插入spring上下文;这不像是自动连接bean,但它可以工作并解决了我的问题。对于websocket,Omniface示例中的代码似乎使用HTTP请求来增加计数器。我在这里遗漏了什么,不是吗?@Ced:它只是触发推送。如果有人在获取HttpSession时得到null,那么解决方案是:不,这不是bug。它表明httpSession没有被创建。你可以事先自己制作。有关更多信息,请参阅。谢谢-这是我没有想到的解决方案(我以为会话总是由基础设施创建的)。唉,因为我只想将其用于应用程序全局数据,所以我仍然会坚持手工制作的解决方案,因为它现在可以工作。(这不应该作为答案发布)Jetty中的行为是一样的。正如@Kalle指出的那样;只需使用
      HttpFilter
      ServletRequestListener
      并调用
      request.getSession(true)
      -可惜
      握手请求.getHttpSession()上没有类似的重载