接着上文IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE搭建基于Tomcat-7.0.68的WebSocket应用
上文的最后我们说到,WebSocket
是需要定时心跳的,否则会在一段时间后自动断开连接,而更重要的是,不是所有的浏览器都支持WebSocket
,早期的IE 10
之前的版本就是不支持的,而这一部分的设备其实是不算少的,而sockjs
的出现,恰恰好来解决了这个问题。对于不支持WebSocket
的浏览器,sockjs
使用了多种方式来兼容这种情况,包括使用长轮询等方式,Spring
更是内建支持这种方式。
下面我们看如何在上篇文章的基础上,增加对于sockjs
的支持。
首先是STOMP
的文档官网地址 http://stomp.github.io/
代码的地址为https://github.com/jmesnil/stomp-websocket,项目下面的lib/stomp.js
就是我们想要的文件。也可以本站下载stomp.js.zip
接下来sockjs
的代码地址https://github.com/sockjs/sockjs-client,项目下面的dist/sockjs-1.1.1.js
就是我们想要的文件。也可以本站下载sockjs-1.1.1.js.zip
接下来我们把下载到的文件放到我们工程目录下面的web
->resources
->javascript
目录下面,如下图:
接下来,添加我们需要的com.fasterxml.jackson.core:jackson-annotations:2.8.1
,com.fasterxml.jackson.core:jackson-core:2.8.1
,com.fasterxml.jackson.core:jackson-databind:2.8.1
这三个jar
包,增加的方式参照上一篇中对于javax.servlet:javax.servlet-api:3.1.0
的操作方法。与上一篇的操作不同的是,这次添加的三个jar
包,都要放到编译完成后的War
包中。最后的结果如下图:
下面,我们开始进行代码的操作,我们在上篇文章中的src
->Tools
->WebSocket
中新增两个源代码文件SockJsController.java
,WebJsSocketConfig.java
.如下图:
其中的代码如下:
SockJsController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package Tools.WebSocket; ? import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; ? @Controller public class SockJsController { ? ????????@MessageMapping("/hello") ????????@SendTo("/hello/subscribe") /*貌似这个名字可以随意的,主要用在stomp.subscribe时候的名字*/ ????????public String Hello(String message) throws Exception { ????????????return new String("Hello"); ????????} } |
WebJsSocketConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package Tools.WebSocket; ? import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; ? ? @Configuration @EnableWebSocketMessageBroker public class WebJsSocketConfig??extends AbstractWebSocketMessageBrokerConfigurer { ????@Override ????public void configureMessageBroker(MessageBrokerRegistry config) { ????????config.setApplicationDestinationPrefixes("/webSocketServer"); ????} ? ????@Override ????public void registerStompEndpoints(StompEndpointRegistry registry) { ????????registry.addEndpoint("/hello").setAllowedOrigins("*").withSockJS(); ????} } |
然后修改WebSocket.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
曾道人吉数<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> ????<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> ????<title>WebSocket/SockJS Echo Sample (Adapted from Tomcat's echo sample) ????type="text/css"> ????????#connect-container { ????????????float: left; ????????????width: 400px ????????} ? ????????#connect-container div { ????????????padding: 5px; ????????} ? ????????#console-container { ????????????float: left; ????????????margin-left: 15px; ????????????width: 400px; ????????} ? ????????#console { ????????????border: 1px solid #CCCCCC; ????????????border-right-color: #999999; ????????????border-bottom-color: #999999; ????????????height: 170px; ????????????overflow-y: scroll; ????????????padding: 5px; ????????????width: 100%; ????????} ? ????????#console p { ????????????padding: 0; ????????????margin: 0; ????????} ???? ? ????src="./resources/javascript/sockjs-1.1.1.js"> ????src="./resources/javascript/stomp.js"> ? ????type="text/javascript"> ????????var ws = null; ????????var url = null; ????????var transports = []; ????????var stompClient = null; ? ????????function setConnected(connected) { ????????????document.getElementById('connect').disabled = connected; ????????????document.getElementById('disconnect').disabled = !connected; ????????????document.getElementById('echo').disabled = !connected; ????????} ? ????????function connect() { ????????????if (!url) { ????????????????alert('Select whether to use W3C WebSocket or SockJS'); ????????????????return; ????????????} ? ????????????ws = (url.indexOf('sockjs') != -1) ? ????????????????????new SockJS(url,undefined, {transports: transports}) : new WebSocket(url); ????????????if((url.indexOf('sockjs') != -1)) { ????????????????stompClient = Stomp.over(ws); ????????????????stompClient.connect({}, function(frame) { ????????????????????setConnected(true); ????????????????????log('Connected: ' + frame); ????????????????????stompClient.subscribe('/hello/subscribe', function(message){ ????????????????????????log(message.body); ????????????????????}); ????????????????}); ????????????}else { ????????????????ws.onopen = function () { ????????????????????setConnected(true); ????????????????????log('Info: connection opened.'); ????????????????}; ? ????????????????ws.onmessage = function (event) { ????????????????????log('Received: ' + event.data); ????????????????}; ? ????????????????ws.onclose = function (event) { ????????????????????setConnected(false); ????????????????????log('Info: connection closed.'); ????????????????????log(event); ????????????????}; ????????????} ????????} ? ????????function disconnect() { ????????????if (ws != null) { ????????????????ws.close(); ????????????????ws = null; ????????????} ????????????stompClient = null; ????????????setConnected(false); ????????} ? ????????function echo() { ????????????if(stompClient != null){ ????????????????var message = document.getElementById('message').value; ????????????????stompClient.send("/webSocketServer/hello", {}, message); ????????????????log('Sent: ' + message); ????????????}else { ????????????????if (ws != null) { ????????????????????var message = document.getElementById('message').value; ????????????????????log('Sent: ' + message); ????????????????????ws.send(message); ????????????????} else { ????????????????????alert('connection not established, please connect.'); ????????????????} ????????????} ????????} ? ????????function updateUrl(urlPath) { ????????????if (urlPath.indexOf('sockjs') != -1) { ????????????????url = urlPath; ????????????????document.getElementById('sockJsTransportSelect').style.visibility = 'visible'; ????????????} ????????????else { ????????????????if (window.location.protocol == 'http:') { ????????????????????url = 'ws://' + window.location.host + urlPath; ????????????????} else { ????????????????????url = 'wss://' + window.location.host + urlPath; ????????????????} ????????????????document.getElementById('sockJsTransportSelect').style.visibility = 'hidden'; ????????????} ????????} ? ????????function updateTransport(transport) { ????????????transports = (transport == 'all') ???[] : [transport]; ????????} ? ????????function log(message) { ????????????var console = document.getElementById('console'); ????????????var p = document.createElement('p'); ????????????p.style.wordWrap = 'break-word'; ????????????p.appendChild(document.createTextNode(message)); ????????????console.appendChild(p); ????????????while (console.childNodes.length > 25) { ????????????????console.removeChild(console.firstChild); ????????????} ????????????console.scrollTop = console.scrollHeight; ????????} ???? t support Javascript! Websockets ????rely on Javascript being enabled. Please enable ????Javascript and reload this page!</h2></noscript> <div> ????<div id="connect-container"> ????????<input id="radio1" type="radio" name="group1" onclick="updateUrl('/webSocketServer');"> ????????<label for="radio1">W3C WebSocket</label> ????????<br> ????????<input id="radio2" type="radio" name="group1" onclick="updateUrl('/webSocketServer/sockjs/hello');"> ????????<label for="radio2">SockJS</label> ???????? ????????????SockJS transport:pan> ????????????<select onchange="updateTransport(this.value)"> ????????????????<option value="all">all</option> ????????????????<option value="websocket">websocket</option> ????????????????<option value="xhr-polling">xhr-polling</option> ????????????????<option value="jsonp-polling">jsonp-polling</option> ????????????????<option value="xhr-streaming">xhr-streaming</option> ????????????????<option value="iframe-eventsource">iframe-eventsource</option> ????????????????<option value="iframe-htmlfile">iframe-htmlfile</option> ????????????elect> ????????</div> ????????<div> ????????????<button id="connect" onclick="connect();">Connect</button> ????????????<button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button> ????????</div> ????????<div> ????????????<textarea id="message" style="width: 350px">Here is a message!</textarea> ????????</div> ????????<div> ????????????<button id="echo" onclick="echo();" disabled="disabled">Echo message</button> ????????</div> ????</div> ????<div id="console-container"> ????????<div id="console"></div> ????</div> </div> ? </body> </html> |
最后,我们修改web
->WEB-INF
->web.xml
,在其中增加
1 2 3 4 |
修改后的最终结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
xml version="1.0" encoding="UTF-8"?> ???????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ???????? xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" ???????? version="3.1"> ???? ???????? ???????? ???? ???? ???????? ???????? ???????? ???? ???? ???????? ???????? ???? ???? ???????? ???? ???? ???????? ???????? ???????? ???????? ???? ???? ???????? ???????? ???? ???? ???????? ???????? ???? ???? ???????? ???????? ???? |
参考链接
?
- 如何在Spring中配置Websocket
- 26. WebSocket Support
- Spring 4 WebSocket + SockJS + STOMP + Tomcat Example
- Spring+Websocket实现消息的推送
- 基于spring websocket+sockjs实现的长连接请求
IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE搭建基于Tomcat-7.0.68的WebSocket应用
先参照 IntelliJ IDEA 2016.1建立Strut2工程并使用Tomcat调试建立新的工程,一步一步操作,包括最后引用Spring
框架部分。
经过上面的操作,Spring-WebSocket
的包应该已经被默认引入了,如下图所示:
这就意味着我们已经不需要再进行过多的额外配置了。
接下来,我们在src
->Tools
下面新建一个WebSocket
的目录,里面创建三个Java
文件。如下图:
每个文件中的代码如下:
SystemWebSocketHandler.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
package Tools.WebSocket; ? import java.io.IOException; import java.util.ArrayList; import java.util.Date; ? import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; ? public class SystemWebSocketHandler extends TextWebSocketHandler { ? ????private static final ArrayList ? ? ????@Override ????public void afterConnectionEstablished(WebSocketSession session) throws Exception { ????????System.out.println("ConnectionEstablished"); ????????users.add(session); ? ????????session.sendMessage(new TextMessage("connect")); ????????session.sendMessage(new TextMessage("new_msg")); ? ????} ? ????@Override ????public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { ????????System.out.println("handleMessage" + message.toString()); ????????session.sendMessage(new TextMessage(new Date() + "")); ????} ? ????@Override ????public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { ????????if(session.isOpen()){ ????????????session.close(); ????????} ????????users.remove(session); ????} ? ????@Override ????public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { ????????users.remove(session); ????} ? ????@Override ????public boolean supportsPartialMessages() { ????????return false; ????} ? ????/** ???? * 给所有在线用户发送消息 ???? * ???? * @param message ???? */ ????public void sendMessageToUsers(TextMessage message) { ????????for (WebSocketSession user : users) { ????????????try { ????????????????if (user.isOpen()) { ????????????????????user.sendMessage(message); ????????????????} ????????????} catch (IOException e) { ????????????????e.printStackTrace(); ????????????} ????????} ????} } |
WebSocketConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package Tools.WebSocket; ? import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; ? @Configuration @EnableWebMvc @EnableWebSocket public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer { ? ????@Override ????public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { ????????registry.addHandler(systemWebSocketHandler(), "/webSocketServer").addInterceptors(new WebSocketHandshakeInterceptor()); ????} ? ????@Bean ????public WebSocketHandler systemWebSocketHandler() { ????????return new SystemWebSocketHandler(); ????} } |
WebSocketHandshakeInterceptor.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package Tools.WebSocket; ? import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; ? import java.util.Map; ? public class WebSocketHandshakeInterceptor implements HandshakeInterceptor { ????@Override ????public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception { ????????System.out.println("beforeHandshake"); ????????return true; ????} ? ????@Override ????public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { ????????System.out.println("afterHandshake"); ????} } |
由于我们使用了Struts2
导致我们的网络请求优先被Struts2
的拦截器拦截,而Struts2
又处理不了websocket
请求,结果直接返回了404
错误。因此我们需要替换掉默认的在web.xml
中定义的Struts2
的拦截器,要求Struts2
不处理websocket
请求。
我们在src
->Tools
下面新建一个Filter
的目录,下面创建一个名为StrutsPrepareAndExecuteFilterEx.java
的源代码文件,如下图:
具体的代码如下:
StrutsPrepareAndExecuteFilterEx.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package Tools.Filter; ? import org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter; ? import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; ? public class StrutsPrepareAndExecuteFilterEx extends StrutsPrepareAndExecuteFilter { ? ????@Override ????public void doFilter(ServletRequest req, ServletResponse resp, ???????????????????????? FilterChain filter) throws IOException, ServletException { ???????? ????????String requestURI = ((HttpServletRequest) req).getRequestURI(); ????????if(requestURI != null && (requestURI.contains("/webSocketServer")) || requestURI.startsWith("ws://") || requestURI.contains("/mspjapi")) ????????????filter.doFilter(req, resp); ????????else ????????????super.doFilter(req, resp, filter); ????} } |
这时候的代码是无法编译通过的,原因是依赖的javax
的Jar
包不存在。此时,我们需要手工引入javax.servlet:javax.servlet-api:3.1.0
这个Jar
包。如下图:
还要注意,刚刚添加进入的javax.servlet:javax.servlet-api:3.1.0
这个Jar
包,我们只在编译期间使用,在运行时候,使用Tomcat
自己实现的那个同名Jar
包。也就是这个包是个Provided
,而不是Compile
关系,具体如下图:
接下来,修改web.xml
指定Struts2
的拦截器为我们定义的拦截器
1 2 3 4 5 6 7 8 |
修改为
1 2 3 4 5 6 7 8 |
经过上面的修改后,依旧是没办法进行网络访问的,原因是web.xml
中的Spring
拦截器并没有拦截WebSocket
的数据请求。
1 2 3 4 5 6 7 8 9 10 |
修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/webSocketServer/*</url-pattern> </servlet-mapping> |
注意增加的
1 |
下一步,配置Spring
的配置文件web
->WEB-INF
->dispatcher-servlet.xml
增加配置信息类的扫描目录包含我们刚刚创建的src
->Tools
->WebSocket
的目录(缺少这一步会导致我们通过注解实现的配置信息类没有被自动加载,导致无法访问),修改后的内容如下:
1 2 3 4 5 6 7 8 |
xml version="1.0" encoding="UTF-8"?> ?????? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ?????? xmlns:context="http://www.springframework.org/schema/context" ?????? xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> ???? ???? |
最后,调用的页面的代码
WebSocket.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> ??<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> ??<title>WebSocket</title> ??type="text/css"> ? ????#console-container { ??????margin-left: 15px; ??????margin-right: 15px; ??????padding: 5px; ??????width: 95%; ????} ? ????#console { ??????border: 1px solid #CCCCCC; ??????border-right-color: #999999; ??????border-bottom-color: #999999; ??????height: 300px; ??????overflow-y: scroll; ??????padding: 5px; ??????width: 100%; ????} ? ????#console p { ??????padding: 0; ??????margin: 0; ????} ?? ? ??type="text/javascript"> ????var url = 'ws://' + window.location.host + '/webSocketServer'; ????var ws = new WebSocket(url); ? ????ws.onopen = function () { ??????log('Info: connection opened.'); ????}; ? ????ws.onmessage = function (event) { ??????log('Received: ' + event.data); ????}; ? ????ws.onclose = function (event) { ??????log('Info: connection closed.'); ??????log(event); ????}; ? ????function log(message) { ??????var console = document.getElementById('console'); ??????var p = document.createElement('p'); ??????p.style.wordWrap = 'break-word'; ??????p.appendChild(document.createTextNode(message)); ??????console.appendChild(p); ??????while (console.childNodes.length > 1000) { ????????console.removeChild(console.firstChild); ??????} ??????console.scrollTop = console.scrollHeight; ????} ?? </head> <body> ? <div> ??<div id="console-container"> ????<div id="console"></div> ??</div> </div> ? </body> </html> |
最后的客户端显示效果如下图所示:
注意:如此创建的WebSocket
是会在两三分钟后主动断开连接的,原因在于WebSocket
需要周期性的发送心跳报文来维持连接。后续我们会尝试使用sockjs
来实现自动发送心跳的逻辑。
具体的sockjs的接入方法,参考IntelliJ IDEA 2016.2使用Spring 4.3.1.RELEASE,sockjs-1.1.1,stomp-1.2搭建基于Tomcat-7.0.68的WebSocket应用
FireFox中跟踪调试WebSocket通信报文
FireFox
中跟踪调试一般都是使用FireBug
进行页面的调试,但是目前的FireBug
还没办法跟踪分析WebSocket
通信报文,这就需要其他插件来跟踪调试了。
现在比较方便的是使用WebSocket Monitor
这个插件来跟踪,具体的操作步骤如下所示: