对于最新的稳定版本,请使用 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 服务器,WebHandler
API 旨在提供 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 定义了Forwarded
HTTP 标头
代理可以使用该信息来提供有关原始请求的信息。
非标准标头
还有其他非标准标头,包括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
DEBUG
Spring WebFlux 中的级别日志记录被设计为紧凑、最小和
人性化。它侧重于对
与其他仅在调试特定问题时有用的其他问题相比。
TRACE
关卡日志记录通常遵循与DEBUG
(例如,还
不应是消防水带),但可用于调试任何问题。此外,一些日志
消息可能会在TRACE
与DEBUG
.
良好的日志记录来自使用日志的经验。如果你发现任何可以 未达到既定目标,请告诉我们。
日志 ID
在 WebFlux 中,单个请求可以在多个线程上运行,并且线程 ID 对于关联属于特定请求的日志消息没有用处。这就是为什么 默认情况下,WebFlux 日志消息以特定于请求的 ID 为前缀。
在服务器端,日志 ID 存储在ServerWebExchange
属性
(LOG_ID_ATTRIBUTE
),
而基于该 ID 的完整格式化前缀可从ServerWebExchange#getLogPrefix()
.在WebClient
side,日志 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()