此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.1.10Spring中文文档

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.1.10Spring中文文档

在公共 Internet 上,您无法控制的限制性代理可能会阻止 WebSocket 交互,因为它们未配置为传递标头,或者 因为它们关闭了看似空闲的长期连接。UpgradeSpring中文文档

此问题的解决方案是 WebSocket 仿真,即尝试使用 WebSocket 首先,然后回退到模拟 WebSocket 的基于 HTTP 的技术 交互并公开相同的应用程序级 API。Spring中文文档

在 Servlet 堆栈上,Spring Framework 提供服务器(和客户端)支持 用于 SockJS 协议。Spring中文文档

概述

SockJS 的目标是让应用程序使用 WebSocket API,但回退到 在运行时需要时使用非 WebSocket 替代方案,而无需 更改应用程序代码。Spring中文文档

SockJS 由以下部分组成:Spring中文文档

SockJS 专为在浏览器中使用而设计。它使用多种技术 支持多种浏览器版本。 有关 SockJS 传输类型和浏览器的完整列表,请参阅 SockJS 客户端页面。运输 分为三大类:WebSocket、HTTP Streaming 和 HTTP Long Polling。 有关这些类别的概述,请参阅此博客文章Spring中文文档

SockJS 客户端首先将发送到 从服务器获取基本信息。之后,它必须决定什么运输 使用。如果可能,则使用 WebSocket。如果没有,在大多数浏览器中, 至少有一个 HTTP 流选项。如果不是,则 HTTP(长) 使用轮询。GET /infoSpring中文文档

所有传输请求都具有以下 URL 结构:Spring中文文档

https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

WebSocket 传输只需要一个 HTTP 请求即可执行 WebSocket 握手。 此后的所有消息都在该套接字上交换。Spring中文文档

HTTP 传输需要更多请求。例如,Ajax/XHR 流依赖于 一个长时间运行的服务器到客户端消息请求和其他 HTTP POST 客户端到服务器消息的请求。长轮询是类似的,只是它 在每次服务器到客户端发送后结束当前请求。Spring中文文档

SockJS 添加了最小的消息框架。例如,服务器最初发送字母(“打开”帧),消息作为(JSON编码数组)发送,如果没有消息流,则发送字母(“心跳”帧) 25 秒(默认情况下),以及字母(“关闭”帧)以关闭会话。oa["message1","message2"]hcSpring中文文档

要了解更多信息,请在浏览器中运行示例并观察 HTTP 请求。 SockJS 客户端允许修复传输列表,因此可以 一次查看一次的每次传输。SockJS 客户端还提供了一个调试标志, 这在浏览器控制台中启用有用的消息。在服务器端,您可以启用 的日志记录。 有关更多详细信息,请参阅 SockJS 协议旁白测试TRACEorg.springframework.web.socketSpring中文文档

启用 SockJS

您可以通过 Java 配置启用 SockJS,如以下示例所示:Spring中文文档

@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 配置:Spring中文文档

<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
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/websocket
		https://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>

前面的示例用于 Spring MVC 应用程序,应包含在 DispatcherServlet 的配置。但是,Spring 的 WebSocket SockJS 支持不依赖于 Spring MVC。相对简单 在 SockJsHttpRequestHandler 的帮助下集成到其他 HTTP 服务环境中。Spring中文文档

在浏览器端,应用程序可以使用 sockjs-client(版本 1.0.x)。它 模拟 W3C WebSocket API 并与服务器通信以选择最佳 transport 选项,具体取决于运行它的浏览器。参见 sockjs-client 页面和 浏览器支持的传输类型。客户端还提供了几个 配置选项 — 例如,指定要包含的传输。Spring中文文档

IE 8 和 9

Internet Explorer 8 和 9 仍在使用中。他们是 拥有 SockJS 的一个关键原因。本节介绍重要的 有关在这些浏览器中运行的注意事项。Spring中文文档

SockJS 客户端使用 Microsoft 的 XDomainRequest 在 IE 8 和 9 中支持 Ajax/XHR 流。 这适用于跨域,但不支持发送 Cookie。 Cookie 通常对于 Java 应用程序至关重要。 但是,由于 SockJS 客户端可以与许多服务器一起使用 类型(不仅仅是 Java 类型),它需要知道 cookie 是否重要。 如果是这样,SockJS 客户端更喜欢 Ajax/XHR 进行流式处理。否则,它 依赖于基于 iframe 的技术。Spring中文文档

