# WebSockets 支持

# WebSockets 支持

从版本 4.1 开始, Spring 集成有 WebSocket 支持。它基于 Spring 框架的web-socket模块中的体系结构、基础设施和 API。因此, Spring WebSocket 的许多组件(例如SubProtocolHandlerWebSocketClient)和配置选项(例如@EnableWebSocketMessageBroker)可以在 Spring 集成内重用。有关更多信息,请参见 Spring 框架参考手册中的Spring Framework WebSocket Support (opens new window)章节。

你需要在项目中包含此依赖项:

Maven

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-websocket</artifactId>
    <version>5.5.9</version>
</dependency>

Gradle

compile "org.springframework.integration:spring-integration-websocket:5.5.9"

对于服务器端,必须显式地包含org.springframework:spring-webmvc依赖项。

Spring 框架 WebSocket 基础设施是基于 Spring 消息传递基础并且提供了基于与 Spring 集成使用的相同的MessageChannel实现和MessageHandler实现(以及一些 POJO-method 注释映射)的基本消息传递框架。因此, Spring 集成可以直接参与到 WebSocket 流中,即使没有 WebSocket 适配器也是如此。为此,你可以使用适当的注释来配置 Spring 集成@MessagingGateway,如下例所示:

@MessagingGateway
@Controller
public interface WebSocketGateway {

    @MessageMapping("/greeting")
    @SendToUser("/queue/answer")
    @Gateway(requestChannel = "greetingChannel")
    String greeting(String payload);

}

# 概述

由于 WebSocket 协议根据定义是流的并且我们可以同时向 WebSocket 发送和接收消息,因此我们可以处理适当的WebSocketSession,而不管是在客户端还是服务器端。为了封装连接管理和WebSocketSession注册中心,IntegrationWebSocketContainer提供了ClientWebSocketContainerServerWebSocketContainer实现。由于WebSocket API (opens new window)及其在 Spring 框架中的实现(具有许多扩展),在服务器端和客户端都使用了相同的类(当然,从 Java 的角度来看)。因此,大多数连接和WebSocketSession注册表选项在两边都是相同的。这使我们能够重用许多配置项和基础设施挂钩,以便在服务器端和客户端构建 WebSocket 应用程序。下面的示例展示了组件如何实现这两个目的:

//Client side
@Bean
public WebSocketClient webSocketClient() {
    return new SockJsClient(Collections.singletonList(new WebSocketTransport(new JettyWebSocketClient())));
}

@Bean
public IntegrationWebSocketContainer clientWebSocketContainer() {
    return new ClientWebSocketContainer(webSocketClient(), "ws://my.server.com/endpoint");
}

//Server side
@Bean
public IntegrationWebSocketContainer serverWebSocketContainer() {
    return new ServerWebSocketContainer("/endpoint").withSockJs();
}

WebSocket 该被设计用于实现双向消息传递,并且可以在入站和出站通道适配器之间共享(见下文),在使用单向消息传递时可以仅从其中一个引用 WebSocket。它可以在没有任何通道适配器的情况下使用,但是,在这种情况下,IntegrationWebSocketContainer仅作为WebSocketSession注册中心发挥作用。

ServerWebSocketContainer实现WebSocketConfigurer将一个内部IntegrationWebSocketContainer.IntegrationWebSocketHandler注册为Endpoint
它在提供的paths下这样做和其他服务器 WebSocket 选项(例如HandshakeHandlerSockJS fallback)内的ServletWebSocketHandlerRegistry用于目标供应商 WebSocket 容器。
该注册是通过一个基础设施WebSocketIntegrationConfigurationInitializer组件实现的,其执行与@EnableWebSocket注释相同的操作。
这意味着,通过使用@EnableIntegration(或应用程序上下文中的任何 Spring 集成名称空间),你可以省略@EnableWebSocket声明,因为 Spring 集成基础结构检测所有 WebSocket 端点。

# WebSocket 入站通道适配器

WebSocketInboundChannelAdapter实现了WebSocketSession交互的接收部分。你必须为它提供IntegrationWebSocketContainer,并且适配器将自身注册为WebSocketListener以处理传入消息和WebSocketSession事件。

只有一个WebSocketListener可以在IntegrationWebSocketContainer中注册。

