• Springboot-WebSocket获取HttpSession问题


    换了新工作,第一个任务就是和这个有关,以前没接触过,没办法,各种度娘、谷哥,大部分都是只言片语,要么就是特定的配置环境还不贴配置……,踩坑无数, 遂整理成笔记


    WebSocket协议

    WebSocket是一种在单个TCP连接上进行全双工通讯的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

    WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

    STOMP协议

    STOMP是面向文本的消息传送协议。STOMP客户端与支持STOMP协议的消息代理进行通信。STOMP使用不同的命令,如连接,发送,订阅,断开等进行通信。

    具体参考:官方介绍

    SockJS

    SockJS是一个JavaScript库,提供跨浏览器JavaScript的API,创建了一个低延迟、全双工的浏览器和web服务器之间通信通道


    以上内容出自维基百科和百度百科

    环境配置

    1. SpringBoot2.0全家桶
    2. 特别说明一下,现在很多服务器都支持websocket,我这次写代码用的是SpringBoot内置的tomcat

    应用场景

    1. 如果只是单纯的配置一个建立连接发送消息,并不难,具体客户端编写参考这个 菜鸟教程-websocket
    2. 我需要解决的问题是:当客户端和服务器建立连接时,将登录的用户信息从httpSession中取出,这个问题的难点在于,websocket的请求和http请求完全不相关,所以没有办法直接获取HttpSession

    实施思路

    1. 服务器端的编码实现,具体有两套方案
      1. 使用继承的方式,这种方式,我在看Spring官方介绍文档时貌似也是这种方式实现的
      2. 使用注解的方式:很蛋疼的是我们的项目使用的是这种方式
    2. 具体思路
      1. 虽然websocket的请求和http请求完全不相关,但是如果基于注解的话,EndPoint支持读取一个配置
      2. 我当时的想法就是在配置中拦截或者获取HttpSession,事实证明思路是正确的,但是当我按照这个思路去百度谷歌发现获取的一律都是null,然后我就崩溃了

    具体编码实现

    1. 只贴核心代码,完整项目在本文的底部,我放在了github上

    2. 先上核心的服务器端消息处理类

      /**
       * WebSocket主要的消息类
       * @author 侯叶飞
       */
       //onfigurator = WebsocketConfig.class 该属性就是我上面提到我们可以自己配置的东西
      @ServerEndpoint(value = "/api/websocket", configurator = WebsocketConfig.class)
      @Component
      @Slf4j
      public class WebSocket {
      	/*每个浏览器连接都会有一个新的会话对象*/
      	private Session session;
      	/*用来存储每个会话的session,静态的不会被实例化*/
      	private static CopyOnWriteArraySet<WebSocket> webSocketSets = new CopyOnWriteArraySet<>();
      	/**
      	 * 主要用来监听连接建立,config用来获取WebsocketConfig中的配置信息
      	 * @param session
      	 * @param config
      	 */
      	@OnOpen
      	public void onOpen(Session session, EndpointConfig config) {
      		log.info("config:{}", config.getUserProperties().get("name"));
      		log.info("session:{}", config.getUserProperties().get("sessionid"));
      		this.session = session;
      		webSocketSets.add(this);
      		log.info("【websocket消息】有新的连接, 总数:{}", webSocketSets.size());
      	}
      
      	@OnClose
      	public void onClose() {
      		webSocketSets.remove(this);
      		log.info("【websocket消息】连接断开, 总数:{}", webSocketSets.size());
      	}
      
      	@OnError
      	public void onError(Throwable e, Session session) {
      		webSocketSets.remove(this);
      		log.info("【websocket消息】连接出错或未关闭socket:" + e.getMessage());
      
      	}
      
      	@OnMessage
      	public void onMessage(String message, Session session) {
      		for(WebSocket ws:webSocketSets){
      			ws.session.getAsyncRemote().sendText("广播:"+message);
      		}
      		log.info("【websocket消息】收到客户端发来的消息:{}", message);
      	}
      }
      
      
    3. 下面代码就是核心的配置类

      /**
       * 主要的配置类
       *  本类必须要继承Configurator,因为@ServerEndpoint注解中的config属性只接收这个类型
       * @author 侯叶飞
       *
       */
      @Configuration
      @Slf4j
      public class WebsocketConfig extends ServerEndpointConfig.Configurator {
      
      	private static final String HttpSession = null;
      	/* 修改握手,就是在握手协议建立之前修改其中携带的内容 */
      	@Override
      	public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
      		/*如果没有监听器,那么这里获取到的HttpSession是null*/
      		StandardSessionFacade ssf = (StandardSessionFacade) request.getHttpSession();
      		if (ssf != null) {
      			HttpSession session = (HttpSession) request.getHttpSession();
      			sec.getUserProperties().put("sessionid", session);
      			log.info("获取到的SessionID:{}",session.getId());
      		}
      		sec.getUserProperties().put("name", "小强");
      		super.modifyHandshake(sec, request, response);
      	}
      	@Bean
      	public ServerEndpointExporter serverEndpointExporter() {
      	    //这个对象说一下,貌似只有服务器是tomcat的时候才需要配置,具体我没有研究
      		return new ServerEndpointExporter();
      	}
      }
      
      1. 仅仅有上面的配置获取的肯定是null,至于原因,网上说法不一,我也不确定,解决方案如下
      /**
       * 监听器类:主要任务是用ServletRequest将我们的HttpSession携带过去
       * @author 侯叶飞
       */
      @Component //此注解千万千万不要忘记,它的主要作用就是将这个监听器纳入到Spring容器中进行管理,相当于注册监听吧
      @Slf4j
      public class RequestListener implements ServletRequestListener {  
          @Override  
          public void requestInitialized(ServletRequestEvent sre)  {  
          	//将所有request请求都携带上httpSession  
              HttpSession session = ((HttpServletRequest) sre.getServletRequest()).getSession();  
              log.info("将所有request请求都携带上httpSession {}",session.getId());
          }  
          public RequestListener() {}  
        
          @Override  
          public void requestDestroyed(ServletRequestEvent arg0)  {}  
      }  
      
    4. 此外在我谷歌的过程中有博客提到需要添加一个WebListener的注解,结果发现基于springboot的话,如果不加也不影响


    GitHub项目地址:Demo


    以上代码纯属个人研究,如果有错误的地方,各位留言或者发邮件都可以!

  • 相关阅读:
    while(~scanf(..))为什么可以这样写
    【 HDU3294 】Girls' research (Manacher)
    【 HDU2966 】In case of failure(KD-Tree)
    【 HDU 1538 】A Puzzle for Pirates (海盗博弈论)
    【 HDU 2177 】取(2堆)石子游戏 (威佐夫博弈)
    【 HDU 4936 】Rainbow Island (hash + 高斯消元)
    【 HDU1081 】 To The Max (最大子矩阵和)
    Partition Numbers的计算
    【 HDU
    【 Gym 101116K 】Mixing Bowls(dfs)
  • 原文地址:https://www.cnblogs.com/coder163/p/8605645.html
Copyright © 2020-2023  润新知