1. 程式人生 > >Springboot-WebSocket初探-獲取HttpSession問題

Springboot-WebSocket初探-獲取HttpSession問題

turn mar 代碼 close ati nwr html5 解決方案 code

換了新工作,第一個任務就是和這個有關,以前沒接觸過,沒辦法,各種度娘、谷哥,大部分都是只言片語,要麽就是特定的配置環境還不貼配置……,踩坑無數, 遂整理成筆記


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


以上代碼純屬個人研究,如果有錯誤的地方,各位留言或者發郵件都可以!

Springboot-WebSocket初探-獲取HttpSession問題