tomcat 7下spring 4.x mvc集成websocket以及sockjs徹底參考指南(含nginx/https支持)

之因此sockjs會存在,說得很差聽點,就是由於微軟是個流氓,如今使用windows 7的系統仍然有近半,而windows 7默認自帶的是ie 8,有些會自動更新到ie 9,可是大部分非IT用戶其實都不肯意或者不會升級(一般咱們作IT的認爲很簡單的事情,在其餘行業的人來看,那就是天書,不要以爲不可能,現實已如此)。javascript

如今言歸正傳,這裏完整的講下在spring 4.x集成sockjs,以及運行在tomcat 7下時的一些額外注意事項。html

spring websocket依賴jar:html5

        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope> <!-- 注意,scope必須爲provided,不然runtime會衝突,若是使用tomcat 8,還須要將TOMCAT_HOME/lib下的javax.websocket-api.jar一併刪除 -->
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.2.8.RELEASE</version>
        </dependency>

除非使用STOMP協議,不然不須要依賴spring-messaging。java

spring經過兩種模式支持websocket,一種是經過原生websocket規範的ws://協議訪問(我的認爲若是肯定只用標準websocket訪問,還不如tomcat升級到8.x(tomcat 8原生支持JSR 356註解),spring的大量封裝畢竟增長了很多額外負載);另外一種則是經過sockjs(也就是js)訪問,二者目前暫時沒法作到兼容。nginx

先完整說明第一種:git

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

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

三、spring支持websocket總共分爲四個小步驟,handler、interceptor、config、web.xml,基本上能夠認爲spring mvc的翻版。spring

3.一、建立WebSocketHandler,spring支持兩種方式,一種是實現org.springframework.web.socket.WebSocketHandler接口,另一種則是繼承TextWebSocketHandler或BinaryWebSocketHandler(如今大部分模板式框架或者插件一般都是在提供了API的基礎上提供了抽象類,把一些能統一的工做提早預置了,以便應用只須要關心業務,咱們本身公司的中間件框架不少也是用這個模式實現了)。apache

/**
 * 
 */
package com.ld.net.spider.demo.ws;

/**
 * @author zhjh256@163.com
 * {@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.二、建立攔截器

/**
 * 
 */
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 zhjh256@163.com 
 * {@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.三、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"/>

上述須要注意的是,一、spring javadoc的說明是默認狀況下,容許全部來源訪問,但咱們跑下來發現不配置allowed-origins的話老是報403錯誤。

二、sockjs是不容許有後綴的,不然將沒法匹配,後面會專門講到。

3.四、web.xml配置

在web.xml中增長*.ws映射便可(若是原來不是/*的話),以下:

    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>*.ws</url-pattern>
    </servlet-mapping>

上述配置完成以後,就能夠經過標準的websocket接口進行訪問了,以下所示。

四、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。
相關文章
相關標籤/搜索