SockJS 备选方案
在公共互联网上,受限代理可能会阻止 WebSocket 交互。解决此问题的办法是 WebSocket 模拟:首先尝试使用 WebSocket,如果失败,则回退到基于 HTTP 的技术,这些技术模拟 WebSocket 交互并公开相同的应用层 API。
概述
SockJS 的目标是让应用程序使用 WebSocket API,但在必要时可以回退到非 WebSocket 替代方案,而无需更改应用程序代码。
SockJS 的传输方式分为三大类:
- WebSocket
- HTTP 流 (Streaming)
- HTTP 长轮询 (Long Polling)
SockJS 客户端首先发送 GET /info 从服务器获取基本信息。之后,它必须决定使用哪种协议。如果可能,使用 WebSocket。如果不行,在大多数浏览器中,至少有一种 HTTP 流选项。如果还是不行,则使用 HTTP (长) 轮询。
启用 SockJS
你可以在配置中轻松启用它:
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}@Configuration
@EnableWebSocket
class WebSocketConfiguration : WebSocketConfigurer {
override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS()
}
@Bean
fun myHandler(): WebSocketHandler {
return MyHandler()
}
}心跳 (Heartbeats)
SockJS 协议要求服务器发送心跳消息,以防止代理认为连接已挂起。Spring 的 SockJS 配置有一个名为 heartbeatTime 的属性,默认值为 25 秒(如果没有其他消息传输)。
SockJS 客户端 (SockJsClient)
Spring 提供了一个 SockJS Java 客户端,用于连接远程 SockJS 端点而不使用浏览器。这对于服务器间的双向通信或压力测试非常有用。
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");补充教学
1. 为什么需要 SockJS?
即便是在 2024 年,仍有一些过时的企业防火墙、防病毒软件或负载均衡器不支持 Upgrade 标头。SockJS 就像是为 WebSocket 穿上了一层“补丁”,确保你的实时应用在各种网络环境下都能 100% 可用。
2. URL 结构的奥秘
SockJS 的请求通常包含 {server-id}/{session-id}。这不仅是为了区分连接,更重要的是在集群环境下配合“粘性会话”(Sticky Sessions) 确保后续的 HTTP 请求能落到同一台服务器上。
3. 与 Spring Security 配合
如果你使用了 Spring Security,启用 SockJS(尤其是使用了 iframe 模式时)可能需要调整 X-Frame-Options:
http.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()));如果不设置,某些老旧浏览器的回退机制可能会失效。