1. 程式人生 > >關於spring支援websocket的學習

關於spring支援websocket的學習

參考路徑:https://www.cnblogs.com/zhjh256/p/6052102.html

spring通過兩種模式支援websocket,一種是通過原生websocket規範的ws://協議訪問(個人認為如果確定只用標準websocket訪問,還不如tomcat升級到8.x(tomcat 8原生支援JSR 356註解),spring的大量封裝畢竟增加了不少額外負載);另一種則是通過sockjs(也就是js)訪問,兩者目前暫時無法做到相容。


先完整說明第一種:


1、搭建spring mvc環境,這一點假設讀者已知;


2、pom.xml中引入上面兩個jar包;


3、spring支援websocket總共分為四個小步驟,handler、interceptor、config、web.xml,基本上可以認為spring mvc的翻版。


3.1、建立WebSocketHandler,spring支援兩種方式,一種是實現org.springframework.web.socket.WebSocketHandler介面,另外一種則是繼承TextWebSocketHandler或BinaryWebSocketHandler(現在大部分模板式框架或者外掛通常都是在提供了API的基礎上提供了抽象類,把一些能統一的工作提前預置了,以便應用只需要關心業務,我們自己公司的中介軟體框架很多也是用這個模式實現了)。


複製程式碼
/**
 * 
 */
package com.ld.net.spider.demo.ws;


/**
 * @author

[email protected]
 * {@link} http://www.cnblogs.com/zhjh256
 */
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;


public class DemoWSHandler implements WebSocketHandler {  
  
    @Override  
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {  
        System.out.println("connect to the websocket success......");  
        session.sendMessage(new TextMessage("Server:connected OK!"));  
    }  
  
    @Override  
    public void handleMessage(WebSocketSession wss, WebSocketMessage<?> wsm) throws Exception {  
        TextMessage returnMessage = new TextMessage(wsm.getPayload()  
                + " received at server");  
        System.out.println(wss.getHandshakeHeaders().getFirst("Cookie"));  
        wss.sendMessage(returnMessage);  
    }  
  
    @Override
    public void handleTransportError(WebSocketSession wss, Throwable thrwbl) throws Exception {  
        if(wss.isOpen()){  
            wss.close();  
        }  
       System.out.println("websocket connection closed......");  
    }  
  
    @Override  
    public void afterConnectionClosed(WebSocketSession wss, CloseStatus cs) throws Exception {  
        System.out.println("websocket connection closed......");  
    }  
  
    @Override  
    public boolean supportsPartialMessages() {  
        return false;  
    }
}
複製程式碼
3.2、建立攔截器


複製程式碼
/**
 * 
 */
package com.ld.net.spider.demo.ws;


import java.util.Map;


import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;


/**
 * @author
[email protected]
 
 * {@link} http://www.cnblogs.com/zhjh256
 */
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {


    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {


        // 解決The extension [x-webkit-deflate-frame] is not supported問題
        if (request.getHeaders().containsKey("Sec-WebSocket-Extensions")) {
            request.getHeaders().set("Sec-WebSocket-Extensions",
                    "permessage-deflate");
        }


        System.out.println("Before Handshake");
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }


    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception ex) {
        System.out.println("After Handshake");
        super.afterHandshake(request, response, wsHandler, ex);
    }
}
複製程式碼
3.3、bean配置


複製程式碼
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:util="http://www.springframework.org/schema/util"   
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/mongo       
http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd   
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://activemq.apache.org/schema/core 
http://activemq.apache.org/schema/core/activemq-core.xsd
http://www.springframework.org/schema/jms 
http://www.springframework.org/schema/jms/spring-jms.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
    <websocket:handlers allowed-origins="*">
        <websocket:mapping path="/springws/websocket.ws" handler="demoWSHandler"/>
         <websocket:handshake-interceptors>
            <bean class="com.ld.net.spider.demo.ws.HandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>


    <bean id="demoWSHandler" class="com.ld.net.spider.demo.ws.DemoWSHandler"/>
