之所以sockjs会存在,说得不好听点,就是因为微软是个流氓,现在使用windows 7的系统仍然有近半,而windows 7默认自带的是ie 8,有些会自动更新到ie 9,但是大部分非IT用户其实都不愿意或者不会升级(通常我们做IT的认为很简单的事情,在其他行业的人来看,那就是天书,不要觉得不可能,现实已如此)。

现在言归正传,这里完整的讲下在spring 4.x集成sockjs,以及运行在tomcat 7下时的一些额外注意事项。

spring websocket依赖jar:

        <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。

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 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.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 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.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。

tomcat 7下spring 4.x mvc集成websocket以及sockjs完全参考指南(含nginx/https支持)的更多相关文章

  1. tomcat 7下spring 4.x mvc集成websocket以及sockjs完全参考指南

    之所以sockjs会存在,说得不好听点,就是因为微软是个流氓,现在使用windows 7的系统仍然有近半,而windows 7默认自带的是ie 8,有些会自动更新到ie 9,但是大部分非IT用户其实都 ...

  2. ASP.NET Core 集成 WebSocket

    1. 环境 AspNetCore Web 2.0 (MVC) Windows 10 IIS 10 Express/IIS VS 2017 2.如何配置 在已有的或者新创建的 AspNet Core M ...

  3. spring mvc集成freemarker使用

    freemarker作为视图技术出现的比velocity早,想当年struts风靡一时,freemarker作为视图层也风光了一把.但现在velocity作为后起之秀的轻量级模板引擎,更容易得到青睐. ...

  4. Spring MVC集成Log4j

    以下示例显示如何使用Spring Web MVC框架集成LOG4J.首先使用Eclipse IDE,并按照以下步骤使用Spring Web Framework开发基于动态表单的Web应用程序: 创建一 ...

  5. Spring MVC集成slf4j-logback

    转自: Spring MVC集成slf4j-logback 1.  Spring MVC集成slf4j-log4j 关于slf4j和log4j的相关介绍和用法,网上有很多文章可供参考,但是关于logb ...

  6. spring mvc集成velocity使用

    目前流行的三大页面视图神器是:老牌大哥jsp.后起之秀freemarker和velocity.这里不详细比较这三者的优劣,总体来说,jsp是标配,但后面两个更严格的执行了视图与业务的分离,页面里是不允 ...

  7. 我的Spring Boot学习记录(二):Tomcat Server以及Spring MVC的上下文问题

    Spring Boot版本: 2.0.0.RELEASE 这里需要引入依赖 spring-boot-starter-web 这里有可能有个人的误解,请抱着怀疑态度看. 建议: 感觉自己也会被绕晕,所以 ...

  8. Maven 工程下 Spring MVC 站点配置 (三) C3P0连接池与@Autowired的应用

    Maven 工程下 Spring MVC 站点配置 (一) Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作 前两篇文章主要是对站点和数据库操作配置进行了演示,如果单 ...

  9. Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作

    详细的Spring MVC框架搭配在这个连接中: Maven 工程下 Spring MVC 站点配置 (一) Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作 这篇主 ...

随机推荐

  1. poj3261 Milk Patterns【后缀数组】【二分】

    Farmer John has noticed that the quality of milk given by his cows varies from day to day. On furthe ...

  2. Oracle安装部署之 oracle 11g install linux

    #!/bin/bash#Purpose:Create and config oracle install.#Usage:Log on as the superuser('root') #1.creat ...

  3. 东哥讲义2 - 基于TCP,UDP协议的攻击,分析与防护

    TCP SYN FLOOD 攻击 正常的TCP三次握手过程: 处于SYN FLOOD攻击状态时的三次握手过程: 查看示例:x_syn.c文件,一个实现了自定义mac,ip,tcp头部的syn floo ...

  4. date 命令之日期和秒数转换

    时间转为秒数 date -d "2012-11-12 13:00:00" +"%s" 描述转为日期 date -d@1352692800 +"%Y-% ...

  5. -bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory

    本人使用mac系统,命令行工具使用的iterm2,登录自己的云主机的时候 每次都要提示如下错误 -bash: warning: setlocale: LC_CTYPE: cannot change l ...

  6. FCoin API

    本文介绍FCoin API 介绍 通过了解以下信息,您可以方便的使用 FCoin 提供的 API 来接入 FCoin 交易平台. 认证 执行下面的代码进行用户验证: import fcoin api ...

  7. webpack初入门

    首先利用npm install webpack -g,这里我试过用cnpm安装不成功,不知道是什么回事, webpack 用来压缩打包网站的一些插件和依赖包而存在的, webpack  文件名  打包 ...

  8. flask_SQLAlchemy常用数据类型及列选项

    SQLAlchemy常用数据类型:1. Integer:整形,映射到数据库中是int类型.2. Float:浮点类型,映射到数据库中是float类型.他占据的32位.3. Double:双精度浮点类型 ...

  9. 如何下载Bilibili视频

    方法1: https://www.bilibili.com/video/av25940642 (源网址) https://www.ibilibili.com/video/av25940642 (新网址 ...

  10. linux,centOS,用LNMP搭建wordpress,更新固定连接--全流程

    下午到晚上的时间,买了个linux服务器,用的centOS系统,遇到各种问题! 1.用putty,ssh到vps后,根据网上命令,一步步下载并安装,具体步骤可以看一下网上教程,LNMP.org站上的教 ...