来自 SockJS 客户端的第一个请求是 可能影响客户选择交通工具的信息。 其中一个细节是服务器应用程序是否依赖于 cookie (例如,用于身份验证目的或使用粘性会话进行群集)。 Spring 的 SockJS 支持包括一个名为 的属性。 默认情况下,它处于启用状态,因为大多数 Java 应用程序都依赖于 cookie。如果你的应用程序不需要它,你可以关闭这个选项, 然后,SockJS 客户端应该在 IE 8 和 9 中进行选择。/infosessionCookieNeededJSESSIONIDxdr-streamingSpring中文文档

如果您确实使用基于 iframe 的传输,请记住 可以指示浏览器通过以下方式阻止在给定页面上使用 IFrames 将 HTTP 响应标头设置为 、 或 。这用于防止点击劫持X-Frame-OptionsDENYSAMEORIGINALLOW-FROM <origin>Spring中文文档

Spring Security 3.2+ 支持在每个 响应。默认情况下,Spring Security Java 配置将其设置为 。 在 3.2 中,Spring Security XML 命名空间默认不设置该标头 但可以配置为这样做。将来,它可能会默认设置它。X-Frame-OptionsDENYSpring中文文档

有关如何配置 标头的设置。您还可以查看 gh-2718 以获取其他背景信息。X-Frame-OptionsSpring中文文档

如果您的应用程序添加了响应标头(它应该如此! 并且依赖于基于 iframe 的传输,您需要将标头值设置为 或 。春季 SockJS 支持还需要知道 SockJS 客户端的位置,因为它是加载的 从 iframe.默认情况下,iframe 设置为下载 SockJS 客户端 从 CDN 位置。最好将此选项配置为使用 与应用程序来自同一源的 URL。X-Frame-OptionsSAMEORIGINALLOW-FROM <origin>Spring中文文档

以下示例显示了如何在 Java 配置中执行此操作:Spring中文文档

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/portfolio").withSockJS()
				.setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
	}

	// ...

}

XML 命名空间通过元素提供类似的选项。<websocket:sockjs>Spring中文文档

在初始开发期间,请启用 SockJS 客户端模式,以防止 浏览器缓存 SockJS 请求(如 iframe),否则会 被缓存。有关如何启用它的详细信息,请参阅 SockJS 客户端页面。devel

Spring Security 3.2+ 支持在每个 响应。默认情况下,Spring Security Java 配置将其设置为 。 在 3.2 中,Spring Security XML 命名空间默认不设置该标头 但可以配置为这样做。将来,它可能会默认设置它。X-Frame-OptionsDENYSpring中文文档

有关如何配置 标头的设置。您还可以查看 gh-2718 以获取其他背景信息。X-Frame-OptionsSpring中文文档

在初始开发期间,请启用 SockJS 客户端模式,以防止 浏览器缓存 SockJS 请求(如 iframe),否则会 被缓存。有关如何启用它的详细信息,请参阅 SockJS 客户端页面。devel

心跳

SockJS 协议要求服务器发送心跳消息以排除代理 从断定连接挂起。Spring SockJS 配置有一个属性 调用,可用于自定义频率。默认情况下,一个 心跳在 25 秒后发送,假设没有发送其他消息 连接。此 25 秒值符合以下针对公共 Internet 应用程序的 IETF 建议heartbeatTimeSpring中文文档

在 WebSocket 和 SockJS 上使用 STOMP 时,如果 STOMP 客户端和服务器协商 要交换的心跳,则禁用 SockJS 心跳。

Spring SockJS 支持还允许您将 计划检测信号任务。任务计划程序由线程池支持, 使用基于可用处理器数量的默认设置。你 应考虑根据您的特定需求自定义设置。TaskSchedulerSpring中文文档

在 WebSocket 和 SockJS 上使用 STOMP 时,如果 STOMP 客户端和服务器协商 要交换的心跳,则禁用 SockJS 心跳。

客户端断开连接

HTTP 流式处理和 HTTP 长轮询 SockJS 传输需要保持连接 营业时间比平时更长。有关这些技术的概述,请参阅此博客文章Spring中文文档

在 Servlet 容器中,这是通过 Servlet 3 异步支持完成的,该支持 允许退出 Servlet 容器线程、处理请求并继续 写入来自另一个线程的响应。Spring中文文档

一个具体问题是 Servlet API 不为客户机提供通知 那已经消失了。请参见eclipse-ee4j/servlet-api#44。 但是,Servlet 容器会在后续尝试写入时引发异常 到响应。由于 Spring 的 SockJS 服务支持服务器发送的心跳(每 默认为 25 秒),这意味着通常会在其中检测到客户端断开连接 时间段(或更早,如果消息发送频率更高)。Spring中文文档

