spring-websocket接入文档.md 7.8 KB

spring-websocket接入文档

本次是第二次接入spring-websocket到项目。此次接入遇到的问题与上一次完全不一样。

本次接入要点的前端说明

前端会判断当前浏览器是否支付websocket,支持的话,使用原生服务连接; 不支持的话采用sockjs库。

服务器端要点说明

为了区分前端是否采用原生库的服务创建websocket,所以配置了两个Mapping-url.

前端相关

  1. 前端首先判断当前使用平台的http协议类型(http OR https)。对应websocket的ws 和 wss。
  2. 判断当前浏览器的websocket原生库类型(其实是浏览器分类),支持的用原生库,不支持的用sockjs库。
  3. 编写websocket数据通信事件的默认实现。
  • 前端的代码

    var ws = null, ws_uri = "/sba/ws/srv"; // /sba是spring-mvc的拦截前缀。
    /** websocket */
    function initWebSocket(){
        var ws_pre = "ws://" + window.location.host;
        var protocolStr = document.location.protocol;
        if(protocolStr == "https:") {
            ws_pre = "wss://" + window.location.host;
        }
        //判断当前浏览器是否支持WebSocket。浏览器的原生ws组件,没有心跳机制,默认是5分钟超时,具体需要参照http代理的超时配置。
        if ('WebSocket' in window) {
            ws = new WebSocket(ws_pre + ws_uri);
        } 
        else if ('MozWebSocket' in window) {
            ws = new MozWebSocket(ws_pre + ws_uri);
        } 
        else { // 有心跳协议,可以一直保持连接。
            ws_uri = "/sba/ws/sockjs";
            if(protocolStr == "https:") {
                ws = new SockJS("https://"+window.location.host + ws_uri);
            } else {
                ws = new SockJS("http://"+window.location.host + ws_uri);
            }
        }
        ws.onopen = function () {
            alert("WS服务已开启。");
        };
      //这个事件是接受后端传过来的数据
        ws.onmessage = function (event) {
            //根据业务逻辑解析数据
            var data = JSON.parse(event.data);
            alert(data);
        };
        ws.onclose = function (event) {
            alert("WS服务已关闭.");
        };
        ws.onerror = function (event) {
            alert("WS服务出现异常:" + +event.data);
        };
    }
    

服务器端相关

  1. 配置spring-websocket,接入spring的上下文(将webskocket配置文件import到sping 的配置文件里)。
  2. 实现spring-websocket握手和协议升级的拦截器(HttpSessionHandshakeInterceptor)。将httpsession的用户标识与websocket会话关联.
  3. 实现spring-websocket的消息处理器(TextWebSocketHandler)。
  4. 如果使用了权限控制,应对 /ws/srv 与 /ws/sockjs 进行访问控制。
  5. com.jahelai.ws 是项目的前缀包名。

spring-websocket的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:websocket="http://www.springframework.org/schema/websocket"
   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">

<!-- 支持原生websocket -->
<websocket:handlers allowed-origins="*">
    <websocket:mapping path="/ws/srv" handler="wsMessageHandler"/>
    <websocket:handshake-interceptors>
        <bean class="com.jahelai.ws.websocket.WsHandshakeInterceptor"/>
    </websocket:handshake-interceptors>
</websocket:handlers>

<!-- 支持sockjs -->
<websocket:handlers allowed-origins="*">
    <websocket:mapping path="/ws/sockjs" handler="wsMessageHandler"/>
    <websocket:handshake-interceptors>
        <bean class="com.jahelai.ws.websocket.WsHandshakeInterceptor"/>
    </websocket:handshake-interceptors>
    <websocket:sockjs  />
</websocket:handlers> 

<bean id="wsMessageHandler" class="com.jahelai.ws.websocket.WsMesssageHandler"/>

</beans>

WsHandshakeInterceptor

import java.util.Map;

import javax.servlet.http.HttpSession;

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

public class WsHandshakeInterceptor extends HttpSessionHandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {
        ///获取请求参数,首先我们要获取HttpServletRequest对象才能获取请求参数;当ServerHttpRequset的层次结构打开后其子类可以获取到我们想要的http对象,那么就简单了。
        //我这里是把获取的请求数据绑定到session的map对象中(attributes)
        ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
        HttpSession session = servletRequest.getServletRequest().getSession(false);
        if (session != null) {
            String userName = (String) session.getAttribute("SESSION_USERNAME");
            if(userName == null){
                userName = "WEBSOCKET_USERNAME_IS_NULL";
            }
            // httpsession的用户标识与websocket的WebSocketSession建立关联。
            attributes.put("username", session.getAttribute("username"));
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request,
                               ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception ex) {
        super.afterHandshake(request, response, wsHandler, ex);
    }

}

WsMesssageHandler

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

/**
 * ws服务的消息处理器。
 * 接入spring-websocket的步骤:
 * 1。配置ws的url,开发消息处理器,协议升级处理器
 * 2。将配置文件加入到spring的上下文。
 * 3。 spring-security开放ws的url校验
 * 4。开发前端的连接,消息处理。
 * @author jahe.lai
 *
 */
public class WsMesssageHandler extends TextWebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        // username=session.getAttributes().get("username").toString()
        // 将WebSocketSession与用户的标识关联,以供调用。可以Map<username, WebSocketSession>
        WsSessionManager.addOnline(session);    
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        // Todo 你对交互消息的处理写在这里。
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        WsSessionManager.remove(session);
    }

}

nginx代理websocket.

见: http://nginx.org/en/docs/http/websocket.html