对于 WebSocket 子协议,WebSocketInboundChannelAdapter可以配置SubProtocolHandlerRegistry作为第二个构造函数参数。适配器委托给SubProtocolHandlerRegistry,以确定适用于已接受的WebSocketSessionSubProtocolHandler,并根据子协议实现将WebSocketMessage转换为Message

默认情况下,WebSocketInboundChannelAdapter仅依赖于 RAWPassThruSubProtocolHandler实现,该实现将WebSocketMessage转换为Message

WebSocketInboundChannelAdapter只接受并发送到底层集成流Message实例,这些实例具有SimpMessageType.MESSAGE或空的simpMessageType头。所有其他Message类型都是通过ApplicationEvent实现发出的SubProtocolHandler实例来处理的(例如StompSubProtocolHandler)。

在服务器端,如果存在@EnableWebSocketMessageBroker配置,则可以使用useBroker = true选项配置WebSocketInboundChannelAdapter。在这种情况下,所有non-MESSAGE``Message类型都被委托给所提供的AbstractBrokerMessageHandler。此外,如果代理中继配置了目标前缀,那么那些匹配代理目标的消息将路由到AbstractBrokerMessageHandler,而不是WebSocketInboundChannelAdapteroutputChannel

如果useBroker = false并且接收到的消息是SimpMessageType.CONNECT类型的,则WebSocketInboundChannelAdapter立即向SimpMessageType.CONNECT_ACK消息发送WebSocketSession,而不将其发送到信道。

Spring 的 WebSocket 支持只允许配置一个代理中继。
因此,我们不需要AbstractBrokerMessageHandler引用。
它是在应用程序上下文中检测到的。

有关更多配置选项,请参见WebSockets 名称空间支持

# WebSocket 出站通道适配器

theWebSocketOutboundChannelAdapter:

  1. 接受 Spring 来自其MessageChannel的集成消息

  2. MessageHeaders中确定WebSocketSession``id

  3. 从提供的IntegrationWebSocketContainer检索WebSocketSession

  4. 从提供的SubProtocolHandlerRegistryWebSocketMessage工作的转换和发送委托给相应的SubProtocolHandler

在客户端,WebSocketSession``id消息头不是必需的,因为ClientWebSocketContainer仅处理单个连接及其WebSocketSession分别。

要使用 STOMP 子协议,你应该使用StompSubProtocolHandler配置此适配器。然后,你可以使用StompHeaderAccessor.create(StompCommand…​)MessageBuilder向此适配器发送任何 Stomp 消息类型,或者只使用HeaderEnricher(参见页眉 Enricher)。

本章的其余部分主要介绍了附加的配置选项。

# WebSockets 名称空间支持

Spring 集成 WebSocket 命名空间包括在本章的其余部分中描述的几个组件。要将其包含在配置中,请在应用程序上下文配置文件中使用以下名称空间声明:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:int="http://www.springframework.org/schema/integration"
  xmlns:int-websocket="http://www.springframework.org/schema/integration/websocket"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration
    https://www.springframework.org/schema/integration/spring-integration.xsd
    http://www.springframework.org/schema/integration/websocket
    https://www.springframework.org/schema/integration/websocket/spring-integration-websocket.xsd">
    ...
</beans>

# <int-websocket:client-container>属性

下面的清单显示了<int-websocket:client-container>元素可用的属性:

<int-websocket:client-container
                  id=""                        (1)
                  client=""                    (2)
                  uri=""                       (3)
                  uri-variables=""             (4)
                  origin=""                    (5)
                  send-time-limit=""           (6)
                  send-buffer-size-limit=""    (7)
                  auto-startup=""              (8)
                  phase="">                    (9)
                <int-websocket:http-headers>
                  <entry key="" value=""/>
                </int-websocket:http-headers>  (10)
