在公网上,一些代理可能会阻止WebSocket交互,有的代理会配置不传递Upgrade
头,有的会断开空闲的连接。
可以使用仿真来解决这个问题,例如:首先尝试使用WebSocket连接,失败后是用基于HTTP的技术,模仿WebSocket的交互,并且暴露相同的API。
Spring使用了SockJS协议来支持。
SockJS的目标是使用WebSocket的API,当WebSocket不可用的时候使用非WebSocket的选项,而无需修改代码。 SockJS包括:
SockJS是设计在浏览器中使用的。查询SockJS Client来查看SockJS支持的浏览器。支持三种方式传输数据:WebSocket,HTTP Streaming,HTTP长轮询。 当SockJS发送 GET /info 请求的时候,服务端需要决定使用哪种传输格式,首先会检查WebSocket,如果不行则使用HTTP Streaming,如果还是不行就使用HTTP的长轮询。
URL 的格式如下:
http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
WebSocket 只需要一个HTTP请求用来握手,剩下的都只需要单个链接即可完成交互 HTTP 需要更多的请求。例如:Ajax/XHR streaming 依赖于一个长时间运行的server到client的请求。长轮询则每次都会进行请求查询。 SockJS简化了消息帧,例如:server发送 o 表示(open),如果25秒没有消息发送 h 表示 heartbeat 心跳。c 表示close等。
Java配置:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
XML 配置:
<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:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:sockjs/>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
在浏览器中直接使用socket-client
如果使用了iframe,那么需要在http 响应头设置 X-Frame-Options
为:SAMEORIGIN,ALLOW-FROM。
Java 配置:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS()
.setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
}
// ...
}
SockJS协议要求服务端发送心跳信息,防止代理挂起链接。SockJS配置heartbeatTime
来设置心跳的频率。没有其他消息发送的时候,默认心跳时间为25秒。25秒的设置是依据IETF recommendation的推荐设置。
Spring SockJS 同样也可以配置TaskScheduler
管理心跳任务。
HTTP Streaming和长轮询模式要求请求比之前持续的时间更长。在Servlet容器中,Servlet 3 异步特性可以支持这个功能。但是Servlet API在客户端断开连接的时候并没有发送消息。当像一个已经关闭的链接写入响应的时候,Servlet 容器会抛出异常。
SockJS使用CORS来支持跨域问题。CORS头是自动加的,所以如果已经在SpringMVC配置了的话就可以完全跳过这部分。 SockJS 支持的头:
SockJS提供了Java客户端可以不使用浏览器就可以访问服务器。这样在公网上两台服务器双向通信的时候非常的方便。例如代理可能会组织使用WebSocket。同样,SockJS的Java客户端也很适合做测试,例如测试同时访问最多用户数。 SockJS的Java客户端支持websocket,xhr-streaming,xhr-polling传输协议。 示例如下:
List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
配置:
@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/sockjs").withSockJS()
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000)
.setDisconnectDelay(30 * 1000);
}
// ...
}