複製程式碼
上述需要注意的是,1、spring javadoc的說明是預設情況下,允許所有來源訪問,但我們跑下來發現不配置allowed-origins的話總是報403錯誤。


2、sockjs是不允許有後綴的,否則將無法匹配,後面會專門講到。


3.4、web.xml配置


在web.xml中增加*.ws對映即可(如果原來不是/*的話),如下:


    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>*.ws</url-pattern>
    </servlet-mapping>
上述配置完成之後,就可以通過標準的websocket介面進行訪問了,如下所示。


4、websocket客戶端


複製程式碼
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Web Socket JavaScript Echo Client</title>
  <script src="http://cdn.jsdelivr.net/sockjs/1/sockjs.min.js"></script>
  <script language="javascript" type="text/javascript">
    var echo_websocket;
    function init() {
      output = document.getElementById("output");
    }


    function send_echo() {
      var wsUri = "ws://localhost:28080/springws/websocket.ws";
      writeToScreen("Connecting to " + wsUri);
      echo_websocket = new WebSocket(wsUri);
      echo_websocket.onopen = function (evt) {
        writeToScreen("Connected !");
        doSend(textID.value);
      };
      echo_websocket.onmessage = function (evt) {
        writeToScreen("Received message: " + evt.data);
        echo_websocket.close();
      };
      echo_websocket.onerror = function (evt) {
        writeToScreen('<span style="color: red;">ERROR:</span> '
          + evt.data);
        echo_websocket.close();
      };
    }
    function doSend(message) {
      echo_websocket.send(message);
      writeToScreen("Sent message: " + message);
    }
    function writeToScreen(message) {
      var pre = document.createElement("p");
      pre.style.wordWrap = "break-word";
      pre.innerHTML = message;
      output.appendChild(pre);
    }
    window.addEventListener("load", init, false);
  </script>
</head>
<body>
<h1>Echo Server</h1>
<div style="text-align: left;">
  <form action="">
    <input onclick="send_echo()" value="傳送socket請求" type="button">
    <input id="textID" name="message" value="Hello World, Web Sockets" type="text">
    <br>
  </form>
</div>
<div id="output"></div>
</body>
</html>
複製程式碼
上述前後端均配置完成後,基於標準websocket api的搭建就完成了,試試吧。。


現在再來看下sockjs的配置。


spring對sockjs和websocket支援的差別在於配置,web.xml,以及客戶端,服務實現無差別。


3.3需要調整為如下:


複製程式碼
    <websocket:handlers>
        <websocket:mapping path="/springws/websocket" handler="demoWSHandler"/>
         <websocket:handshake-interceptors>
            <bean class="com.ld.net.spider.demo.ws.HandshakeInterceptor"/>
        </websocket:handshake-interceptors>
        <websocket:sockjs/>
    </websocket:handlers>


    <bean id="demoWSHandler" class="com.ld.net.spider.demo.ws.DemoWSHandler"/>
複製程式碼
3.4 一定要有到/xxx/*的對映,簡單的可以直接/*,如下所示:


    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
上述配置完成後,就sockjs直接性的支援而言,就可以沒有問題了。


客戶端則為如下:


複製程式碼
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Web Socket JavaScript Echo Client</title>
  <script src="http://cdn.jsdelivr.net/sockjs/1/sockjs.min.js"></script>
  <script language="javascript" type="text/javascript">
    var echo_websocket;
    function init() {
      output = document.getElementById("output");
    }
    function send_echo() {
      echo_websocket = new SockJS("http://localhost:28080/springws/websocket") ;   //初始化 websocket


      echo_websocket.onopen = function () {
        console.log('Info: connection opened.');
      };


      echo_websocket.onmessage = function (event) {
        console.log('Received: ' + event.data); //處理服務端返回訊息
      };


      echo_websocket.onclose = function (event) {
        console.log('Info: connection closed.');
        console.log(event);
      };


      ws.send("abcabc");
    }
    
    function doSend(message) {
      echo_websocket.send(message);
      writeToScreen("Sent message: " + message);
    }
    function writeToScreen(message) {
      var pre = document.createElement("p");
      pre.style.wordWrap = "break-word";
      pre.innerHTML = message;
      output.appendChild(pre);
    }
    window.addEventListener("load", init, false);
  </script>
</head>
<body>
<h1>Echo Server</h1>
<div style="text-align: left;">
  <form action="">
    <input onclick="send_echo()" value="send websocket request" type="button">
    <input id="textID" name="message" value="Hello world, Web Sockets" type="text">
    <br>
  </form>
</div>
<div id="output"></div>
</body>
</html>
複製程式碼
上述配置完成後,如果訪問沒有CORS異常的話,基於sockjs的websocket就完成了。試試吧。。。


典型錯誤及原因、解決方法如下:


Error during WebSocket handshake: Unexpected response code: 404
檢查web.xml servlet-mapping包含了到websocket路徑的對映,比如如果請求不含字尾,就必須包含/*的對映


 


WebSocket connection to 'ws://localhost:8080/springwebsocket/websocket' failed: Error during WebSocket handshake: Unexpected response code: 403
<websocket:handlers allowed-origins="*">,javadoc說明預設代表所有站點,實際好像並不是,所以需要配置*


 


sockjs啟用
啟用sockjs後,直接用websocket協議訪問會報
html5ws.html:15 WebSocket connection to 'ws://localhost:28080/springws/websocket.ws' failed: Error during WebSocket handshake: Unexpected response code: 200


 


直接改為sockjs後,會報
XMLHttpRequest cannot load http://localhost:28080/springws/websocket.ws/info?t=1478758042205. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:63342' is therefore not allowed access. The response had HTTP status code 404.
需要在web.xml中配置CORS過濾(注意,如果apache有自帶的類庫,建議直接使用,不要隨意聽信網上的自己實現過濾器的搞法,這些庫一天的執行次數可能就比自己寫的執行到淘汰還多,所以幾乎常見的問題都不可能遺漏):


複製程式碼
    <filter>
        <filter-name>CorsFilter</filter-name>
        <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
        <init-param>
            <param-name>cors.allowed.methods</param-name>
            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
        </init-param>
        <init-param>
            <param-name>cors.allowed.headers</param-name>
            <!--注意,若你的應用中不只有這些檔案頭,則需要將你應用中需要傳的檔案頭也加上; 例如:我的應用中需要在header中傳token,所以這裡的值就應該是下面的配置,在原有基礎上將token加上,否則,應用就不會被允許呼叫 
                <param-value>token,Access-Control-Allow-Origin,Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> -->
            <param-value>Access-Control-Allow-Origin,Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
        </init-param>
        <async-supported>true</async-supported>
    </filter>
    <filter-mapping>
        <filter-name>CorsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
複製程式碼
使用sockjs還有一點需要注意的是:
因為sockjs會自動在url之後增加/info?t=XXXX等路徑,如果這裡url-pattern攔截類似於*.ws這種帶字尾的就找不到對映,比如想通過sockjs訪問地址/springws/websocket.ws,但是sockjs框架會先訪問/springws/websocket.ws/info這個地址,但是這個地址又不可被spring框架識別,所以導致不可用。


到此為止,tomcat 7下spring 4.x mvc整合websocket以及sockjs的配置就全部介紹完成。


今天看群裡一個訊息的時候,提到HA時一臺伺服器掛掉的問題,這就回到socket的思路了,客戶端也得加上個定時的心跳邏輯,萬一某臺伺服器掛了或者斷網可以failover並自動重新建立連線。在我們的業務中,可靠性這一點是很關鍵的。


預設情況下,ws://走的時候http協議,即使主頁面是通過https訪問,此時會出現連線時異常"[blocked] The page at 'https://localhost:8443/endpoint-wss/index.jsp' was loaded over HTTPS, but ran insecure content from 'ws://localhost:8080/endpoint-wss/websocket': this content should also be loaded over HTTPS.Uncaught SecurityError: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.",此時需要使用如下連線:


websocket:wss://localhost:8080/endpoint-wss/websocket


sockJS:https://localhost:8080/endpoint-wss/socketJS


 


配置nginx支援websocket,預設情況下,nginx不支援自動升級至websocket協議,否則js中會出現連線時異常"Error during WebSocket handshake: Unexpected response code: 400",需在恰當的位置加上如下設定:


server {
    listen 8020;
    location / {
        proxy_pass http://websocket;
        proxy_set_header Host $host:8020; #注意, 原host必須配置, 否則傳遞給後臺的值是websocket,埠如果沒有輸入的話會是80, 這會導致連線失敗
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}
upstream websocket {
    server 192.168.100.10:8081;
}


經上述調整後,websocket就可以同時支援通過nginx代理的https協議,結合MQ機制,可以做到B端實時推送、B端/C端實時通訊。
nginx的https(自動跳轉http->https)+nginx+websocket的完整配置可參考http://www.cnblogs.com/zhjh256/p/6262620.html

相關推薦

關於spring支援websocket學習

參考路徑:https://www.cnblogs.com/zhjh256/p/6052102.htmlspring通過兩種模式支援websocket,一種是通過原生websocket規範的ws://協議訪問(個人認為如果確定只用標準websocket訪問,還不如tomcat升

Maven學習記錄(三)--實戰引入Spring支援

一.建立專案 maven專案在IDEA下建立是相當容易 然後給定專案座標,確定即可 二.完善專案目錄結構 IDEA建立完專案結構和標準的maven專案有些差異,這個時候就需要我們手動調整一下 更改前結構 更改後 三.引入sp

spring boot 基礎學習

sdn 快速入門 itl 基礎 使用 ref 第一篇 快速 .com 構建微服務:Spring boot 入門篇 http://www.cnblogs.com/ityouknow/p/5662753.html SpringBoot入門系列:第一篇 Hello World h

spring源碼學習spring的IOC容器之BeanFactoryPostProcessor接口學習

時機 process roc sta 自動 註解 lis nbsp factor 【一】org.springframework.beans.factory.config.BeanFactoryPostProcessor接口==>該接口實現方法的執行時機:該接口void

spring源碼學習spring的AOP面向切面編程的實現解析

內部 遠程調用 關註 add aps 文件 uem 連接 row 一:Advice(通知)(1)定義在連接點做什麽,為切面增強提供織入接口。在spring aop中主要描述圍繞方法調用而註入的切面行為。(2)spring定義了幾個時刻織入增強行為的接口??=>org.

Spring 源碼學習(一)

pla 註入 websocket web前端 tex 異常 messaging ping 完整 設計伊始 Spring 是為解決企業級應用開發的復雜性而設計,她可以做很多事。但歸根到底支撐Spring的僅僅是少許的基本理念,而所有地這些的基本理念都能可以追溯到一個

spring源碼學習spring的遠程調用實現源碼分析

數據 編碼方式 ria date 技術 color nbsp mvc err 【一】spring的遠程調用提供的基礎類 (1)org.springframework.remoting.support.RemotingSupport ===>spring提供實現的遠程調

WebSocket學習記錄

實時推送 tomcat 使用 數據 tony ebs goeasy log gac 參考資料: Java後端WebSocket的Tomcat實現 java WebSocket的實現以及Spring WebSocket 基於Java的WebSocket推送 基於GoEas

Spring.Net框架學習錯誤集錦1

javaee java ee oda 錯誤集錦 tex img 理解 font sso 最近IoC框架非常火熱,所以就學習了非常流行的IOC框架之一spring.NET,遇到如下問題: Error creating context ‘spring.root‘: Could

Spring Framework基礎學習

正常 應用程序 指南 actor 行為 contex modules all es2017 Spring Framework基礎學習 Core support for dependency injection,transaction management,web ap

spring源碼學習spring配置的事務方式是REQUIRED,但業務層拋出TransactionRequiredException異常問題

uncaught easy lap api tee class odi lose gre (1)spring拋出異常的點:org.springframework.orm.jpa.EntityManagerFactoryUtils public static DataAc

spring boot框架學習學前掌握之重要註解(1)-java配置方式

spring boot   本節主要內容:  1:重點註解介紹  2:使用重點註解環境搭建  聲明:  本文是《凱哥陪你學系列-框架學習之spring boot框架學習》中學前掌握之重要註解(1)  java配置是spring 4.x推薦的撇嘴方式。可以完全代替xml配置。  1:重點註解  @con

spring boot框架學習之重要註解3註解方式讀取外部資源配置文件

凱哥java java註解 本節主要內容:1:是用非註解方式怎麽獲取配置文件中的配置項2:使用註解實戰獲取外部properties文件配置項聲明:本文是《凱哥陪你學系列-框架學習之spring boot框架學習》中spring boot框架學習學前掌握之重要註解(3)-通過註解方式讀取外部資源配置文件

spring boot框架學習學前掌握之重要註解(4)-通過註解方式讀取外部資源配置文件2

spring boot kaigejava 凱哥java本節主要內容:1:思考問題:怎麽讀取多個配置文件,如果文件不存在怎麽辦2:配置數據庫連接池聲明:本文是《凱哥陪你學系列-框架學習之spring boot框架學習》中spring boot框架學習學前掌握之重要註解(4)-通過註解方式讀取外部資源配置文件2

spring boot框架學習3-spring boot核心(2)

spring boot kaigejava 凱哥java本節主要:1:怎麽手動關閉不需要的配置?2:修改自定義啟動的banner3:全局配置文件本文是《凱哥陪你學系列-框架學習之spring boot框架學習》中第三篇 spring boot框架學習3-spring boot核心(2)聲明:本文系凱哥Java

spring boot框架學習4-spring boot核心(3)

spring boot kaigejava 凱哥java本節主要:1:spring boot 為我們提供的 starter pom 都有哪些2:怎麽添加xml配置文件3:日誌相關本文是《凱哥陪你學系列-框架學習之spring boot框架學習》中第四篇 spring boot框架學習4-spring boot

spring boot框架學習6-spring boot的web開發(2)

凱哥spring boot spring boot框架 本章節主要內容:通過前面的學習,我們了解並快速完成了spring boot第一個應用。spring boot企業級框架,那麽spring boot怎麽讀取靜態資源?如js文件夾,css文件以及png/jpg圖片呢?怎麽自定義消息轉換器呢?怎麽自定

spring boot框架學習7-spring boot的web開發(3)-自定義消息轉換器

spring boot 凱哥java本章節主要內容:通過前面的學習,我們了解並快速完成了spring boot第一個應用。spring boot企業級框架,那麽spring boot怎麽讀取靜態資源?如js文件夾,css文件以及png/jpg圖片呢?怎麽自定義消息轉換器呢?怎麽自定義spring mvc的配置

spring boot框架學習8-【幹貨】spring boot的web開發(4)-自定義攔截器處理權限

凱哥spring boot spring boot框架 本章節主要內容:通過前面的學習,我們了解並快速完成了spring boot第一個應用。spring boot企業級框架,那麽spring boot怎麽讀取靜態資源?如js文件夾,css文件以及png/jpg圖片呢?怎麽自定義消息轉換器呢?怎麽自定

spring boot框架學習9-spring boot的web開發(5)-錯誤解決及跳轉頁面

凱哥spring boot spring boot框架 本章節主要內容:通過前面的學習,我們了解並快速完成了spring boot第一個應用。spring boot企業級框架,那麽spring boot怎麽讀取靜態資源?如js文件夾,css文件以及png/jpg圖片呢?怎麽自定義消息轉換器呢?怎麽自定