</int-websocket:client-container>
1 组件 Bean 名称。
2 参考文献WebSocketClient Bean。
3 uriuriTemplate发送到目标 WebSocket 服务。
如果将其用作带有 URI 变量占位符的uriTemplate,则需要uri-variables属性。
4
属性值内的 URI 变量占位符的逗号分隔的值。
这些值根据它们在uri中的顺序被替换到占位符中。
参见[UriComponents.expand(Object…​uriVariableValues)(https://DOCS. Spring.io/ Spring//DOCS/current/javadoc-api/org/SpringFramework/web/util/utilComponents.html expand-uribot)。
5 Origin握手 HTTP 标头值。
6 WebSocket 会话“发送”超时限制。
默认为10000
7 WebSocket 会话“发送”消息大小限制。
默认为524288
8 布尔值,表示此端点是否应自动启动。
默认为false,假设此容器是从WebSocket inbound adapter启动的。
9 这个端点应该在其中开始和停止的生命周期阶段。
值越低,这个端点开始得越早,停止得越晚。
默认值是Integer.MAX_VALUE
值可以是负数。
参见[SmartLifeCycle(https:/DOCS. Spring.io/ Spring//DOCS/current/javadoc-api/org/api/context/smartlifycle.html)。
10 aMapofHttpHeaders用于握手请求。

# <int-websocket:server-container>属性

下面的清单显示了<int-websocket:server-container>元素可用的属性:

<int-websocket:server-container
          id=""                         (1)
          path=""                       (2)
          handshake-handler=""          (3)
          handshake-interceptors=""     (4)
          decorator-factories=""        (5)
          send-time-limit=""            (6)
          send-buffer-size-limit=""     (7)
          allowed-origins="">           (8)
          <int-websocket:sockjs
            client-library-url=""       (9)
            stream-bytes-limit=""       (10)
            session-cookie-needed=""    (11)
            heartbeat-time=""           (12)
            disconnect-delay=""         (13)
            message-cache-size=""       (14)
            websocket-enabled=""        (15)
            scheduler=""                (16)
            message-codec=""            (17)
            transport-handlers=""       (18)
            suppress-cors="true"="" />  (19)
</int-websocket:server-container>
1 组件 Bean 名称。
2 一种将特定请求映射到WebSocketHandler的路径(或以逗号分隔的路径)。
支持精确的路径映射 URI(例如/myPath)和 Ant 样式的路径模式(例如/myPath/**)。
3 HandshakeHandler Bean 引用.
默认为DefaultHandshakeHandler
4 参考文献列表HandshakeInterceptor Bean。
5 用于修饰用于处理 WebSocket 消息的处理程序的一个或多个工厂的列表(WebSocketHandlerDecoratorFactory)。
这对于某些高级用例(例如,以允许 Spring 安全性在相应的 HTTP会话过期时强制关闭 WebSocket 会话。有关更多信息,请参见。
6 请参阅[<int-websocket:client-container>](# WebSocket-client-container-attributes)上的相同选项。
7 请参阅[<int-websocket:client-container>](# WebSocket-client-container-attributes)上的相同选项。
8 允许的源头值。
你可以将多个源代码指定为逗号分隔的列表。
此检查主要是为浏览器客户端设计的。
没有什么可以阻止其他类型的客户端修改源头值。
当启用 Sockjs 并且限制允许的源代码时,不使用起源头来处理跨起源请求(jsonp-pollingiframe-xhr-pollingiframe-eventsource,和iframe-htmlfile)的传输类型将被禁用。因此,不支持 IE6 和 IE7,并且只支持 IE8 和 IE9 而不支持 cookie。
默认情况下,所有起源都是允许的。
9 没有本机跨域通信的传输(例如eventsourcehtmlfile)必须在不可见的 iframe 中从“foreign”域获得一个简单的页面,以便 IFRAME 中的代码可以从本地域运行到 Sockjs 服务器。
由于 IFRAME 需要加载 Sockjs JavaScript 客户库,因此该属性允许你指定加载它的位置。
默认情况下,它指向[https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js](https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js)。,但是,
,还可以将它设置为指向应用程序提供的 URL。
注意,可以指定一个相对 URL,在这种情况下,URL 必须与 IFRAME URL 相对。
,例如,假设一个 Sockjs 端点映射到/sockjs,并且得到的 iFrame URL 是/sockjs/iframe.html,那么相对 URL 必须以“.././”开头,才能遍历到 Sockjs 映射上方的位置。
对于基于前缀的 Servlet 映射,你可能需要再遍历一次。
10 在关闭单个 HTTP 流请求之前,可以通过该请求发送的最小字节数。
默认为128K(即 128*1024 或 131072 字节)。
11 来自 Sockjs 的响应中的cookie_needed/infoEndpoint.
此属性指示应用程序是否需要JSESSIONIDcookie 才能正常工作(例如,用于负载平衡或在 Java Servlet 容器中使用 HTTP会话)。
12 当服务器没有发送任何消息时的时间量(以毫秒为单位),在此之后服务器应该
向客户端发送心跳帧,以防止连接中断。
默认值为25,000(25 秒)。
13 客户机在没有接收连接(即服务器可以在其上向客户机发送数据的活动连接)之后被认为断开连接之前的时间量(以毫秒为单位)。
默认值为5000
14 会话在等待来自客户端的下一个 HTTP 轮询请求时可以缓存的服务器到客户端消息的数量。
默认大小为100
15 有些负载均衡器不支持 WebSockets。
将此选项设置为false,以禁用服务器端的 WebSocket 传输。
默认值为true
16 TaskScheduler Bean 引用。
如果不提供值,将创建一个新的ThreadPoolTaskScheduler实例。
此调度程序实例用于调度心跳消息。
17 该 Bean 引用用于编码和解码 Sockjs 消息。默认情况下,使用,这要求在 Classpath 上存在 Jackson 库。
18 参考文献列表TransportHandler Bean。
19 是否禁用自动添加 SOCKJS 请求的 CORS 头。
默认值为false

# <int-websocket:outbound-channel-adapter>属性

下面的清单显示了<int-websocket:outbound-channel-adapter>元素可用的属性:

<int-websocket:outbound-channel-adapter
                          id=""                             (1)
                          channel=""                        (2)
                          container=""                      (3)
                          default-protocol-handler=""       (4)
                          protocol-handlers=""              (5)
                          message-converters=""             (6)
                          merge-with-default-converters=""  (7)
                          auto-startup=""                   (8)
                          phase=""/>                        (9)
1 组件 Bean name.
如果不提供channel属性,则在应用程序上下文中创建并注册一个DirectChannel,并将该id属性作为 Bean 名称。
在这种情况下,端点以 Bean 名称id加上.adapter注册。
MessageHandler则以 Bean 别名id加上.handler注册。
2 标识连接到此适配器的通道。
3 IntegrationWebSocketContainer Bean 的引用,它封装了低级连接和WebSocketSession处理操作。
需要。
4 SubProtocolHandler实例的可选引用。
当客户端未请求子协议或它是单个协议处理程序时使用。
如果未提供此引用或protocol-handlers列表,则默认使用PassThruSubProtocolHandler
5 该通道适配器的SubProtocolHandler Bean 引用列表。
如果只提供单个 Bean 引用而不提供default-protocol-handler,则该单个SubProtocolHandler用作default-protocol-handler
如果不设置此属性或default-protocol-handler,默认情况下使用PassThruSubProtocolHandler
6 此通道适配器的MessageConverter Bean 引用列表。
7 布尔值,表示是否应在任何自定义转换器之后注册默认转换器。
只有在提供message-converters时才使用此标志。
否则,将注册所有默认转换器。
默认为false
默认转换器(按顺序排列):StringMessageConverterByteArrayMessageConverter,和(如果 Jackson 库存在于 Classpath 上)。
8 布尔值,表示此端点是否应自动启动。
默认为true
9 这个端点应该在其中开始和停止的生命周期阶段。
值越低,这个端点开始得越早,停止得越晚。
默认值是Integer.MIN_VALUE
值可以是负数。
参见[SmartLifeCycle(https:/DOCS. Spring.io/ Spring/DOCS/current/javadoc-apf/org/api/context/smartlifycle.html)。

# <int-websocket:inbound-channel-adapter>属性

下面的清单显示了<int-websocket:outbound-channel-adapter>元素可用的属性:

<int-websocket:inbound-channel-adapter
                            id=""  (1)
                            channel=""  (2)
                            error-channel=""  (3)
                            container=""  (4)
                            default-protocol-handler=""  (5)
                            protocol-handlers=""  (6)
                            message-converters=""  (7)
                            merge-with-default-converters=""  (8)
                            send-timeout=""  (9)
                            payload-type=""  (10)
                            use-broker=""  (11)
                            auto-startup=""  (12)
                            phase=""/>  (13)
1 组件 Bean name.
如果不设置channel属性,则在应用程序上下文中创建并注册一个DirectChannel,并将该id属性作为 Bean 名称。
在本例中,端点以 Bean 名称id加上.adapter进行注册。
2 标识连接到此适配器的通道。
3 应该发送ErrorMessage实例的MessageChannel Bean 引用。
4 请参阅[<int-websocket:outbound-channel-adapter>](# WebSocket-出站-通道-适配器-属性)上的相同选项。
5 请参阅[<int-websocket:outbound-channel-adapter>](# WebSocket-出站-通道-适配器-属性)上的相同选项。
6 请参阅[<int-websocket:outbound-channel-adapter>](# WebSocket-出站-通道-适配器-属性)上的相同选项。
7 请参阅[<int-websocket:outbound-channel-adapter>](# WebSocket-出站-通道-适配器-属性)上的相同选项。
8 请参阅[<int-websocket:outbound-channel-adapter>](# WebSocket-出站-通道-适配器-属性)上的相同选项。
9 如果信道可以阻塞,则在向信道发送消息时等待的最大时间量(以毫秒为单位)。
例如,如果已达到其最大容量,则QueueChannel可以阻塞直到可用空间。
10 用于从传入的WebSocketMessage转换为目标payload的完全限定的 Java 类型名称。
默认为java.lang.String
11 指示此适配器是否从应用程序上下文向AbstractBrokerMessageHandler发送带有代理目的地的AbstractBrokerMessageHandler实例和消息。
当此属性为true时,需要Broker Relay配置。
此属性仅在服务器端使用。
在客户端,它被忽略。
默认为false
12 请参阅[<int-websocket:outbound-channel-adapter>](# WebSocket-出站-通道-适配器-属性)上的相同选项。
13 参见[<int-websocket:outbound-channel-adapter>](# WebSocket-出站-通道-适配器-属性)上的相同选项。

# 使用ClientStompEncoder

从版本 4.3.13 开始, Spring 集成提供了ClientStompEncoder(作为标准StompEncoder的扩展),用于在 WebSocket 通道适配器的客户端上使用。为了进行正确的客户端消息准备,你必须将ClientStompEncoder的一个实例注入StompSubProtocolHandler中。默认StompSubProtocolHandler的一个问题是,它是为服务器端设计的,因此它将SEND``stompCommand报头更新为MESSAGE(根据服务器端的 stomp 协议的要求)。如果客户机没有在适当的SENDWeb 套接字框架中发送消息,则某些 STOMP 代理不接受它们。在这种情况下,ClientStompEncoder的目的是覆盖stompCommand头并将其设置为SEND值,然后将消息编码为byte[]

# 动态 WebSocket 端点注册

从版本 5.5 开始, WebSocket 服务器端点(基于ServerWebSocketContainer的通道适配器)现在可以在运行时注册(并删除)-映射的pathsaServerWebSocketContainer通过HandlerMapping公开到DispatcherServlet中,并可供 WebSocket 客户端访问。动态和运行时集成流支持有助于以透明的方式注册这些端点:

@Autowired
IntegrationFlowContext integrationFlowContext;

@Autowired
HandshakeHandler handshakeHandler;
...
ServerWebSocketContainer serverWebSocketContainer =
       new ServerWebSocketContainer("/dynamic")
               .setHandshakeHandler(this.handshakeHandler);

WebSocketInboundChannelAdapter webSocketInboundChannelAdapter =
       new WebSocketInboundChannelAdapter(serverWebSocketContainer);

QueueChannel dynamicRequestsChannel = new QueueChannel();

IntegrationFlow serverFlow =
       IntegrationFlows.from(webSocketInboundChannelAdapter)
               .channel(dynamicRequestsChannel)
               .get();

IntegrationFlowContext.IntegrationFlowRegistration dynamicServerFlow =
       this.integrationFlowContext.registration(serverFlow)
               .addBean(serverWebSocketContainer)
               .register();
...
dynamicServerFlow.destroy();
在动态流注册中调用.addBean(serverWebSocketContainer)ServerWebSocketContainer的实例添加到ApplicationContext中以进行端点注册是很重要的,
当动态流注册被破坏时,相关的ServerWebSocketContainer实例也被破坏,以及相应的端点注册也被破坏,包括 URL 路径映射。
动态 WebSocket 端点只能通过 Spring 集成机制进行注册:当使用常规 Spring 时, Spring 集成配置退出,并且不注册用于动态端点的基础设施。