该模块包含以下对响应式 Web 的基础支持
应用:spring-web
-
对于服务器请求处理,有两个级别的支持。
-
HttpHandler:用于处理 HTTP 请求的基本协定 非阻塞 I/O 和无功流背压,以及用于 Reactor Netty 的适配器, Undertow、Tomcat、Jetty 和任何 Servlet 容器。
-
WebHandler
API:级别稍高的通用 Web API,用于 请求处理,在此之上进行具体的编程模型,例如 Annotated 构建控制器和功能端点。
-
-
对于客户端,有一个基本的合约来执行 HTTP 具有非阻塞 I/O 和 Reactive Streams 背压的请求,以及用于 Reactor Netty、Reactive Jetty HttpClient 和 Apache HttpComponents 的适配器。 应用程序中使用的更高级别的 WebClient 建立在这个基本契约之上。
ClientHttpConnector
-
对于客户端和服务器,用于序列化和 HTTP 请求和响应内容的反序列化。
HttpHandler
HttpHandler 是一个简单的协定,具有处理请求和响应的单一方法。是的 有意最小化,其主要和唯一目的是成为最小抽象 在不同的 HTTP 服务器 API 上。
下表描述了支持的服务器 API:
服务器名称 | 使用的服务器 API | 响应式流支持 |
---|---|---|
内蒂 |
Netty API |
|
退波 |
Undertow API |
spring-web:Undertow 到 Reactive Streams 桥接 |
雄猫 |
Servlet 非阻塞 I/O;Tomcat API 读取和写入 ByteBuffers 与 byte[] |
spring-web:Servlet 非阻塞 I/O 到反应式流的桥接 |
码头 |
Servlet 非阻塞 I/O;Jetty API 用于编写 ByteBuffers 与 byte[] |
spring-web:Servlet 非阻塞 I/O 到反应式流的桥接 |
Servlet 容器 |
Servlet 非阻塞 I/O |
spring-web:Servlet 非阻塞 I/O 到反应式流的桥接 |
下表描述了服务器依赖关系(另请参阅支持的版本):
服务器名称 | 组 ID | 项目名称 |
---|---|---|
Reactor Netty(内蒂反应堆) |
io.projectreactor.netty |
反应堆-netty |
退波 |
io.undertow |
暗曳芯 |
雄猫 |
org.apache.tomcat.embed |
tomcat-embed-core(tomcat-embed-core) |
码头 |
org.eclipse.jetty |
jetty-server, jetty-servlet |
下面的代码片段显示了将适配器与每个服务器 API 配合使用:HttpHandler
Reactor Netty(内蒂反应堆)
-
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()
退波
-
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()
雄猫
-
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()
码头
-
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 容器,可以在 WAR 中扩展并包含 AbstractReactiveWebInitializer
。该类包装 with 并寄存器
那作为一个.HttpHandler
ServletHttpHandlerAdapter
Servlet
WebHandler
应用程序接口
该包基于 HttpHandler
协定构建
提供通用 Web API,用于通过多个 WebExceptionHandler
、多个 WebFilter
和单个 WebHandler
组件的链处理请求。链条可以
只需指向自动检测组件的弹簧和/或注册组件即可组合在一起
与建设者。org.springframework.web.server
WebHttpHandlerBuilder
ApplicationContext
虽然 API 的目标很简单,就是抽象出不同 HTTP 服务器的使用,但 API 旨在提供 Web 应用程序中常用的更广泛的功能集
如:HttpHandler
WebHandler
-
具有属性的用户会话。
-
请求属性。
-
已解决或用于请求。
Locale
Principal
-
访问已解析和缓存的表单数据。
-
多部分数据的抽象。
-
和更多..
特殊豆类
下表列出了可以在
Spring ApplicationContext,或者可以直接向它注册:WebHttpHandlerBuilder
Bean 名称 | 豆类 | 计数 | 描述 |
---|---|---|---|
<任何> |
|
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>
使用 configured 来解析表单数据
() 转换为 .缺省情况下,配置为供 Bean 使用
(请参阅 Web 处理程序 API)。DefaultServerWebExchange
HttpMessageReader
application/x-www-form-urlencoded
MultiValueMap
FormHttpMessageReader
ServerCodecConfigurer
多部分数据
ServerWebExchange
公开以下用于访问分段数据的方法:
-
Java
-
Kotlin
Mono<MultiValueMap<String, Part>> getMultipartData();
suspend fun getMultipartData(): MultiValueMap<String, Part>
使用 配置的 将 、 和 内容解析为 .
默认情况下,这是 ,它没有任何第三方
依赖。
或者,可以使用基于 Synchronoss NIO Multipart 库的 。
两者都是通过 Bean 配置的
(请参阅 Web 处理程序 API)。DefaultServerWebExchange
HttpMessageReader<MultiValueMap<String, Part>>
multipart/form-data
multipart/mixed
multipart/related
MultiValueMap
DefaultPartHttpMessageReader
SynchronossPartHttpMessageReader
ServerCodecConfigurer
要以流式处理方式解析多部分数据,可以使用返回的 而不是使用 ,因为这意味着类似 的访问
按名称对单个部件进行分析,因此需要完整解析多部件数据。
相比之下,您可以使用将内容解码为 没有
收集到 .Flux<PartEvent>
PartEventHttpMessageReader
@RequestPart
Map
@RequestBody
Flux<PartEvent>
MultiValueMap
转发的标头
当请求通过负载均衡器等代理时,主机、端口和 方案可能会改变,这使得创建指向正确链接成为一项挑战 从客户端的角度来看,主机、端口和方案。
RFC 7239 定义了 HTTP 标头
代理可以使用它来提供有关原始请求的信息。Forwarded
非标准接头
还有其他非标准标头,包括 、 、 、 和 。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
虽然不是标准的,但它是一个事实上的标准标头,用于传达
原始协议(例如https/https)到下游服务器。例如,如果将请求发送到代理,该代理将请求转发到 ,则 的标头通知服务器
原始协议是 。X-Forwarded-Ssl: (on|off)
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}
代理没有前缀,而 applications 和 分别有路径前缀。代理可以发送到
让空前缀覆盖服务器前缀和 .app1
app2
/app1
/app2
X-Forwarded-Prefix:
/app1
/app2
此部署方案的一个常见情况是按 生产应用程序服务器,并且最好每个部署多个应用程序 服务器以降低费用。另一个原因是在同一台服务器上运行更多应用程序 为了共享服务器运行所需的资源。 在这些方案中,应用程序需要一个非空上下文根,因为有多个 应用程序在同一服务器上。但是,这不应该在 URL 路径中可见 公共 API,应用程序可以在其中使用不同的子域来提供好处 如:
|
方案 3:插入路径前缀
在其他情况下,可能需要在前缀前面加上一个前缀。例如,考虑 以下代理到服务器映射:
https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}
在这种情况下,代理的前缀为 ,服务器的前缀为 。代理可以发送以使原始前缀覆盖服务器前缀。/api/app1
/app1
X-Forwarded-Prefix: /api/app1
/api/app1
/app1
ForwardedHeader转换器
ForwardedHeaderTransformer
是修改主机、端口和方案的组件
该请求基于转发的标头,然后删除这些标头。如果您声明
它作为具有名称的 Bean ,它将被检测和使用。forwardedHeaderTransformer
在 5.1 中被弃用并被取代,因此转发的标头可以更早地处理,在
Exchange 已创建。如果仍然配置了过滤器,则会将其从列表中删除
过滤器,并改用。ForwardedHeaderFilter ForwardedHeaderTransformer ForwardedHeaderTransformer |
过滤 器
在 WebHandler
API 中,您可以使用 来应用拦截样式
在过滤器和目标的处理链的其余部分之前和之后的逻辑。使用 WebFlux Config 时,注册 a 非常简单
将其声明为 Spring Bean 并(可选)通过使用 on 来表达优先级
Bean 声明或通过实现 .WebFilter
WebHandler
WebFilter
@Order
Ordered
CORS(核心斯酒店)
Spring WebFlux 通过对 CORS 配置的注释提供细粒度支持
控制器。但是,当您将其与 Spring Security 一起使用时,我们建议您依赖内置的 ,必须在 Spring Security 的过滤器链之前订购。CorsFilter
有关详细信息,请参阅有关 CORS 和 CORS WebFilter
的部分。
异常
在 WebHandler
API 中,您可以使用 来处理
来自实例链和目标的异常。使用 WebFlux Config 时,注册 a 就像将其声明为
Spring bean 和(可选)通过在 bean 声明上使用 或
通过实现 .WebExceptionHandler
WebFilter
WebHandler
WebExceptionHandler
@Order
Ordered
下表描述了可用的实现:WebExceptionHandler
异常处理程序 | 描述 |
---|---|
|
通过将响应设置为异常的 HTTP 状态代码,提供对 |
|
扩展也可以确定 HTTP 状态
任何异常的注释代码。 此处理程序在 WebFlux Config 中声明。 |
编 解码 器
和 模块支持序列化和
通过非阻塞 I/O 将字节内容反序列化到更高级别的对象和从更高级别的对象反序列化
反应流背压。下面介绍了此支持:spring-web
spring-core
-
HttpMessageReader
和HttpMessageWriter
是协定 对 HTTP 消息内容进行编码和解码。 -
可以包裹以使其适应在网络中使用 应用程序,而 a 可以用 .
Encoder
EncoderHttpMessageWriter
Decoder
DecoderHttpMessageReader
-
DataBuffer
抽象不同的 字节缓冲区表示(例如 Netty 、 等)和 is 所有编解码器的工作原理。请参阅 “Spring Core”部分,了解有关此主题的更多信息。ByteBuf
java.nio.ByteBuffer
该模块提供 、 、 、该模块提供 Jackson
JSON、Jackson Smile、JAXB2、协议缓冲区和其他编码器和解码器以及
表单数据、多部分内容、
服务器发送的事件等。spring-core
byte[]
ByteBuffer
DataBuffer
Resource
String
spring-web
ClientCodecConfigurer
通常用于配置和
自定义要在应用程序中使用的编解码器。请参阅有关配置 HTTP 消息编解码器的部分。ServerCodecConfigurer
杰克逊 JSON
JSON 和二进制 JSON (Smile) 是 当 Jackson 库存在时,两者都受支持。
作品如下:Jackson2Decoder
-
Jackson 的异步非阻塞解析器用于聚合字节块流 into 的每个表示一个 JSON 对象。
TokenBuffer
-
每个都传递给 Jackson's 以创建更高级别的对象。
TokenBuffer
ObjectMapper
-
当解码到单值发布者(例如)时,有一个 .
Mono
TokenBuffer
-
当解码到多值发布者(例如)时,每个都传递给 一旦为完全形成的对象接收到足够的字节。这 输入内容可以是 JSON 数组,也可以是任何以行分隔的 JSON 格式,例如 NDJSON, JSON 行或 JSON 文本序列。
Flux
TokenBuffer
ObjectMapper
作品如下:Jackson2Encoder
-
对于单个值发布者(例如),只需通过 .
Mono
ObjectMapper
-
对于具有 的多值发布者,默认情况下收集 的值,然后序列化生成的集合。
application/json
Flux#collectToList()
-
对于具有流媒体类型(如 或)、编码、写入和 使用行分隔的 JSON 格式单独刷新每个值。其他 流媒体类型可以注册到编码器。
application/x-ndjson
application/stream+x-jackson-smile
-
对于 SSE,将按事件调用,并刷新输出以确保 毫不拖延地交货。
Jackson2Encoder
默认情况下,两者都不支持 类型的元素。相反,默认假设是一个字符串或字符串序列
表示序列化的 JSON 内容,由 .如果什么
您需要从 、 使用 和 中渲染一个 JSON 数组
对 . |
表单数据
FormHttpMessageReader
并支持解码和编码内容。FormHttpMessageWriter
application/x-www-form-urlencoded
在经常需要从多个位置访问表单内容的服务器端,提供分析内容的专用方法
through,然后缓存结果以供重复访问。
请参阅WebHandler
API部分中的表单数据。ServerWebExchange
getFormData()
FormHttpMessageReader
一旦使用,就无法再从
请求正文。因此,应用程序应一致地访问缓存的表单数据,而不是从原始请求正文中读取数据。getFormData()
ServerWebExchange
多部分
MultipartHttpMessageReader
并支持解码和
对“多部分/表单数据”、“多部分/混合”和“多部分/相关”内容进行编码。
反过来,将委托给另一个进行实际解析,然后简单地将部分收集到 a 中。
默认情况下,使用 ,但可以通过 进行更改。
有关 的更多信息,请参阅 DefaultPartHttpMessageReader
的 javadoc。MultipartHttpMessageWriter
MultipartHttpMessageReader
HttpMessageReader
Flux<Part>
MultiValueMap
DefaultPartHttpMessageReader
ServerCodecConfigurer
DefaultPartHttpMessageReader
在服务器端,可能需要从多个区域访问多部分表单内容
places,提供解析
内容通过,然后缓存结果以供重复访问。
请参阅 WebHandler
API 部分中的分段数据。ServerWebExchange
getMultipartData()
MultipartHttpMessageReader
一旦使用,就无法再从
请求正文。因此,应用程序必须始终如一地使用对零件的重复、类似映射的访问,或者以其他方式依赖 对 .getMultipartData()
getMultipartData()
SynchronossPartHttpMessageReader
Flux<Part>
限制
Decoder
以及缓冲部分或全部输入的实现
可以对内存中要缓冲的最大字节数进行配置。
在某些情况下,缓冲是因为输入被聚合并表示为单个
对象 — 例如,具有 、 数据等的控制器方法。在流式处理中也可以发生缓冲,当
拆分输入流 — 例如,分隔文本、JSON 对象流和
依此类推。对于这些流式处理案例,该限制适用于关联的字节数
流中有一个对象。HttpMessageReader
@RequestBody byte[]
x-www-form-urlencoded
要配置缓冲区大小,您可以检查给定或公开属性,如果是,Javadoc 将包含有关缺省的详细信息
值。在服务器端,提供一个位置
设置所有编解码器,请参阅 HTTP 消息编解码器。在客户端,限制
所有编解码器都可以在 WebClient.Builder 中更改。Decoder
HttpMessageReader
maxInMemorySize
ServerCodecConfigurer
对于分段分析属性限制
非文件部件的大小。对于文件部件,它确定部件的阈值
写入磁盘。对于写入磁盘的文件部件,有一个附加属性来限制每个部件的磁盘空间量。还有
一个属性,用于限制分组请求中的部件总数。
若要在 WebFlux 中配置这三个,需要提供 to 的预配置实例。maxInMemorySize
maxDiskUsagePerPart
maxParts
MultipartHttpMessageReader
ServerCodecConfigurer
流
流式传输到 HTTP 响应(例如,,)时,定期发送数据非常重要,以便
尽早可靠地检测断开连接的客户端。这样的发送可能是
仅注释、空 SSE 事件或任何其他“无操作”数据,这些数据将有效地用作
心跳。text/event-stream
application/x-ndjson
DataBuffer
DataBuffer
是 WebFlux 中字节缓冲区的表示形式。Spring Core 部分
此参考在“数据缓冲区和编解码器”一节中对此进行了更多介绍。要了解的关键点是,在某些
像 Netty 这样的服务器,字节缓冲区是池化的,引用计数,必须释放
使用时以避免内存泄漏。
WebFlux 应用程序通常不需要关注此类问题,除非它们 直接使用或生成数据缓冲区,而不是依赖编解码器来转换为 以及来自更高级别的对象,或者除非他们选择创建自定义编解码器。对于这样的 案例请查看数据缓冲区和编解码器中的信息, 尤其是关于使用 DataBuffer 的部分。
伐木
DEBUG
Spring WebFlux 中的关卡日志记录被设计为紧凑、最小且
人性化。它侧重于对 和
与仅在调试特定问题时有用的其他方法相比。
TRACE
级别日志记录通常遵循与(例如,也
不应该是消防水带),但可用于调试任何问题。此外,一些日志
消息可能会在 vs 中显示不同级别的详细信息。DEBUG
TRACE
DEBUG
良好的日志记录来自使用日志的经验。如果你发现任何 未达到既定目标,请告知我们。
日志 ID
在 WebFlux 中,单个请求可以在多个线程和线程 ID 上运行 对于关联属于特定请求的日志消息没有用。这就是为什么 默认情况下,WebFlux 日志消息以特定于请求的 ID 为前缀。
在服务器端,日志 ID 存储在属性中
(LOG_ID_ATTRIBUTE
),
而基于该 ID 的完全格式化前缀可从 .另一方面,日志 ID 存储在属性中
(LOG_ID_ATTRIBUTE
)
,而完全格式化的前缀可从 获得。ServerWebExchange
ServerWebExchange#getLogPrefix()
WebClient
ClientRequest
ClientRequest#logPrefix()
敏感数据
DEBUG
日志记录可以记录敏感信息。这就是为什么表单参数和
默认情况下,标头被屏蔽,您必须显式完全启用其日志记录。TRACE
以下示例演示如何对服务器端请求执行此操作:
-
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()
附录
SLF4J 和 Log4J 2 等日志记录库提供了异步记录器,可避免 阻塞。虽然这些有其自身的缺点,例如可能会丢弃消息 无法排队进行日志记录,它们是目前最好的可用选项 用于反应式、非阻塞式应用。
自定义编解码器
应用程序可以注册自定义编解码器以支持其他媒体类型, 或默认编解码器不支持的特定行为。
以下示例演示如何对客户端请求执行此操作:
-
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()