Skip to content

SockJS 备选方案

在公共互联网上,受限代理可能会阻止 WebSocket 交互。解决此问题的办法是 WebSocket 模拟:首先尝试使用 WebSocket,如果失败,则回退到基于 HTTP 的技术,这些技术模拟 WebSocket 交互并公开相同的应用层 API。

概述

SockJS 的目标是让应用程序使用 WebSocket API,但在必要时可以回退到非 WebSocket 替代方案,而无需更改应用程序代码。

SockJS 的传输方式分为三大类:

  1. WebSocket
  2. HTTP 流 (Streaming)
  3. HTTP 长轮询 (Long Polling)

SockJS 客户端首先发送 GET /info 从服务器获取基本信息。之后,它必须决定使用哪种协议。如果可能,使用 WebSocket。如果不行,在大多数浏览器中,至少有一种 HTTP 流选项。如果还是不行,则使用 HTTP (长) 轮询。

启用 SockJS

你可以在配置中轻松启用它:

java
@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();
	}
}
kotlin
@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 端点而不使用浏览器。这对于服务器间的双向通信或压力测试非常有用。

java
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

java
http.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()));

如果不设置,某些老旧浏览器的回退机制可能会失效。

Based on Spring Framework.