SockJS 回退

在公共 Internet 上,不受您控制的限制性代理可能会阻止 WebSocket 交互,要么是因为它们未配置为将Upgradeheader 或 ,因为它们会关闭似乎处于空闲状态的长期连接。spring-doc.cadn.net.cn

此问题的解决方案是 WebSocket 仿真 — 即尝试使用 WebSocket 首先,然后回退到模拟 WebSocket 的基于 HTTP 的技术 交互并公开相同的应用程序级 API。spring-doc.cadn.net.cn

在 Servlet 堆栈上,Spring Framework 提供服务器(和客户端)支持 对于 Sockjs 协议。spring-doc.cadn.net.cn

概述

Sockjs 的目标是让应用程序使用 WebSocket API,但回退到 非 WebSocket 替代方案,而无需 更改应用程序代码。spring-doc.cadn.net.cn

Sockjs 包括:spring-doc.cadn.net.cn

Sockjs 专为在浏览器中使用而设计。它使用了多种技术 以支持各种浏览器版本。 有关 Sockjs 传输类型和浏览器的完整列表,请参阅 Sockjs 客户端页面。运输 分为三大类:WebSocket、HTTP 流和 HTTP 长轮询。 有关这些类别的概述,请参阅此博客文章spring-doc.cadn.net.cn

Sockjs 客户端首先发送GET /info自 从服务器获取基本信息。之后,它必须决定哪种传输方式 使用。如果可能,使用 WebSocket。如果没有,在大多数浏览器中, 至少有一个 HTTP 流式处理选项。如果不是,则 HTTP (long) 轮询。spring-doc.cadn.net.cn

所有传输请求都具有以下 URL 结构:spring-doc.cadn.net.cn

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

WebSocket 传输只需要一个 HTTP 请求即可执行 WebSocket 握手。 此后的所有消息都在该套接字上交换。spring-doc.cadn.net.cn

HTTP 传输需要更多请求。例如,Ajax/XHR 流依赖于 一个长时间运行的服务器到客户端消息请求和其他 HTTP POST 客户端到服务器消息的请求。长轮询与此类似,只不过它 在每次服务器到客户端发送后结束当前请求。spring-doc.cadn.net.cn

Sockjs 添加了最少的消息框架。例如,服务器将信件o(“打开”框架)最初,消息以a["message1","message2"](JSON 编码数组),则字母h(“heartbeat” 帧)如果没有消息流 25 秒(默认),并且字母c(“close” frame) 以关闭会话。spring-doc.cadn.net.cn

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

启用 SockJS

您可以通过配置启用 Sockjs,如下例所示:spring-doc.cadn.net.cn

@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();
	}

}

前面的示例用于 Spring MVC 应用程序,应包含在 配置DispatcherServlet.但是,Spring 的 WebSocket 并且 Sockjs 支持不依赖于 Spring MVC。它相对简单 在SockJsHttpRequestHandler.spring-doc.cadn.net.cn

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

IE 8 和 9

Internet Explorer 8 和 9 仍在使用中。他们是 拥有 Sockjs 的关键原因。本节涵盖重要的 有关在这些浏览器中运行的注意事项。spring-doc.cadn.net.cn

Sockjs 客户端使用 Microsoft 的XDomainRequest. 这可以跨域工作,但不支持发送 Cookie。 Cookie 通常对于 Java 应用程序至关重要。 但是,由于 Sockjs 客户端可以与许多服务器一起使用 类型(不仅仅是 Java 类型)中,它需要知道 cookie 是否重要。 如果是这样,则 Sockjs 客户端首选 Ajax/XHR 进行流式处理。否则,它会 依赖于基于 iframe 的技术。spring-doc.cadn.net.cn

第一个/info请求是 可以影响客户选择交通工具的信息。 其中一个细节是服务器应用程序是否依赖 Cookie (例如,用于身份验证目的或使用粘性会话进行集群)。 Spring 的 Sockjs 支持包括一个名为sessionCookieNeeded. 默认情况下,它是启用的,因为大多数 Java 应用程序都依赖于JSESSIONID饼干。如果您的应用程序不需要它,您可以关闭此选项, 然后 Sockjs 客户端应该选择xdr-streaming在 IE 8 和 9 中。spring-doc.cadn.net.cn

如果您确实使用基于 iframe 的传输,请记住 可以通过以下方式指示浏览器在给定页面上阻止使用 IFrames 设置 HTTP 响应标头X-Frame-OptionsDENY,SAMEORIGINALLOW-FROM <origin>.这用于防止点击劫持spring-doc.cadn.net.cn

Spring Security 3.2+ 支持设置X-Frame-Options在每个 响应。默认情况下,Spring Security Java 配置将其设置为DENY. 在 3.2 中, Spring Security XML 命名空间默认情况下不设置该 Headers 但可以配置为执行此作。将来,它可能会默认设置它。spring-doc.cadn.net.cn

