SpringWebFlux
-5(5.3.12版学习笔记2021.11.10)
3.0 WebSockets (自行看官网 、中文文档)
3.1.2. 何时使用 WebSockets
WebSockets 可以使网页具有动态性和交互性。但是,在许多情况下,结合使用 Ajax 和 HTTP 流或长时间轮询可以提供一种简单有效的解决方案。
例如,新闻,邮件和社交订阅源需要动态更新,但是每隔几分钟这样做是完全可以的。另一方面,协作,游戏和金融应用程序需要更接近实时。
仅延迟并不是决定因素。如果消息量相对较少(例如,监视网络故障),则 HTTP 流或轮询可以提供有效的解决方案。低延迟,高频率和高音量的结合才是使用 WebSocket 的最佳案例。
还请记住,在 Internet 上,控件之外的限制性代理可能会阻止 WebSocket 交互,这可能是因为未将它们配置为传递
Upgrade
Headers,或者是因为它们关闭了长期处于空闲状态的连接。这意味着与面向公众的应用程序相比,将 WebSocket 用于防火墙内部的应用程序是一个更直接的决定。
3.2. WebSocket API
Spring 框架提供了一个 WebSocket API,可用于编写处理 WebSocket 消息的 Client 端和服务器端应用程序。
3.2.1. Server
要创建 WebSocket 服务器,您可以先创建一个
WebSocketHandler
。以下示例显示了如何执行此操作:
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
// ...
}
}
然后,您可以将其 Map 到 URL 并添加
WebSocketHandlerAdapter
,如以下示例所示:
@Configuration
static class WebConfig {
@Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/path", new MyWebSocketHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1); // before annotated controllers
return mapping;
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
3.2.2. WebSocketHandler
WebSocketHandler
的handle
方法采用WebSocketSession
并返回Mono<Void>
来指示会话的应用程序处理何时完成。通过两个流处理会话,一个流用于入站消息,一个流用于出站消息。下表描述了两种处理流的方法:
WebSocketSession 方法 | Description |
---|---|
Flux<WebSocketMessage> receive() | 提供对入站消息流的访问,并在关闭连接后完成。 |
Mono<Void> send(Publisher<WebSocketMessage>) | 获取传出消息的源,编写消息,然后返回Mono<Void> ,该源完成并写入后即完成。 |
WebSocketHandler
必须将入站和出站流组成一个统一的流,并返回Mono<Void>
以反映该流的完成。根据应用程序要求,统一流程在以下情况下完成:
- 入站或出站消息流完成。
- 入站流完成(即,连接已关闭),而出站流是无限的。
- 在选定的位置,通过
WebSocketSession
的close
方法。
将入站和出站消息流组合在一起时,无需检查连接是否打开,因为“响应流”signal 会终止活动。入站流接收完成或错误 signal,而出站流接收取消 signal。
处理程序最基本的实现是处理入站流的实现。以下示例显示了这样的实现:
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
return session.receive() (1 =访问入站消息流)
.doOnNext(message -> {
// ... (2 =对每条消息进行处理)
})
.concatMap(message -> {
// ... (3 =执行使用消息内容的嵌套异步操作)
})
.then(); (4 =返回一个Mono<Void>,它在接收完成时会完成)
}
}
对于嵌套的异步操作,您可能需要在使用池化数据缓冲区(例如 Netty)的基础服务器上调用
message.retain()
。否则,在您有机会读取数据之前,可能会释放数据缓冲区。有关更多背景信息,请参见数据缓冲区和编解码器。以下实现将入站和出站流组合在一起:
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Flux<WebSocketMessage> output = session.receive() (1 = 处理入站消息流)
.doOnNext(message -> {
// ...
})
.concatMap(message -> {
// ...
})
.map(value -> session.textMessage("Echo " + value)); (2 = 创建出站消息,从而产生组合流)
return session.send(output); (3 = 返回Mono<Void>,但我们 continue 接收时未完成)
}
}
入站和出站流可以是独立的,并且只能为了完成而加入,如以下示例所示:
class ExampleHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> input = session.receive() (1 = 处理入站消息流)
.doOnNext(message -> {
// ...
})
.concatMap(message -> {
// ...
})
.then();
Flux<String> source = ... ;
Mono<Void> output = session.send(source.map(session::textMessage)); (2 =发送传出邮件)
return Mono.zip(input, output).then(); (3 = 加入流并返回一个Mono<Void>,当任何一个流结束时,该Mono<Void>完成)
}
}
3.2.3. DataBuffer
DataBuffer
是 WebFlux 中字节缓冲区的表示形式。参考的 Spring Core 部分在数据缓冲区和编解码器的部分中有更多内容。要理解的关键点是,在诸如 Netty 之类的某些服务器上,字节缓冲区被池化并引用计数,并且在消耗字节缓冲区时必须将其释放以避免内存泄漏。在 Netty 上运行时,如果应用程序希望保留 Importing 数据缓冲区以确保它们不被释放,则必须使用
DataBufferUtils.retain(dataBuffer)
,然后在使用缓冲区时使用DataBufferUtils.release(dataBuffer)
。
3.2.4. Handshake (握手)
WebSocketHandlerAdapter
代表WebSocketService
。默认情况下,它是HandshakeWebSocketService
的实例,该实例对 WebSocket 请求执行基本检查,然后将RequestUpgradeStrategy
用于所使用的服务器。当前,内置了对 Reactor Netty,Tomcat,Jetty 和 Undertow 的支持。
HandshakeWebSocketService
公开了sessionAttributePredicate
属性,该属性允许设置Predicate<String>
以从WebSession
提取属性并将其插入WebSocketSession
的属性。
3.2.5. 服务器配置
RequestUpgradeStrategy
每个服务器公开配置的具体到下面的WebSocket服务器引擎。使用 WebFlux Java 配置时,您可以自定义此类属性,如WebFlux 配置的相应部分所示 ,否则,如果不使用 WebFlux 配置, 使用以下示例在 Tomcat 上运行时设置 WebSocket 选项:
@Configuration
static class WebConfig {
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter(webSocketService());
}
@Bean
public WebSocketService webSocketService() {
TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
strategy.setMaxSessionIdleTimeout(0L);
return new HandshakeWebSocketService(strategy);
}
}
检查服务器的升级策略,以查看可用的选项。当前,只有 Tomcat 和 Jetty 公开了此类选项。
3.2.6. CORS
配置 CORS 并限制对 WebSocket 端点的访问的最简单方法是让
WebSocketHandler
实现CorsConfigurationSource
并返回CorsConfiguraiton
并带有允许的来源,Headers 和其他详细信息。如果您不能这样做,则还可以在SimpleUrlHandler
上设置corsConfigurations
属性,以通过 URL 模式指定 CORS 设置。如果同时指定了两者,则使用CorsConfiguration
上的combine
方法将它们组合在一起。
3.2.7. Client (客户端)
Spring WebFlux 为 Reactor Netty,Tomcat,Jetty,Undertow 和标准 Java(即 JSR-356)的实现提供了
WebSocketClient
抽象。TomcatClient 端实际上是标准 JavaClient 端的扩展,在
WebSocketSession
处理中具有一些额外功能,以利用特定于 Tomcat 的 API 暂停接收消息以产生反压。要启动 WebSocket 会话,您可以创建 Client 端的实例并使用其
execute
方法:
WebSocketClient client = new ReactorNettyWebSocketClient();
URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
session.receive()
.doOnNext(System.out::println)
.then());
某些 Client 端(例如 Jetty)实现
Lifecycle
,并且需要先停止然后启动,然后才能使用它们。所有 Client 端都具有与基础 WebSocketClient 端的配置有关的构造器选项。
4. Testing (测试)
spring-test
模块提供ServerHttpRequest
,ServerHttpResponse
和ServerWebExchange
的模拟实现。有关模拟对象的讨论,请参见Spring WebReactive。WebTestClient构建在这些模拟请求和响应对象的基础上,以提供对不使用 HTTP 服务器的 WebFlux 应用程序测试的支持。您也可以使用
WebTestClient
进行端到端集成测试。
5.RSocket (详情看官网)
本节介绍 Spring Framework 对 RSocket 协议的支持。
5.1. 概述
RSocket 是一种应用协议,用于通过 TCP、WebSocket 和其他字节流传输进行多路复用、双工通信,使用以下交互模型之一:
Request-Response
— 发送一条消息并收到一条消息。Request-Stream
— 发送一条消息并接收返回的消息流。Channel
— 双向发送消息流。Fire-and-Forget
— 发送单向消息。
一旦建立了初始连接,“客户端”与“服务器”的区别就会消失,因为双方变得对称并且每一方都可以发起上述交互之一。这就是为什么在协议中将参与方称为“请求者”和“响应者”,而将上述交互称为“请求流”或简称为“请求”。