|
对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
响应式核心
这spring-web模块包含以下对响应式 Web 的基础支持
应用:
-
对于服务器请求处理,有两个级别的支持。
-
HttpHandler:HTTP 请求处理的基本合约 非阻塞 I/O 和反应流背压,以及用于 Reactor Netty 的适配器, Undertow、Tomcat、Jetty 和任何 Servlet 容器。
-
WebHandler应用程序接口:稍高级别的通用 Web API 请求处理,在此基础上有具体的编程模型,例如 Commentted 构建控制器和功能端点。
-
-
对于客户端,有一个基本的
ClientHttpConnector执行 HTTP 的合约 具有非阻塞 I/O 和响应式流背压的请求,以及 Reactor Netty、响应式 Jetty HttpClient 和 Apache HttpComponents 的适配器。 应用程序中使用的更高级别的 WebClient 建立在这个基本契约的基础上。 -
对于客户端和服务器,用于序列化的编解码器和 HTTP 请求和响应内容的反序列化。
HttpHandler
HttpHandler 是一个简单的合约,具有处理请求和响应的单一方法。是的 故意最小化,其主要和唯一目的是成为最小抽象 通过不同的 HTTP 服务器 API。
下表介绍了支持的服务器 API:
| 服务器名称 | 使用的服务器 API | 响应式流支持 |
|---|---|---|
网 |
Netty API |
|
Undertow |
暗流 API |
spring-web:Undertow 到 Reactive Streams 桥接器 |
Tomcat |
Servlet 非阻塞 I/O;用于读写 ByteBuffers 的 Tomcat API 与 byte[] |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥接器 |
Jetty |
Servlet 非阻塞 I/O;用于编写 ByteBuffers 的 Jetty API 与 byte[] |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥接器 |
Servlet 容器 |
Servlet 非阻塞 I/O |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥接器 |
下表描述了服务器依赖项(另请参阅支持的版本):
| 服务器名称 | 组 ID | 神器名称 |
|---|---|---|
反应器网 |
io.projectreactor.netty |
反应器网 |
Undertow |
io.undertow |
暗拖核心 |
Tomcat |
org.apache.tomcat.embed |
tomcat-嵌入核心 |
Jetty |
org.eclipse.Jetty |
jetty-server, jetty-servlet |
下面的代码片段显示了使用HttpHandler适配器与每个服务器 API 一起使用:
反应器网
-
Java
-
Kotlin
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bindNow();
val handler: HttpHandler = ...
val adapter = ReactorHttpHandlerAdapter(handler)
HttpServer.create().host(host).port(port).handle(adapter).bindNow()
Undertow
-
Java
-
Kotlin
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
val handler: HttpHandler = ...
val adapter = UndertowHttpHandlerAdapter(handler)
val server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build()
server.start()
Tomcat
-
Java
-
Kotlin
HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);
Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();
val handler: HttpHandler = ...
val servlet = TomcatHttpHandlerAdapter(handler)
val server = Tomcat()
val base = File(System.getProperty("java.io.tmpdir"))
val rootContext = server.addContext("", base.absolutePath)
Tomcat.addServlet(rootContext, "main", servlet)
rootContext.addServletMappingDecoded("/", "main")
server.host = host
server.setPort(port)
server.start()
Jetty
-
Java
-
Kotlin
HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);
Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();
ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();
val handler: HttpHandler = ...
val servlet = JettyHttpHandlerAdapter(handler)
val server = Server()
val contextHandler = ServletContextHandler(server, "")
contextHandler.addServlet(ServletHolder(servlet), "/")
contextHandler.start();
val connector = ServerConnector(server)
connector.host = host
connector.port = port
server.addConnector(connector)
server.start()
Servlet 容器
要作为 WAR 部署到任何 Servlet 容器,您可以扩展并包含AbstractReactiveWebInitializer在战争中。该类包装了HttpHandler跟ServletHttpHandlerAdapter和寄存器
作为Servlet.
WebHandler应用程序接口
这org.springframework.web.server包构建在HttpHandler合同
提供通用 Web API,用于通过多个链处理请求WebExceptionHandler倍数WebFilter,并且单个WebHandler元件。链条可以
与WebHttpHandlerBuilder只需指向弹簧ApplicationContext其中组件是自动检测的,和/或通过注册组件
与架构商。
而HttpHandler有一个简单的目标,即抽象使用不同的 HTTP 服务器,WebHandlerAPI 旨在提供 Web 应用程序中常用的更广泛的功能集
如:
-
具有属性的用户会话。
-
请求属性。
-
解决
Locale或Principal对于请求。 -
访问解析和缓存的表单数据。
-
多部分数据的抽象。
-
以及更多..
特殊Beans
下表列出了以下组件:WebHttpHandlerBuilder可以在
Spring ApplicationContext,或者可以直接向它注册:
| 豆子名称 | Beans | 计数 | 描述 |
|---|---|---|---|
<任何> |
|
0..N |
提供对来自 |
<任何> |
|
0..N |
将拦截样式逻辑应用于过滤器链的其余部分之前和之后,以及目标 |
|
|
1 |
请求的处理程序。 |
|
|
0..1 |
经理 |
|
|
0..1 |
用于访问 |
|
|
0..1 |
的解析器 |
|
|
0..1 |
用于处理转发的类型标头,通过提取和删除它们或仅删除它们。 默认情况下不使用。 |
表单数据
ServerWebExchange公开了以下访问表单数据的方法:
-
Java
-
Kotlin
Mono<MultiValueMap<String, String>> getFormData();
suspend fun getFormData(): MultiValueMap<String, String>
这DefaultServerWebExchange使用配置的HttpMessageReader解析表单数据
(application/x-www-form-urlencoded) 转换为MultiValueMap.默认情况下,FormHttpMessageReader配置为ServerCodecConfigurer豆
(请参阅 Web 处理程序 API)。
多部分数据
ServerWebExchange公开了以下用于访问多部分数据的方法:
-
Java
-
Kotlin
Mono<MultiValueMap<String, Part>> getMultipartData();
suspend fun getMultipartData(): MultiValueMap<String, Part>
这DefaultServerWebExchange使用配置的HttpMessageReader<MultiValueMap<String, Part>>解析multipart/form-data,multipart/mixed和multipart/related内容转换为MultiValueMap.
默认情况下,这是DefaultPartHttpMessageReader,没有任何第三方
依赖。
或者,SynchronossPartHttpMessageReader可以使用,它基于 Synchronoss NIO Multipart 库。
两者都是通过ServerCodecConfigurer豆
(请参阅 Web 处理程序 API)。
要以流式方式解析多部分数据,您可以使用Flux<PartEvent>从PartEventHttpMessageReader而不是使用@RequestPart,正如这意味着的那样Map-类似访问
按名称添加到各个部分,因此需要完整解析多部分数据。
相比之下,您可以使用@RequestBody将内容解码为Flux<PartEvent>没有
收集到MultiValueMap.
请求头转发
当请求通过代理(例如负载均衡器)时,主机、端口和 方案可能会发生变化,这使得创建指向正确链接成为一项挑战 从客户端的角度来看,主机、端口和方案。
RFC 7239 定义了ForwardedHTTP 标头
代理可以使用该信息来提供有关原始请求的信息。
非标准标头
还有其他非标准标头,包括X-Forwarded-Host,X-Forwarded-Port,X-Forwarded-Proto,X-Forwarded-Ssl和X-Forwarded-Prefix.
X转发主机
虽然不是标准的,X-Forwarded-Host: <host>是一个事实上的标准标头,用于将原始主机传达给
下游服务器。例如,如果请求example.com/resource被发送到
将请求转发给localhost:8080/resource,然后是X-Forwarded-Host: example.com可以发送以通知服务器原始主机是example.com.
X转发端口
虽然不是标准的,X-Forwarded-Port: <port>是一个事实上的标准标头,用于
将原始端口通信到下游服务器。例如,如果请求example.com/resource被发送到代理,代理将请求转发给localhost:8080/resource,然后是X-Forwarded-Port: 443可以发送
通知服务器原始端口是443.
X-转发-原型
虽然不是标准的,X-Forwarded-Proto: (https|http)是用于通信原始协议(例如 https / https)的事实上的标准标头
到下游服务器。例如,如果请求example.com/resource被发送到
将请求转发给localhost:8080/resource,然后是X-Forwarded-Proto: https可以发送以通知服务器原始协议是https.
X-转发-SSL
虽然不是标准的,X-Forwarded-Ssl: (on|off)是一个事实上的标准标头,用于传达
原始协议(例如 HTTPS/HTTPS)到下游服务器。例如,如果请求example.com/resource被发送到代理,代理将请求转发给localhost:8080/resource,然后是X-Forwarded-Ssl: on通知服务器
最初的协议是https.
X转发前缀
虽然不是标准的,X-Forwarded-Prefix: <prefix>是一个事实上的标准标头,用于将原始 URL 路径前缀传达给
下游服务器。
用途X-Forwarded-Prefix可能因部署方案而异,并且需要灵活地
允许替换、删除或预置目标服务器的路径前缀。
方案 1:覆盖路径前缀
https://example.com/api/{path} -> http://localhost:8080/app1/{path}
前缀是捕获组之前路径的开头{path}.对于代理,
前缀是/api而对于服务器,前缀是/app1.在这种情况下,代理
可以发送X-Forwarded-Prefix: /api具有原始前缀/api覆盖
服务器前缀/app1.
方案 2:删除路径前缀
有时,应用程序可能希望删除前缀。例如,考虑 以下代理到服务器的映射:
https://app1.example.com/{path} -> http://localhost:8080/app1/{path}
https://app2.example.com/{path} -> http://localhost:8080/app2/{path}
代理没有前缀,而应用程序app1和app2有路径前缀/app1和/app2分别。代理可以发送X-Forwarded-Prefix: 自
让空前缀覆盖服务器前缀/app1和/app2.
|
此部署方案的一个常见情况是按按 生产应用程序服务器,最好在每个应用程序中部署多个应用程序 服务器以降低费用。另一个原因是在同一台服务器上运行更多应用程序 以共享服务器运行所需的资源。 在这些方案中,应用程序需要一个非空上下文根,因为有多个 同一服务器上的应用程序。但是,这在 公共 API,应用程序可以使用提供好处的不同子域 如:
|
方案 3:插入路径前缀
在其他情况下,可能需要在前面添加前缀。例如,考虑 以下代理到服务器的映射:
https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}
在这种情况下,代理的前缀为/api/app1服务器的前缀为/app1.代理可以发送X-Forwarded-Prefix: /api/app1具有原始前缀/api/app1覆盖服务器前缀/app1.
转发标头转换器
ForwardedHeaderTransformer是修改主机、端口和方案的组件
请求,然后删除这些标头。如果您声明
它作为一个豆子,名字forwardedHeaderTransformer,它将被检测和使用。
在 5.1 中ForwardedHeaderFilter已被弃用并被ForwardedHeaderTransformer因此,请求头转发可以更早地处理,在
交换被创建。如果仍然配置了过滤器,则它将从
过滤器,以及ForwardedHeaderTransformer而是使用。 |
过滤器
在WebHandler应用程序接口,您可以使用WebFilter应用拦截样式
过滤器和目标处理链其余部分之前和之后的逻辑WebHandler.使用 WebFlux Config 时,注册WebFilter就这么简单
将其声明为 Spring bean 并(可选)通过使用@Order上
bean 声明或通过实现Ordered.
CORS
Spring WebFlux 通过对 CORS 配置的注释提供细粒度的支持
控制器。但是,当您将其与 Spring Security 一起使用时,我们建议依赖内置的CorsFilter,必须在 Spring Security 的过滤器链之前订购。
请参阅有关 CORS 和CORSWebFilter了解更多详情。
异常
在WebHandler应用程序接口,您可以使用WebExceptionHandler处理
链中的例外WebFilter实例和目标WebHandler.使用 WebFlux Config 时,注册WebExceptionHandler就像将其声明为
Spring bean 和(可选)通过使用@Order在 bean 声明上或
通过实现Ordered.
下表描述了可用的WebExceptionHandler实现:
| 异常处理程序 | 描述 |
|---|---|
|
提供对类型 |
|
扩展 此处理程序在 WebFlux 配置中声明。 |
Codec
这spring-web和spring-core模块支持序列化和
通过非阻塞 I/O 将字节内容反序列化到更高级别对象或从更高级别对象反序列化
反应流背压。下面介绍了此支持:
-
HttpMessageReader和HttpMessageWriter是合同 对 HTTP 消息内容进行编码和解码。 -
一
Encoder可以用EncoderHttpMessageWriter使其适应在 Web 中使用 应用程序,而Decoder可以用DecoderHttpMessageReader. -
DataBuffer摘要不同 字节缓冲区表示(例如 NettyByteBuf,java.nio.ByteBuffer等)并且是 所有编解码器的工作原理。请参阅 数据缓冲区和编解码器 有关此主题的更多信息,请参阅“Spring Core”部分。
这spring-core模块提供byte[],ByteBuffer,DataBuffer,Resource和String编码器和解码器实现。这spring-web模块提供 Jackson
JSON、Jackson Smile、JAXB2、协议缓冲区和其他编码器和解码器以及
表单数据、多部分内容、
服务器发送的事件等。
ClientCodecConfigurer和ServerCodecConfigurer通常用于配置和
自定义要在应用程序中使用的编解码器。请参阅有关配置 HTTP 消息编解码器的部分。
Jackson JSON
JSON 和二进制 JSON(微笑)是 当 Jackson 库存在时,两者都受支持。
这Jackson2Decoder工作原理如下:
-
Jackson 的异步、非阻塞解析器用于聚合字节块流 到
TokenBuffer的每个代表一个 JSON 对象。 -
每
TokenBuffer传给Jackson的ObjectMapper以创建更高级别的对象。 -
当解码到单值发布者(例如
Mono),有一个TokenBuffer. -
解码到多值发布者时(例如
Flux),每个TokenBuffer被传递给 这ObjectMapper一旦收到完全格式对象的足够字节。这 输入内容可以是 JSON 数组,也可以是任何以行分隔的 JSON 格式,例如 NDJSON、 JSON 行或 JSON 文本序列。
这Jackson2Encoder工作原理如下:
-
对于单一价值发布商(例如
Mono),只需通过ObjectMapper. -
对于具有
application/json,默认情况下使用Flux#collectToList(),然后序列化生成的集合。 -
对于具有流媒体类型(如
application/x-ndjson或application/stream+x-jackson-smile、编码、写入和 使用行分隔的 JSON 格式单独刷新每个值。其他 流媒体类型可以注册到编码器。 -
对于 SSE,
Jackson2Encoder每个事件调用,并刷新输出以确保 立即交货。
|
默认情况下,两者 |
表单数据
FormHttpMessageReader和FormHttpMessageWriter支持解码和编码application/x-www-form-urlencoded内容。
在经常需要从多个地方访问表单内容的服务器端,ServerWebExchange提供专用的getFormData()解析内容的方法
通过FormHttpMessageReader然后缓存结果以供重复访问。
请参阅WebHandler应用程序接口部分。
一次getFormData()使用时,无法再从
请求正文。因此,申请应通过ServerWebExchange一致地访问缓存的表单数据,而不是从原始请求正文中读取。
多部分
MultipartHttpMessageReader和MultipartHttpMessageWriter支持解码和
对“multipart/form-data”、“multipart/mixed”和“multipart/related”内容进行编码。
挨次MultipartHttpMessageReader委托给另一个HttpMessageReader用于实际解析为Flux<Part>然后简单地将这些部件收集到MultiValueMap.
默认情况下,DefaultPartHttpMessageReader,但这可以通过ServerCodecConfigurer.
有关DefaultPartHttpMessageReader,请参阅javadoc 的DefaultPartHttpMessageReader.
在服务器端,可能需要从多个
地方ServerWebExchange提供专用的getMultipartData()解析的方法
内容通过MultipartHttpMessageReader然后缓存结果以供重复访问。
请参阅WebHandler应用程序接口部分。
一次getMultipartData()使用时,无法再从
请求正文。因此,应用程序必须始终使用getMultipartData()用于重复、类似地图的部件访问,或以其他方式依赖SynchronossPartHttpMessageReader一次性访问Flux<Part>.
限制
Decoder和HttpMessageReader缓冲部分或全部输入的实现
stream 可以配置对内存中缓冲的最大字节数进行限制。
在某些情况下,缓冲是因为输入被聚合并表示为单个
object — 例如,具有@RequestBody byte[],x-www-form-urlencoded数据,依此类推。缓冲也可能发生在流式处理中,当
拆分输入流 - 例如,分隔文本、JSON 对象流和
依此类推。对于这些流式处理情况,限制适用于关联的字节数
在流中有一个对象。
要配置缓冲区大小,您可以检查给定的Decoder或HttpMessageReader公开一个maxInMemorySize属性,如果是这样,Javadoc 将包含有关默认值的详细信息
值。在服务器端,ServerCodecConfigurer提供从哪里到
设置所有编解码器,请参阅 HTTP 消息编解码器。在客户端,限制
所有编解码器都可以在 WebClient.Builder 中更改。
对于多部分解析maxInMemorySize属性限制
非文件部件的大小。对于文件部件,它确定部件的阈值
写入磁盘。对于写入磁盘的文件部分,还有一个额外的maxDiskUsagePerPart属性来限制每个部分的磁盘空间量。还有
一个maxParts属性来限制多部分请求中的部分总数。
要在 WebFlux 中配置所有三个,您需要提供一个预配置的实例MultipartHttpMessageReader自ServerCodecConfigurer.
流
流式传输到 HTTP 响应时(例如text/event-stream,application/x-ndjson),定期发送数据非常重要,以便
尽早可靠地检测断开连接的客户端。这样的发送可能是
仅注释、空 SSE 事件或任何其他“无作”数据,这些数据将有效地充当
心跳。
DataBuffer
DataBuffer是 WebFlux 中字节缓冲区的表示。的 Spring Core 部分此参考在数据缓冲区和编解码器部分中有更多相关内容。要理解的关键点是,在某些像 Netty 这样的服务器上,字节缓冲区被池化并对引用进行计数,并且必须释放当使用以避免内存泄漏时。
WebFlux 应用程序通常不需要关注此类问题,除非它们 直接使用或生成数据缓冲区,而不是依赖编解码器进行转换 以及来自更高级别的对象,或者除非他们选择创建自定义编解码器。对于这样的 情况请查看数据缓冲区和编解码器中的信息, 尤其是关于使用 DataBuffer 的部分。
Logging
DEBUGSpring WebFlux 中的级别日志记录被设计为紧凑、最小和
人性化。它侧重于对
与其他仅在调试特定问题时有用的其他问题相比。
TRACE关卡日志记录通常遵循与DEBUG(例如,还
不应是消防水带),但可用于调试任何问题。此外,一些日志
消息可能会在TRACE与DEBUG.
良好的日志记录来自使用日志的经验。如果你发现任何可以 未达到既定目标,请告诉我们。
日志 ID
在 WebFlux 中,单个请求可以在多个线程上运行,并且线程 ID 对于关联属于特定请求的日志消息没有用处。这就是为什么 默认情况下,WebFlux 日志消息以特定于请求的 ID 为前缀。
在服务器端,日志 ID 存储在ServerWebExchange属性
(LOG_ID_ATTRIBUTE),
而基于该 ID 的完整格式化前缀可从ServerWebExchange#getLogPrefix().在WebClientside,日志 ID 存储在ClientRequest属性
(LOG_ID_ATTRIBUTE)
,而完全格式化的前缀可从ClientRequest#logPrefix().
敏感数据
DEBUG和TRACE日志记录可以记录敏感信息。这就是为什么 form 参数和
默认情况下,标头是屏蔽的,您必须显式启用其完全日志记录。
以下示例显示了如何对服务器端请求执行此作:
-
Java
-
Kotlin
@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().enableLoggingRequestDetails(true);
}
}
@Configuration
@EnableWebFlux
class MyConfig : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
configurer.defaultCodecs().enableLoggingRequestDetails(true)
}
}
以下示例演示如何对客户端请求执行此作:
-
Java
-
Kotlin
Consumer<ClientCodecConfigurer> consumer = configurer ->
configurer.defaultCodecs().enableLoggingRequestDetails(true);
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies -> strategies.codecs(consumer))
.build();
val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) }
val webClient = WebClient.builder()
.exchangeStrategies({ strategies -> strategies.codecs(consumer) })
.build()
自定义编解码器
应用程序可以注册自定义编解码器以支持其他媒体类型, 或默认编解码器不支持的特定行为。
以下示例演示如何对客户端请求执行此作:
-
Java
-
Kotlin
WebClient webClient = WebClient.builder()
.codecs(configurer -> {
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();
val webClient = WebClient.builder()
.codecs({ configurer ->
val decoder = CustomDecoder()
configurer.customCodecs().registerWithDefaultConfig(decoder)
})
.build()