因此,网络 I/O 故障可能会发生,因为客户端已断开连接,这 可以使用不必要的堆栈跟踪填充日志。Spring 尽最大努力识别 表示客户端断开连接(特定于每台服务器)和日志的此类网络故障 使用专用日志类别(在 中定义)的最小消息。如果需要查看堆栈跟踪,可以设置 log category 设置为 TRACE。DISCONNECTED_CLIENT_LOG_CATEGORYAbstractSockJsSession
因此,网络 I/O 故障可能会发生,因为客户端已断开连接,这 可以使用不必要的堆栈跟踪填充日志。Spring 尽最大努力识别 表示客户端断开连接(特定于每台服务器)和日志的此类网络故障 使用专用日志类别(在 中定义)的最小消息。如果需要查看堆栈跟踪,可以设置 log category 设置为 TRACE。DISCONNECTED_CLIENT_LOG_CATEGORYAbstractSockJsSession

SockJS 和 CORS

如果允许跨域请求(请参阅允许的源),则 SockJS 协议 在 XHR 流式处理和轮询传输中使用 CORS 进行跨域支持。因此 CORS 标头是自动添加的,除非响应中存在 CORS 标头 被检测到。因此,如果应用程序已配置为提供 CORS 支持(例如, 通过 Servlet 过滤器),Spring 跳过了这部分。SockJsServiceSpring中文文档

也可以通过在 Spring 的 SockJsService 中设置属性来禁用这些 CORS 标头的添加。suppressCorsSpring中文文档

SockJS 需要以下标头和值:Spring中文文档

  • Access-Control-Allow-Origin:从请求标头的值初始化。OriginSpring中文文档

  • Access-Control-Allow-Credentials:始终设置为 。trueSpring中文文档

  • Access-Control-Request-Headers:从等效请求标头中的值初始化。Spring中文文档

  • Access-Control-Allow-Methods:传输支持的 HTTP 方法(请参阅枚举)。TransportTypeSpring中文文档

  • Access-Control-Max-Age:设置为 31536000(1 年)。Spring中文文档

有关确切的实现,请参阅 in 和 源代码中的枚举。addCorsHeadersAbstractSockJsServiceTransportTypeSpring中文文档

或者,如果 CORS 配置允许,请考虑使用 SockJS 端点前缀,从而让 Spring 处理它。SockJsServiceSpring中文文档

SockJsClient

Spring 提供了一个 SockJS Java 客户端来连接到远程 SockJS 端点,而无需 使用浏览器。当需要双向时,这可能特别有用 通过公共网络(即网络代理可以 排除使用 WebSocket 协议)。SockJS Java 客户端也非常有用 用于测试目的(例如,模拟大量并发用户)。Spring中文文档

SockJS Java 客户端支持 、 和 传输。其余的只在浏览器中使用。websocketxhr-streamingxhr-pollingSpring中文文档

您可以使用以下命令进行配置:WebSocketTransportSpring中文文档

根据定义,一个 同时支持 和 ,因为 从客户端的角度来看,除了用于连接的 URL 之外,没有其他区别 到服务器。目前有两种实现方式:XhrTransportxhr-streamingxhr-pollingSpring中文文档

以下示例演示如何创建 SockJS 客户端并连接到 SockJS 端点:Spring中文文档

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");
SockJS 对消息使用 JSON 格式的数组。默认情况下,使用 Jackson 2 并需要 在类路径上。或者,您可以配置 的自定义实现,并在 .SockJsMessageCodecSockJsClient

要用于模拟大量并发用户,您需要 需要配置底层 HTTP 客户端(用于 XHR 传输)以允许足够的 连接数和线程数。以下示例演示如何使用 Jetty 执行此操作:SockJsClientSpring中文文档

HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));

以下示例显示了服务器端 SockJS 相关属性(有关详细信息,请参见 javadoc) 您还应该考虑自定义:Spring中文文档

@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/sockjs").withSockJS()
			.setStreamBytesLimit(512 * 1024) (1)
			.setHttpMessageCacheSize(1000) (2)
			.setDisconnectDelay(30 * 1000); (3)
	}

	// ...
}
1 将该属性设置为 512KB(默认值为 128KB — )。streamBytesLimit128 * 1024
2 将该属性设置为 1,000(默认值为 )。httpMessageCacheSize100
3 将属性设置为 30 个属性秒(默认值为 5 秒 — )。disconnectDelay5 * 1000
SockJS 对消息使用 JSON 格式的数组。默认情况下,使用 Jackson 2 并需要 在类路径上。或者,您可以配置 的自定义实现,并在 .SockJsMessageCodecSockJsClient
1 将该属性设置为 512KB(默认值为 128KB — )。streamBytesLimit128 * 1024
2 将该属性设置为 1,000(默认值为 )。httpMessageCacheSize100
3 将属性设置为 30 个属性秒(默认值为 5 秒 — )。disconnectDelay5 * 1000