# WebSockets 支持 ## WebSockets 支持 从版本 4.1 开始, Spring 集成有 WebSocket 支持。它基于 Spring 框架的`web-socket`模块中的体系结构、基础设施和 API。因此, Spring WebSocket 的许多组件(例如`SubProtocolHandler`或`WebSocketClient`)和配置选项(例如`@EnableWebSocketMessageBroker`)可以在 Spring 集成内重用。有关更多信息,请参见 Spring 框架参考手册中的[Spring Framework WebSocket Support](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket)章节。 你需要在项目中包含此依赖项: Maven ``` org.springframework.integration spring-integration-websocket 5.5.9 ``` 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`提供了`ClientWebSocketContainer`和`ServerWebSocketContainer`实现。由于[WebSocket API](https://www.jcp.org/en/jsr/detail?id=356)及其在 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 选项(例如`HandshakeHandler`或`SockJS fallback`)内的`ServletWebSocketHandlerRegistry`用于目标供应商 WebSocket 容器。
该注册是通过一个基础设施`WebSocketIntegrationConfigurationInitializer`组件实现的,其执行与`@EnableWebSocket`注释相同的操作。
这意味着,通过使用`@EnableIntegration`(或应用程序上下文中的任何 Spring 集成名称空间),你可以省略`@EnableWebSocket`声明,因为 Spring 集成基础结构检测所有 WebSocket 端点。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ### WebSocket 入站通道适配器 `WebSocketInboundChannelAdapter`实现了`WebSocketSession`交互的接收部分。你必须为它提供`IntegrationWebSocketContainer`,并且适配器将自身注册为`WebSocketListener`以处理传入消息和`WebSocketSession`事件。 | |只有一个`WebSocketListener`可以在`IntegrationWebSocketContainer`中注册。| |---|--------------------------------------------------------------------------------------| 对于 WebSocket 子协议,`WebSocketInboundChannelAdapter`可以配置`SubProtocolHandlerRegistry`作为第二个构造函数参数。适配器委托给`SubProtocolHandlerRegistry`,以确定适用于已接受的`WebSocketSession`的`SubProtocolHandler`,并根据子协议实现将`WebSocketMessage`转换为`Message`。 | |默认情况下,`WebSocketInboundChannelAdapter`仅依赖于 RAW`PassThruSubProtocolHandler`实现,该实现将`WebSocketMessage`转换为`Message`。| |---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| `WebSocketInboundChannelAdapter`只接受并发送到底层集成流`Message`实例,这些实例具有`SimpMessageType.MESSAGE`或空的`simpMessageType`头。所有其他`Message`类型都是通过`ApplicationEvent`实现发出的`SubProtocolHandler`实例来处理的(例如`StompSubProtocolHandler`)。 在服务器端,如果存在`@EnableWebSocketMessageBroker`配置,则可以使用`useBroker = true`选项配置`WebSocketInboundChannelAdapter`。在这种情况下,所有`non-MESSAGE``Message`类型都被委托给所提供的`AbstractBrokerMessageHandler`。此外,如果代理中继配置了目标前缀,那么那些匹配代理目标的消息将路由到`AbstractBrokerMessageHandler`,而不是`WebSocketInboundChannelAdapter`的`outputChannel`。 如果`useBroker = false`并且接收到的消息是`SimpMessageType.CONNECT`类型的,则`WebSocketInboundChannelAdapter`立即向`SimpMessageType.CONNECT_ACK`消息发送`WebSocketSession`,而不将其发送到信道。 | |Spring 的 WebSocket 支持只允许配置一个代理中继。
因此,我们不需要`AbstractBrokerMessageHandler`引用。
它是在应用程序上下文中检测到的。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 有关更多配置选项,请参见[WebSockets 名称空间支持](#web-sockets-namespace)。 ### WebSocket 出站通道适配器 the`WebSocketOutboundChannelAdapter`: 1. 接受 Spring 来自其`MessageChannel`的集成消息 2. 从`MessageHeaders`中确定`WebSocketSession``id` 3. 从提供的`IntegrationWebSocketContainer`检索`WebSocketSession` 4. 从提供的`SubProtocolHandlerRegistry`将`WebSocketMessage`工作的转换和发送委托给相应的`SubProtocolHandler`。 在客户端,`WebSocketSession``id`消息头不是必需的,因为`ClientWebSocketContainer`仅处理单个连接及其`WebSocketSession`分别。 要使用 STOMP 子协议,你应该使用`StompSubProtocolHandler`配置此适配器。然后,你可以使用`StompHeaderAccessor.create(StompCommand…​)`和`MessageBuilder`向此适配器发送任何 Stomp 消息类型,或者只使用`HeaderEnricher`(参见[页眉 Enricher](./content-enrichment.html#header-enricher))。 本章的其余部分主要介绍了附加的配置选项。 ### WebSockets 名称空间支持 Spring 集成 WebSocket 命名空间包括在本章的其余部分中描述的几个组件。要将其包含在配置中,请在应用程序上下文配置文件中使用以下名称空间声明: ``` ... ``` #### ``属性 下面的清单显示了``元素可用的属性: ``` (9) (10) ``` |**1** |组件 Bean 名称。| |------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |**2** |参考文献`WebSocketClient` Bean。| |**3** |将`uri`或`uriTemplate`发送到目标 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](#web-socket-inbound-adapter)启动的。| |**9** |这个端点应该在其中开始和停止的生命周期阶段。
值越低,这个端点开始得越早,停止得越晚。
默认值是`Integer.MAX_VALUE`。
值可以是负数。
参见[`SmartLifeCycle`(https:/DOCS. Spring.io/ Spring//DOCS/current/javadoc-api/org/api/context/smartlifycle.html)。| |**10**|a`Map`of`HttpHeaders`用于握手请求。| #### ``属性 下面的清单显示了``元素可用的属性: ``` (8) (19) ``` |**1** |组件 Bean 名称。| |------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |**2** |一种将特定请求映射到`WebSocketHandler`的路径(或以逗号分隔的路径)。
支持精确的路径映射 URI(例如`/myPath`)和 Ant 样式的路径模式(例如`/myPath/**`)。| |**3** |`HandshakeHandler` Bean 引用.
默认为`DefaultHandshakeHandler`。| |**4** |参考文献列表`HandshakeInterceptor` Bean。| |**5** |用于修饰用于处理 WebSocket 消息的处理程序的一个或多个工厂的列表(`WebSocketHandlerDecoratorFactory`)。
这对于某些高级用例(例如,以允许 Spring 安全性在相应的 HTTP会话过期时强制关闭 WebSocket 会话。有关更多信息,请参见。| |**6** |请参阅[``](# WebSocket-client-container-attributes)上的相同选项。| |**7** |请参阅[``](# WebSocket-client-container-attributes)上的相同选项。| |**8** |允许的源头值。
你可以将多个源代码指定为逗号分隔的列表。
此检查主要是为浏览器客户端设计的。
没有什么可以阻止其他类型的客户端修改源头值。
当启用 Sockjs 并且限制允许的源代码时,不使用起源头来处理跨起源请求(`jsonp-polling`,`iframe-xhr-polling`,`iframe-eventsource`,和`iframe-htmlfile`)的传输类型将被禁用。因此,不支持 IE6 和 IE7,并且只支持 IE8 和 IE9 而不支持 cookie。
默认情况下,所有起源都是允许的。| |**9** |没有本机跨域通信的传输(例如`eventsource`和`htmlfile`)必须在不可见的 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`值`/info`Endpoint.
此属性指示应用程序是否需要`JSESSIONID`cookie 才能正常工作(例如,用于负载平衡或在 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`。| #### ``属性 下面的清单显示了``元素可用的属性: ``` (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`。
默认转换器(按顺序排列):`StringMessageConverter`,`ByteArrayMessageConverter`,和(如果 Jackson 库存在于 Classpath 上)。| |**8**|布尔值,表示此端点是否应自动启动。
默认为`true`。| |**9**|这个端点应该在其中开始和停止的生命周期阶段。
值越低,这个端点开始得越早,停止得越晚。
默认值是`Integer.MIN_VALUE`。
值可以是负数。
参见[`SmartLifeCycle`(https:/DOCS. Spring.io/ Spring/DOCS/current/javadoc-apf/org/api/context/smartlifycle.html)。| #### ``属性 下面的清单显示了``元素可用的属性: ``` (13) ``` |**1** |组件 Bean name.
如果不设置`channel`属性,则在应用程序上下文中创建并注册一个`DirectChannel`,并将该`id`属性作为 Bean 名称。
在本例中,端点以 Bean 名称`id`加上`.adapter`进行注册。| |------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |**2** |标识连接到此适配器的通道。| |**3** |应该发送`ErrorMessage`实例的`MessageChannel` Bean 引用。| |**4** |请参阅[``](# WebSocket-出站-通道-适配器-属性)上的相同选项。| |**5** |请参阅[``](# WebSocket-出站-通道-适配器-属性)上的相同选项。| |**6** |请参阅[``](# WebSocket-出站-通道-适配器-属性)上的相同选项。| |**7** |请参阅[``](# WebSocket-出站-通道-适配器-属性)上的相同选项。| |**8** |请参阅[``](# WebSocket-出站-通道-适配器-属性)上的相同选项。| |**9** |如果信道可以阻塞,则在向信道发送消息时等待的最大时间量(以毫秒为单位)。
例如,如果已达到其最大容量,则`QueueChannel`可以阻塞直到可用空间。| |**10**|用于从传入的`WebSocketMessage`转换为目标`payload`的完全限定的 Java 类型名称。
默认为`java.lang.String`。| |**11**|指示此适配器是否从应用程序上下文向`AbstractBrokerMessageHandler`发送带有代理目的地的`AbstractBrokerMessageHandler`实例和消息。
当此属性为`true`时,需要`Broker Relay`配置。
此属性仅在服务器端使用。
在客户端,它被忽略。
默认为`false`。| |**12**|请参阅[``](# WebSocket-出站-通道-适配器-属性)上的相同选项。| |**13**|参见[``](# WebSocket-出站-通道-适配器-属性)上的相同选项。| ### 使用`ClientStompEncoder` 从版本 4.3.13 开始, Spring 集成提供了`ClientStompEncoder`(作为标准`StompEncoder`的扩展),用于在 WebSocket 通道适配器的客户端上使用。为了进行正确的客户端消息准备,你必须将`ClientStompEncoder`的一个实例注入`StompSubProtocolHandler`中。默认`StompSubProtocolHandler`的一个问题是,它是为服务器端设计的,因此它将`SEND``stompCommand`报头更新为`MESSAGE`(根据服务器端的 stomp 协议的要求)。如果客户机没有在适当的`SEND`Web 套接字框架中发送消息,则某些 STOMP 代理不接受它们。在这种情况下,`ClientStompEncoder`的目的是覆盖`stompCommand`头并将其设置为`SEND`值,然后将消息编码为`byte[]`。 ### 动态 WebSocket 端点注册 从版本 5.5 开始, WebSocket 服务器端点(基于`ServerWebSocketContainer`的通道适配器)现在可以在运行时注册(并删除)-映射的`paths`a`ServerWebSocketContainer`通过`HandlerMapping`公开到`DispatcherServlet`中,并可供 WebSocket 客户端访问。[动态和运行时集成流](./dsl.html#java-dsl-runtime-flows)支持有助于以透明的方式注册这些端点: ``` @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 集成配置退出,并且不注册用于动态端点的基础设施。| |---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|