tomcat 7下spring 4.x mvc集成websocket以及sockjs完全参考指南(含nginx/https支持)
之所以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支持)的更多相关文章
- tomcat 7下spring 4.x mvc集成websocket以及sockjs完全参考指南
之所以sockjs会存在,说得不好听点,就是因为微软是个流氓,现在使用windows 7的系统仍然有近半,而windows 7默认自带的是ie 8,有些会自动更新到ie 9,但是大部分非IT用户其实都 ...
- ASP.NET Core 集成 WebSocket
1. 环境 AspNetCore Web 2.0 (MVC) Windows 10 IIS 10 Express/IIS VS 2017 2.如何配置 在已有的或者新创建的 AspNet Core M ...
- spring mvc集成freemarker使用
freemarker作为视图技术出现的比velocity早,想当年struts风靡一时,freemarker作为视图层也风光了一把.但现在velocity作为后起之秀的轻量级模板引擎,更容易得到青睐. ...
- Spring MVC集成Log4j
以下示例显示如何使用Spring Web MVC框架集成LOG4J.首先使用Eclipse IDE,并按照以下步骤使用Spring Web Framework开发基于动态表单的Web应用程序: 创建一 ...
- Spring MVC集成slf4j-logback
转自: Spring MVC集成slf4j-logback 1. Spring MVC集成slf4j-log4j 关于slf4j和log4j的相关介绍和用法,网上有很多文章可供参考,但是关于logb ...
- spring mvc集成velocity使用
目前流行的三大页面视图神器是:老牌大哥jsp.后起之秀freemarker和velocity.这里不详细比较这三者的优劣,总体来说,jsp是标配,但后面两个更严格的执行了视图与业务的分离,页面里是不允 ...
- 我的Spring Boot学习记录(二):Tomcat Server以及Spring MVC的上下文问题
Spring Boot版本: 2.0.0.RELEASE 这里需要引入依赖 spring-boot-starter-web 这里有可能有个人的误解,请抱着怀疑态度看. 建议: 感觉自己也会被绕晕,所以 ...
- Maven 工程下 Spring MVC 站点配置 (三) C3P0连接池与@Autowired的应用
Maven 工程下 Spring MVC 站点配置 (一) Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作 前两篇文章主要是对站点和数据库操作配置进行了演示,如果单 ...
- Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作
详细的Spring MVC框架搭配在这个连接中: Maven 工程下 Spring MVC 站点配置 (一) Maven 工程下 Spring MVC 站点配置 (二) Mybatis数据操作 这篇主 ...
随机推荐
- Docker 学习应用篇之三: Docker的简单实用
安装完Docker之后,我们就可以简单的使用Docker,来体会Docker的用处. 首先看下Docker的常用命令,都是我在实用Docker的时候用到的命令: docker常用命令: $ docke ...
- iOS核心动画の摘记
- array string mysql IN LIKE
$ids = $_POST['ids']; $str = implode(',',$ids); $str = '('.$str.')'; $db_region->region_del($str) ...
- Java中子类是否可以继承父类的static变量和方法而呈现多态特性
静态方法 通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明为static的方法有以下几条限制: 它们仅能调用其他的static 方法. 它 ...
- mysql主从服务器的配置
使用mysql主从复制的好处有: 1.采用主从服务器这种架构,稳定性得以提升.如果主服务器发生故障,我们可以使用从服务器来提供服务. 2.在主从服务器上分开处理用户的请求,可以提升数据处理效率. 3. ...
- ChinaTest测试感悟
这次去北京参加ChinaTest大会,听了各位大师和同行的心得和感悟,收获颇多.很喜欢这样的大会,可以听到测试的各种声音各种观点.当没有对错时,需要思考的就是怎样采取最适合当前环境的策略.言归正传,谈 ...
- hibernate注解(三)1+N问题
一.什么时候会遇到1+N的问题? 前提:Hibernate默认表与表的关联方法是fetch="select",不是fetch="join",这都是为了懒加载而准 ...
- Spring使用AspectJ注解和XML配置实现AOP
本文演示的是Spring中使用AspectJ注解和XML配置两种方式实现AOP 下面是使用AspectJ注解实现AOP的Java Project首先是位于classpath下的applicationC ...
- LINUX中的ACL
一. 为什么要使用ACL先让我们来简单地复习一下Linux的文件权限. 在 linux下,对一个文件(或者资源)可以进行操作的对象被分为三类: file owner(文件 的拥有者),group(组, ...
- Python模块:配置文件解析器configparser
版权声明:本文为博主皮皮http://blog.csdn.net/pipisorry原创文章,未经博主同意不得转载. https://blog.csdn.net/pipisorry/article/d ...