> For the complete documentation index, see [llms.txt](https://docs.flydean.com/spring-framework-documentation5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flydean.com/spring-framework-documentation5/webservlet/4.websockets/4.3sockjs-fallback.md).

# 4.3 SockJS Fallback

在公共Internet上，控件外的限制性代理可能会阻止WebSocket交互，这可能是因为它们未配置为传递Upgrade标头，或者是因为它们关闭了长期处于空闲状态的连接。

解决此问题的方法是WebSocket仿真，即先尝试使用WebSocket，然后尝试使用基于HTTP的技术来模拟WebSocket交互并公开相同的应用程序级API。

在Servlet堆栈上，Spring框架为SockJS协议提供服务器（和客户端）支持。

## 4.3.1 Overview

SockJS的目标是让应用程序使用WebSocket API，但在运行时必要时使用非WebSocket替代方案，而无需更改应用程序代码。

SockJS包括：

* SockJS协议以可执行叙述测试的形式定义。
* SockJS JavaScript客户端-一种在浏览器中使用的客户端库。
* SockJS服务器实现，包括Spring框架spring-websocket模块中的一个。
* spring-websocket模块中的SockJS Java客户端（从4.1版开始）。

SockJS设计用于浏览器。它使用多种技术来支持各种浏览器版本。有关SockJS传输类型和浏览器的完整列表，请参见SockJS客户端页面。传输分为三大类：WebSocket，HTTP流和HTTP长轮询。有关这些类别的概述，请参阅此博客文章。

SockJS客户端首先发送GET / info以从服务器获取基本信息。在那之后，它必须决定使用哪种交通工具。如果可能，请使用WebSocket。如果没有，在大多数浏览器中，至少有一个HTTP流选项。如果不是，则使用HTTP（长）轮询。

所有传输请求都具有以下URL结构：

`http://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添加了最少的消息框架。例如，服务器最初发送字母o（“open”帧），消息以a \[“ message1”，“ message2”]（JSON编码数组）发送，如果没有消息，则以字母h（“heartbeat”帧）发送持续25秒（默认情况下），然后使用字母c（“close”框）关闭会话。

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

## 4.3.2 Enabling SockJS

您可以通过Java配置启用SockJS，如以下示例所示：

```java
@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配置：

```markup
<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，并与服务器通信以选择最佳的传输选项，具体取决于运行它的浏览器。 请参阅sockjs-client页面和浏览器支持的传输类型列表。 客户端还提供了几个配置选项，例如用于指定要包括的传输。

## 4.3.3 IE 8 and 9

Internet Explorer 8和9仍在使用。它们是拥有SockJS的关键原因。本节涵盖有关在这些浏览器中运行的重要注意事项。

SockJS客户端通过使用Microsoft的XDomainRequest在IE 8和9中支持Ajax / XHR流。跨域有效，但不支持发送Cookie。 Cookies对于Java应用程序通常是必不可少的。但是，由于SockJS客户端可用于多种服务器类型（而不仅仅是Java类型），因此它需要知道cookie是否重要。如果是这样，则SockJS客户端更喜欢Ajax / XHR进行流传输。否则，它依赖于基于iframe的技术。

SockJS客户端发出的第一个/ info请求是对可能影响客户端选择传输方式的信息的请求。这些详细信息之一是服务器应用程序是否依赖Cookie（例如，出于身份验证目的或使用粘性会话进行群集）。 Spring对SockJS的支持包括一个名为sessionCookieNeeded的属性。由于大多数Java应用程序都依赖JSESSIONID cookie，因此默认情况下启用此功能。如果您的应用程序不需要它，则可以关闭此选项，然后SockJS客户端应在IE 8和9中选择xdr-streaming。

如果您确实使用基于iframe的传输，请记住，可以通过将HTTP响应标头X-Frame-Options设置为DENY，SAMEORIGIN或ALLOW-FROM \<origin>来指示浏览器阻止在给定页面上使用iframe 。这用于防止点击劫持。

> Spring Security 3.2+提供了对每个响应设置X-Frame-Options的支持。 默认情况下，Spring Security Java配置将其设置为DENY。 在3.2中，Spring Security XML名称空间默认情况下不设置该标头，但可以配置为这样做。 将来，它可能会默认设置。
>
> 有关如何配置X-Frame-Options标头的设置的详细信息，请参见Spring Security文档的Default Security Headers。 您也可以查看SEC-2501以获取更多背景信息。

如果您的应用程序添加了X-Frame-Options响应标头（应如此！）并依赖于基于iframe的传输，则需要将标头值设置为SAMEORIGIN或ALLOW-FROM \<origin>。 Spring SockJS支持还需要知道SockJS客户端的位置，因为它是从iframe加载的。 默认情况下，iframe设置为从CDN位置下载SockJS客户端。 最好将此选项配置为使用与应用程序源相同的URL。

以下示例显示了如何在Java配置中执行此操作：

```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客户端页面。

## 4.3.4 Heartbeats

SockJS协议要求服务器发送心跳消息，以防止代理断定连接已挂起。 Spring SockJS配置具有一个名为heartbeatTime的属性，可用于自定义频率。 默认情况下，假设没有其他消息在该连接上发送，则心跳将在25秒后发送。 这个25秒的值符合以下IETF对公共Internet应用程序的建议。

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

Spring SockJS支持还允许您配置TaskScheduler来调度心跳任务。 任务调度程序由线程池支持，其默认设置基于可用处理器的数量。 您应该考虑根据您的特定需求自定义设置。

## 4.3.5 Client Disconnects

HTTP流和HTTP长轮询SockJS传输要求连接保持打开的时间比平常更长。有关这些技术的概述，请参见此博客文章。

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

一个特定的问题是，Servlet API不会为已离开的客户端提供通知。请参阅eclipse-ee4j / servlet-api＃44。但是，Servlet容器在随后尝试写入响应时会引发异常。由于Spring的SockJS服务支持服务器发送的心跳信号（默认情况下，每25秒发送一次），这意味着通常会在该时间段内（或更早，如果消息发送频率更高）检测到客户端断开连接。

> 结果，由于客户端已断开连接，可能会发生网络I / O故障，从而可能在日志中填充不必要的堆栈跟踪。 Spring会尽最大努力找出代表客户端断开连接（特定于每个服务器）的网络故障，并通过使用专用日志类别DISCONNECTED\_CLIENT\_LOG\_CATEGORY（在AbstractSockJsSession中定义）来记录最少的消息。如果需要查看堆栈跟踪，可以将该日志类别设置为TRACE。

## 4.3.6 SockJS and CORS

如果允许跨域请求（请参阅“允许的起源”），则SockJS协议将CORS用于XHR流和轮询传输中的跨域支持。因此，除非在响应中检测到CORS标头的存在，否则将自动添加CORS标头。因此，如果已经将应用程序配置为提供CORS支持（例如，通过Servlet过滤器），则Spring的SockJsService会跳过这一部分。

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

SockJS需要以下标头和值：

* Access-Control-Allow-Origin：从Origin请求标头的值初始化。
* Access-Control-Allow-Credentials：始终设置为true。
* Access-Control-Request-Headers：从等效请求标头中的值初始化。
* Access-Control-Allow-Methods：传输支持的HTTP方法（请参阅TransportType枚举）。
* Access-Control-Max-Age：设置为31536000（1年）。

有关确切的实现，请参见AbstractSockJsService中的addCorsHeaders和源代码中的TransportType枚举。

另外，如果CORS配置允许，请考虑排除带有SockJS端点前缀的URL，从而让Spring的SockJsService处理它。

## 4.3.7 SockJsClient

Spring提供了一个SockJS Java客户端，无需使用浏览器即可连接到远程SockJS端点。当需要通过公用网络在两个服务器之间进行双向通信时（即，网络代理可能会阻止使用WebSocket协议），这特别有用。 SockJS Java客户端对于测试目的也非常有用（例如，模拟大量并发用户）。

SockJS Java客户端支持websocket，xhr-streaming和xhr-polling传输。其余的仅在浏览器中有意义。

您可以使用以下命令配置WebSocketTransport：

* JSR-356运行时中的StandardWebSocketClient。
* 通过使用Jetty 9+本机WebSocket API来创建JettyWebSocketClient。
* Spring的WebSocketClient的任何实现。

根据定义，XhrTransport支持xhr-streaming和xhr-polling，因为从客户端的角度来看，除了用于连接服务器的URL之外没有其他区别。当前有两种实现：

* RestTemplateXhrTransport使用Spring的RestTemplate进行HTTP请求。
* JettyXhrTransport使用Jetty的HttpClient进行HTTP请求。

以下示例显示了如何创建SockJS客户端并连接到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");
```

> SockJS对消息使用JSON格式的数组。 默认情况下，使用Jackson 2，并且需要在类路径上。 或者，您可以配置SockJsMessageCodec的自定义实现，并在SockJsClient上对其进行配置。

要使用SockJsClient模拟大量并发用户，您需要配置基础HTTP客户端（用于XHR传输）以允许足够数量的连接和线程。 以下示例显示了如何使用Jetty进行操作：

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

以下示例显示了与服务器端SockJS相关的属性（有关详细信息，请参见javadoc），您还应考虑自定义：

```java
@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {

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

    // ...
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/webservlet/4.websockets/4.3sockjs-fallback.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
