对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
SockJS 回退
在公共 Internet 上,不受您控制的限制性代理可能会阻止 WebSocket
交互,因为它们未配置为传递标头或
,因为它们会关闭似乎处于空闲状态的长期连接。Upgrade
此问题的解决方案是 WebSocket 仿真 — 即尝试使用 WebSocket 首先,然后回退到模拟 WebSocket 的基于 HTTP 的技术 交互并公开相同的应用程序级 API。
在 Servlet 堆栈上,Spring Framework 提供服务器(和客户端)支持 对于 Sockjs 协议。
概述
Sockjs 的目标是让应用程序使用 WebSocket API,但回退到 非 WebSocket 替代方案,而无需 更改应用程序代码。
Sockjs 包括:
-
Sockjs JavaScript 客户端 — 用于浏览器的客户端库。
-
Sockjs 服务器实现,包括 Spring Framework 模块中的一个实现。
spring-websocket
-
模块中的 Sockjs Java 客户端(自版本 4.1 起)。
spring-websocket
Sockjs 专为在浏览器中使用而设计。它使用了多种技术 以支持各种浏览器版本。 有关 Sockjs 传输类型和浏览器的完整列表,请参阅 Sockjs 客户端页面。运输 分为三大类:WebSocket、HTTP 流和 HTTP 长轮询。 有关这些类别的概述,请参阅此博客文章。
Sockjs 客户端首先发送到
从服务器获取基本信息。之后,它必须决定哪种传输方式
使用。如果可能,使用 WebSocket。如果没有,在大多数浏览器中,
至少有一个 HTTP 流式处理选项。如果不是,则 HTTP (long)
轮询。GET /info
所有传输请求都具有以下 URL 结构:
https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
哪里:
-
{server-id}
对于在集群中路由请求很有用,但不会以其他方式使用。 -
{session-id}
关联属于 Sockjs 会话的 HTTP 请求。 -
{transport}
指示传输类型(例如,、 、 等)。websocket
xhr-streaming
WebSocket 传输只需要一个 HTTP 请求即可执行 WebSocket 握手。 此后的所有消息都在该套接字上交换。
HTTP 传输需要更多请求。例如,Ajax/XHR 流依赖于 一个长时间运行的服务器到客户端消息请求和其他 HTTP POST 客户端到服务器消息的请求。长轮询与此类似,只不过它 在每次服务器到客户端发送后结束当前请求。
Sockjs 添加了最少的消息框架。例如,服务器最初发送字母(“打开”帧),如果没有消息流,则消息作为(JSON 编码数组)发送,字母(“心跳”帧)发送
25 秒(默认),并使用字母(“close” frame)以关闭会话。o
a["message1","message2"]
h
c
要了解更多信息,请在浏览器中运行示例并观察 HTTP 请求。
Sockjs 客户端允许修复传输列表,因此可以
一次查看每种运输。Sockjs 客户端还提供了一个 debug 标志
,这将在浏览器控制台中启用有用的消息。在服务器端,您可以为 .
有关更多详细信息,请参阅 Sockjs 协议旁白测试。TRACE
org.springframework.web.socket
启用 SockJS
您可以通过 Java 配置启用 Sockjs,如下例所示:
@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
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 服务环境中。
在浏览器端,应用程序可以使用 sockjs-client
(版本 1.0.x)。它
模拟 W3C WebSocket API 并与服务器通信以选择最佳
transport 选项,具体取决于运行它的浏览器。参见 sockjs-client 页面和
浏览器支持的传输类型。客户端还提供多个
Configuration Options (配置选项) — 例如,指定要包含的传输。
IE 8 和 9
Internet Explorer 8 和 9 仍在使用中。他们是 拥有 Sockjs 的关键原因。本节涵盖重要的 有关在这些浏览器中运行的注意事项。
Sockjs 客户端通过使用 Microsoft 的 XDomainRequest
在 IE 8 和 9 中支持 Ajax/XHR 流。
这可以跨域工作,但不支持发送 Cookie。
Cookie 通常对于 Java 应用程序至关重要。
但是,由于 Sockjs 客户端可以与许多服务器一起使用
类型(不仅仅是 Java 类型)中,它需要知道 cookie 是否重要。
如果是这样,则 Sockjs 客户端首选 Ajax/XHR 进行流式处理。否则,它会
依赖于基于 iframe 的技术。
来自 Sockjs 客户端的第一个请求是对
可以影响客户选择交通工具的信息。
其中一个细节是服务器应用程序是否依赖 Cookie
(例如,用于身份验证目的或使用粘性会话进行集群)。
Spring 的 Sockjs 支持包括一个名为 .
默认情况下,它是启用的,因为大多数 Java 应用程序都依赖于 Cookie。如果您的应用程序不需要它,您可以关闭此选项,
和 Sockjs 客户端应该在 IE 8 和 9 中选择。/info
sessionCookieNeeded
JSESSIONID
xdr-streaming
如果您确实使用基于 iframe 的传输,请记住
可以通过以下方式指示浏览器在给定页面上阻止使用 IFrames
将 HTTP 响应标头设置为 、 、 或 。这用于防止点击劫持。X-Frame-Options
DENY
SAMEORIGIN
ALLOW-FROM <origin>
Spring Security 3.2+ 支持在每个
响应。默认情况下,Spring Security Java 配置将其设置为 。
在 3.2 中, Spring Security XML 命名空间默认情况下不设置该 Headers
但可以配置为执行此操作。将来,它可能会默认设置它。 |
如果您的应用程序添加了响应标头(正如它应该的那样!
并依赖于基于 iframe 的传输,则需要将标头值设置为 或 。春季 SockJS
支持还需要知道 Sockjs 客户端的位置,因为它已加载
从 iframe 中。默认情况下,iframe 设置为下载 Sockjs 客户端
从 CDN 位置。最好将此选项配置为使用
与应用程序来自同一源的 URL。X-Frame-Options
SAMEORIGIN
ALLOW-FROM <origin>
以下示例显示了如何在 Java 配置中执行此操作:
@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>
在初始开发期间,请启用 Sockjs 客户端模式,以防止
浏览器缓存 Sockjs 请求(如 iframe),否则
被缓存。有关如何启用它的详细信息,请参阅 Sockjs 客户端页面。devel |
心跳
Sockjs 协议要求服务器发送心跳消息以排除代理
得出连接已挂起的结论。Spring Sockjs 配置有一个属性
调用,可用于自定义频率。默认情况下,
检测信号在 25 秒后发送,假设没有发送其他消息
连接。此 25 秒值符合以下针对公共 Internet 应用程序的 IETF 建议。heartbeatTime
当通过 WebSocket 和 Sockjs 使用 STOMP 时,如果 STOMP 客户端和服务器协商 heartbeats 进行交换,则禁用 Sockjs 心跳。 |
Spring Sockjs 支持还允许您将
计划检测信号任务。任务调度器由线程池
使用基于可用处理器数量的默认设置。你
应考虑根据您的特定需求自定义设置。TaskScheduler
客户端断开连接
HTTP 流和 HTTP 长轮询 Sockjs 传输需要保持连接 开放时间比平时长。有关这些技术的概述,请参阅此博客文章。
在 Servlet 容器中,这是通过 Servlet 3 异步支持完成的,该 允许退出 Servlet 容器线程,处理请求并继续 写入来自另一个线程的响应。
一个具体问题是 Servlet API 不为客户端提供通知 这已经消失了。参见 eclipse-ee4j/servlet-api#44。 但是,Servlet 容器会在后续写入尝试时引发异常 响应。由于 Spring 的 Sockjs 服务支持服务器发送的心跳(每个 默认为 25 秒),这意味着通常会在该时间内检测到客户端断开连接 时间段(如果消息发送更频繁,则更早)。
因此,网络 I/O 故障可能是因为客户端已断开连接,这
可能会用不必要的堆栈跟踪填充日志。Spring 尽最大努力识别
此类网络故障表示客户端断开连接(特定于每个服务器)并记录
使用专用日志类别(在 中定义)的最小消息。如果您需要查看堆栈跟踪,可以将其
log 类别设置为 TRACE。DISCONNECTED_CLIENT_LOG_CATEGORY AbstractSockJsSession |
SockJS 和 CORS
如果您允许跨域请求(请参阅允许的源),则 Sockjs 协议
在 XHR 流和轮询传输中使用 CORS 实现跨域支持。因此
CORS 标头会自动添加,除非响应中存在 CORS 标头
检测到。因此,如果应用程序已配置为提供 CORS 支持(例如,
通过 Servlet Filter),Spring 跳过了这部分。SockJsService
也可以通过在 Spring 的 SockJsService 中设置属性来禁用这些 CORS 标头的添加。suppressCors
Sockjs 需要以下 Headers 和值:
-
Access-Control-Allow-Origin
:从请求头的值初始化。Origin
-
Access-Control-Allow-Credentials
:始终设置为 。true
-
Access-Control-Request-Headers
:从等效请求标头中的值初始化。 -
Access-Control-Allow-Methods
:传输支持的 HTTP 方法(请参阅 enum)。TransportType
-
Access-Control-Max-Age
:设置为 31536000(1 年)。
有关确切的实现,请参阅 in 和
源代码中的 enum 中。addCorsHeaders
AbstractSockJsService
TransportType
或者,如果 CORS 配置允许,请考虑排除
Sockjs 端点前缀,从而让 Spring 处理它。SockJsService
SockJsClient
Spring 提供了一个 Sockjs Java 客户端来连接到远程 Sockjs 端点,而无需 使用浏览器。当需要双向 两个服务器之间通过公共网络(即网络代理可以 排除使用 WebSocket 协议)。Sockjs Java 客户端也非常有用 用于测试目的(例如,模拟大量并发用户)。
Sockjs Java 客户端支持 、 和 transports。其余的只有在浏览器中使用才有意义。websocket
xhr-streaming
xhr-polling
您可以使用以下方式配置WebSocketTransport
-
StandardWebSocketClient
在 JSR-356 运行时中。 -
JettyWebSocketClient
通过使用 Jetty 9+ 原生 WebSocket API。 -
Spring 的 .
WebSocketClient
根据定义,An 同时支持 和 ,因为
从客户端的角度来看,除了用于连接的 URL 之外,没有其他区别
添加到服务器。目前有两种实现方式:XhrTransport
xhr-streaming
xhr-polling
-
RestTemplateXhrTransport
将 Spring 的用于 HTTP 请求。RestTemplate
-
JettyXhrTransport
将 Jetty 用于 HTTP 请求。HttpClient
以下示例演示如何创建 Sockjs 客户端并连接到 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");
Sockjs 对消息使用 JSON 格式的数组。默认情况下,使用 Jackson 2 并需要
以位于 Classpath 上。或者,您也可以配置 的自定义实现并在 .SockJsMessageCodec SockJsClient |
要使用 来模拟大量并发用户,您需要
需要配置底层 HTTP 客户端(用于 XHR 传输)以允许足够的
连接数和线程数。以下示例显示了如何使用 Jetty 执行此操作:SockJsClient
HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));
以下示例显示了服务器端与 Sockjs 相关的属性(有关详细信息,请参见 javadoc) 您还应考虑自定义:
@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 — )。streamBytesLimit 128 * 1024 |
2 | 将属性设置为 1,000 (默认值为 )。httpMessageCacheSize 100 |
3 | 将属性设置为 30 个属性秒(默认值为 5 秒 — )。disconnectDelay 5 * 1000 |