有关如何配置 设置X-Frame-Options页眉。您还可以查看 gh-2718 以获取更多背景信息。spring-doc.cadn.net.cn

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

以下示例显示了如何在 Java 配置中执行此作:spring-doc.cadn.net.cn

@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-doc.cadn.net.cn

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

心跳

Sockjs 协议要求服务器发送心跳消息以排除代理 得出连接已挂起的结论。Spring Sockjs 配置有一个属性 叫heartbeatTime可用于自定义频率。默认情况下, 检测信号在 25 秒后发送,假设没有发送其他消息 连接。此 25 秒值符合以下针对公共 Internet 应用程序的 IETF 建议spring-doc.cadn.net.cn

当通过 WebSocket 和 Sockjs 使用 STOMP 时,如果 STOMP 客户端和服务器协商 heartbeats 进行交换,则禁用 Sockjs 心跳。

Spring Sockjs 支持还允许您配置TaskScheduler自 计划检测信号任务。任务调度器由线程池 使用基于可用处理器数量的默认设置。你 应考虑根据您的特定需求自定义设置。spring-doc.cadn.net.cn

客户端断开连接

HTTP 流和 HTTP 长轮询 Sockjs 传输需要保持连接 开放时间比平时长。有关这些技术的概述,请参阅此博客文章spring-doc.cadn.net.cn

在 Servlet 容器中,这是通过 Servlet 3 异步支持完成的,该 允许退出 Servlet 容器线程,处理请求并继续 写入来自另一个线程的响应。spring-doc.cadn.net.cn

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

因此,网络 I/O 故障可能是因为客户端已断开连接,这 可能会用不必要的堆栈跟踪填充日志。Spring 尽最大努力识别 此类网络故障表示客户端断开连接(特定于每个服务器)并记录 使用专用日志类别的最小消息,DISCONNECTED_CLIENT_LOG_CATEGORY(在AbstractSockJsSession).如果您需要查看堆栈跟踪,可以将其 log 类别设置为 TRACE。

SockJS 和 CORS

如果您允许跨域请求(请参阅允许的源),则 Sockjs 协议 在 XHR 流和轮询传输中使用 CORS 实现跨域支持。因此 CORS 标头会自动添加,除非响应中存在 CORS 标头 检测到。因此,如果应用程序已配置为提供 CORS 支持(例如, 通过 Servlet 过滤器)、Spring 的SockJsService跳过此部分。spring-doc.cadn.net.cn

也可以通过设置suppressCorsSpring 的 SockJsService 中的属性。spring-doc.cadn.net.cn

Sockjs 需要以下 Headers 和值:spring-doc.cadn.net.cn

有关确切的实现,请参阅addCorsHeadersAbstractSockJsService和 这TransportTypeenum 的 intent 文件。spring-doc.cadn.net.cn

或者,如果 CORS 配置允许,请考虑排除 Sockjs 端点前缀,从而让 Spring 的SockJsService处理它。spring-doc.cadn.net.cn

SockJsClient

Spring 提供了一个 Sockjs Java 客户端来连接到远程 Sockjs 端点,而无需 使用浏览器。当需要双向 两个服务器之间通过公共网络(即网络代理可以 排除使用 WebSocket 协议)。Sockjs Java 客户端也非常有用 用于测试目的(例如,模拟大量并发用户)。spring-doc.cadn.net.cn

Sockjs Java 客户端支持websocket,xhr-streamingxhr-polling运输。其余的只有在浏览器中使用才有意义。spring-doc.cadn.net.cn

您可以配置WebSocketTransport跟:spring-doc.cadn.net.cn

XhrTransport,根据定义,同时支持xhr-streamingxhr-polling因为 从客户端的角度来看,除了用于连接的 URL 之外,没有其他区别 添加到服务器。目前有两种实现方式:spring-doc.cadn.net.cn

以下示例演示如何创建 Sockjs 客户端并连接到 Sockjs 端点:spring-doc.cadn.net.cn

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 并需要 以位于 Classpath 上。或者,您可以配置SockJsMessageCodec并在SockJsClient.

要使用SockJsClient要模拟大量并发用户,您需要 需要配置底层 HTTP 客户端(用于 XHR 传输)以允许足够的 连接数和线程数。以下示例显示了如何使用 Jetty 执行此作:spring-doc.cadn.net.cn

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

以下示例显示了服务器端与 Sockjs 相关的属性(有关详细信息,请参见 javadoc) 您还应考虑自定义:spring-doc.cadn.net.cn

@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 streamBytesLimit属性设置为 512KB(默认值为 128KB —128 * 1024).
2 httpMessageCacheSize属性设置为 1,000(默认值为100).
3 disconnectDelayproperty 设置为 30 个属性秒(默认值为 5 秒 -5 * 1000).

APP信息