Reactive Core
-
服务器端请求处理,使用 HttpHandler 合约和 WebHandler API
-
客户端请求处理,使用 ClientHttpConnector 合约
-
HTTP 请求和响应内容的序列化和反序列化编解码器
spring-web
模块包含对反应式网络应用程序提供以下基本支持:
-
对于服务器请求处理,有两个级别的支持。
-
HttpHandler:用于 HTTP 请求处理的基本合约,带有非阻塞式 I/O 和 Reactive Streams 背压,以及用于 Reactor Netty、Undertow、Tomcat、Jetty 和任何 Servlet 容器的适配器。
-
WebHandler
API:高级一点的通用 Web API,用于请求处理,在此之上构建具体的编程模型,如带注释的控制器和功能式端点。
-
-
对于客户端,有一个基本的
ClientHttpConnector
合约用于执行 HTTP 请求,带有非阻塞式 I/O 和 Reactive Streams 背压,以及用于 Reactor Netty、reactive Jetty HttpClient和 Apache HttpComponents 的适配器。应用程序中使用的更高级别的 WebClient 构建在这个基本合约之上。 -
对于客户端和服务器,codecs 用于 HTTP 请求和响应内容的序列化和反序列化。
HttpHandler
HttpHandler 是一个简单的约定,其中有一个方法用于处理请求和响应。它故意是最小的,其主要也是唯一目的是成为不同 HTTP 服务器 API 上的一个最小抽象。
下表描述了受支持的服务器 API:
Server name | Server API used | Reactive Streams support |
---|---|---|
Netty |
Netty API |
|
Undertow |
Undertow API |
spring-web:从 Undertow 到反应流桥接器 |
Tomcat |
Servlet 非阻塞 I/O;用于读写 ByteBuffers 与 byte[] 的 Tomcat API |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥接 |
Jetty |
Servlet 非阻塞 I/O;用于写 ByteBuffers 与 byte[] 的 Jetty API |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥接 |
Servlet container |
Servlet non-blocking I/O |
spring-web:Servlet 非阻塞 I/O 到 Reactive Streams 桥接 |
下表描述了服务器依赖项(另请参阅 受支持的版本):
Server name | Group id | Artifact name |
---|---|---|
Reactor Netty |
io.projectreactor.netty |
reactor-netty |
Undertow |
io.undertow |
undertow-core |
Tomcat |
org.apache.tomcat.embed |
tomcat-embed-core |
Jetty |
org.eclipse.jetty |
jetty-server, jetty-servlet |
下面的代码片段显示了使用 HttpHandler
适配器与每个服务器 API 的情况:
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()
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 容器,您可以在 WAR 中扩展并包含 AbstractReactiveWebInitializer
。该类使用 [ServletHttpHandlerAdapter
] 将一个 [HttpHandler
] 封装起来,并将其注册为一个 [Servlet
]。
WebHandler
API
org.springframework.web.server`包基于 `HttpHandler
契约,为通过一组多个 WebExceptionHandler
、多个 WebFilter
和单个 WebHandler
组件处理请求提供了通用 Web API。可以通过简单地指向 Spring ApplicationContext
,其中组件是 auto-detected的,和/或使用生成器注册组件来使用 `WebHttpHandlerBuilder`将该组装在一起。
虽然 HttpHandler
的简单目标是抽象不同 HTTP 服务器的使用,但是`WebHandler` API 旨在提供一系列通常用于网络应用程序中的特性,例如:
-
User session with attributes.
-
Request attributes.
-
针对请求设置的
Locale
或Principal
。 -
访问已解析和缓存的表单数据。
-
Abstractions for multipart data.
-
and more..
Special bean types
下表列出了 WebHttpHandlerBuilder
可以自动在 Spring ApplicationContext 中检测到的组件,或者可以直接对其进行注册的组件:
Bean name | Bean type | Count | Description |
---|---|---|---|
<any> |
|
0..N |
为 |
<any> |
|
0..N |
对过滤器链和目标 |
|
|
1 |
处理请求的程序。 |
|
|
0..1 |
通过 |
|
|
0..1 |
用于访问 |
|
|
0..1 |
用于解析 |
|
|
0..1 |
用于处理转发类型标头,通过提取并移除它们或仅移除它们进行处理。默认情况下未使用。 |
Form Data
ServerWebExchange
公开了以下方法用于访问表单数据:
-
Java
-
Kotlin
Mono<MultiValueMap<String, String>> getFormData();
suspend fun getFormData(): MultiValueMap<String, String>
DefaultServerWebExchange`使用已配置的 `HttpMessageReader`将表单数据 (`application/x-www-form-urlencoded
) 解析为 MultiValueMap
。默认情况下, `FormHttpMessageReader`已配置为由 `ServerCodecConfigurer`bean 使用(请参阅 Web Handler API)。
Multipart Data
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`bean 配置(请参阅 Web Handler API)。
要解析流式传输方式的多部件数据,可以使用 PartEventHttpMessageReader
返回的 Flux<PartEvent>
,而不是使用 @RequestPart
,因为这意味着按名称访问类似于 Map
的各个部件,因此需要完全解析多部件数据。相比之下,你可以使用 @RequestBody
将内容解码到 Flux<PartEvent>
,而不收集到 MultiValueMap
。
Non-standard Headers
当请求经过负载平衡器等代理时,主机、端口和协议可能会更改,而且这对于从客户端角度创建指向正确的主机、端口和协议的链接来说是个挑战。
RFC 7239 定义了 Forwarded
HTTP 标头,代理可以使用它来提供有关原始请求的信息。
Non-standard Headers
也有一些其他非标准头,包括 X-Forwarded-Host
、X-Forwarded-Port
、X-Forwarded-Proto
、X-Forwarded-Ssl
和 X-Forwarded-Prefix
。
X-Forwarded-Host
虽然不是标准,但 X-Forwarded-Host: <host>
是一个事实上的标准标头,用于将原始主机传达给下游服务器。例如,如果将 https://example.com/resource
的请求发送到将请求转发给 http://localhost:8080/resource
的代理,那么可以发送 X-Forwarded-Host: example.com
的标头以告知服务器原始主机是 example.com
。
X-Forwarded-Port
虽然不是标准,X-Forwarded-Port: <port>`是用于向服务器传递原始端口的实际标准头信息。例如,如果发送`https://example.com/resource`请求到将请求转发到`http://localhost:8080/resource`的代理,那么可以发送`X-Forwarded-Port: 443`头信息来通知服务器原始端口为`443
。
X-Forwarded-Proto
虽然不是标准,但 X-Forwarded-Proto: (https|http)
是一个事实上的标准标头,用于将原始协议(例如 https/https
)传达给下游服务器。例如,如果将 https://example.com/resource
的请求发送到将请求转发给 http://localhost:8080/resource
的代理,那么可以发送 X-Forwarded-Proto: https
的标头以告知服务器原始协议是 https
。
X-Forwarded-Ssl
虽然不是标准,X-Forwarded-Ssl: (on|off)`是用于向服务器传递原始协议(例如https/https)的实际标准头信息。例如,如果发送`https://example.com/resource`请求到将请求转发到`http://localhost:8080/resource`的代理,那么可以发送`X-Forwarded-Ssl: on`头信息来通知服务器原始协议为`https
。
X-Forwarded-Prefix
虽然不是标准,但 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 的URL路径中可见,这些子域提供的优点包括:
|
场景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`是一个组件,它根据已转发的标头修改请求的主机、端口和方案,然后删除这些标头。如果你将其声明为名称为 `forwardedHeaderTransformer`的 bean,它会 detected并使用。
5.1 中的 |
Security Considerations
由于应用程序无法知道标头是由代理按预期添加的还是由恶意客户端添加的,因此转发标头有安全注意事项。这就是为什么信任边界处的代理应该配置为删除来自外部的不可信转发流量。你还可以使用 removeOnly=true
配置 ForwardedHeaderTransformer
,在这种情况下,它会删除标头,但不会使用它们。
Filters
在 WebHandler
API中,你可以使用 `WebFilter`在过滤器和其他目标 `WebHandler`的其余处理链之前和之后应用拦截式逻辑。当使用 WebFlux Config时,注册 `WebFilter`就像将其声明为 Spring bean,并且(可选)通过在 bean 声明中使用 `@Order`或实现 `Ordered`来说明优先级。
CORS
Spring WebFlux 通过控制器上的注解提供精细的 CORS 配置支持。但是,在将它与 Spring Security 搭配使用时,我们建议依赖内置的 CorsFilter
,它必须排在 Spring Security 的过滤器链之前。
请参阅有关 CORS和 CORS WebFilter
的部分以了解更多详细信息。
Exceptions
在 WebHandler
API中,你可以使用 `WebExceptionHandler`来处理来自 `WebFilter`实例和目标 `WebHandler`的链的异常。当使用 WebFlux Config时,注册 `WebExceptionHandler`就像将其声明为 Spring bean,并且(可选)通过在 bean 声明中使用 `@Order`或通过实现 `Ordered`来说明优先级。
下表描述了可用的 WebExceptionHandler
实现:
Exception Handler | Description |
---|---|
|
通过设置响应为异常的 HTTP 状态码,为 |
|
扩展 |
Codecs
spring-web
和 spring-core
模块通过带有 Reactive Streams 背压的非阻塞 I/O 提供将字节内容序列化和反序列化为更高级别对象的支持。以下对此支持进行说明:
-
HttpMessageReader
和HttpMessageWriter
是合约,用于对 HTTP 消息内容进行编码和解码。 -
可以用
EncoderHttpMessageWriter
包装Encoder
,以使其适用于 Web 应用程序,而可以用DecoderHttpMessageReader
包装Decoder
。 -
DataBuffer
抽象了不同的字节缓冲区表示(例如,NettyByteBuf
、java.nio.ByteBuffer
等),这是所有编解码器在上面工作的。有关此主题的更多信息,请参见“Spring Core”部分中的 Data Buffers and Codecs。
spring-core
模块提供 byte[]
、ByteBuffer
、DataBuffer
、Resource
和 String
编码器和解码器实现。spring-web
模块提供 Jackson JSON、Jackson Smile、JAXB2、协议缓冲区以及其他编码器和解码器,以及表單數據、分段內容、伺服器發送的事件和其他內容的 Web 專用 HTTP 訊息讀取器和寫入器實現。
`ClientCodecConfigurer`和 `ServerCodecConfigurer`通常用于配置和自定义在应用程序中使用的编解码器。请参阅有关配置 HTTP message codecs的部分。
Jackson JSON
當存在 Jackson 函式庫時,JSON 和二進位 JSON (Smile) 受支持。
Jackson2Decoder
的工作原理如下:
-
Jackson 的异步、非阻塞式解析器用于将一串字节块聚合到
TokenBuffer’s 中,每个 `TokenBuffer’s
表示一个 JSON 对象。 -
每个
TokenBuffer
都传递给 Jackson 的ObjectMapper
以创建一个更高级别的对象。 -
当解码为单值发布者(例如
Mono
)时,会有一个TokenBuffer
。 -
如果解码为一个多值发布者(例如:
Flux
),只要收到足以构成一个完整对象所需的字节,就会将每个`TokenBuffer`传递给`ObjectMapper`。输入内容可以是 JSON 数组,或者任何 line-delimited JSON格式,例如 NDJSON、JSON Lines 或 JSON 文本序列。
Jackson2Encoder
的工作原理如下:
-
对于单值发布者(例如:
Mono
),只需通过`ObjectMapper`对它执行序列化。 -
对于使用`application/json`的多值发布者,默认情况下使用`Flux#collectToList()`收集值,然后对结果集执行序列化。
-
对于使用流媒体类型的多值发布者,例如`application/x-ndjson`或`application/stream+x-jackson-smile`,使用 line-delimited JSON格式分别对每个值进行编码、写入和刷新。可以向编码器注册其他流媒体类型。
-
对于 SSE,每次事件都会调用`Jackson2Encoder`,并且输出将被刷新以确保立即交付。
默认情况下, |
Form Data
FormHttpMessageReader
和 FormHttpMessageWriter
支持对 application/x-www-form-urlencoded
内容进行解码和编码。
在服务器端经常需要从多个地方访问表单内容时,ServerWebExchange`提供了一个专门的 `getFormData()`方法,该方法通过 `FormHttpMessageReader`解析内容,然后缓存结果以供重复访问。请参阅 `WebHandler
API部分中的 Form Data。
一旦使用 getFormData()
,便无法再从请求正文中读取原始原始内容。因此,预期应用程序将始终通过 ServerWebExchange
访问缓存的表单数据,而不是从原始请求正文读取内容。
Multipart
MultipartHttpMessageReader
和 MultipartHttpMessageWriter
支援對 "multipart/form-data"、"multipart/mixed" 和 "multipart/related" 內容進行解碼和編碼。反過來,MultipartHttpMessageReader
會委派給另一個 HttpMessageReader
對 Flux<Part>
進行實際解析,然後只將各個部分收集到一個 MultiValueMap
中。預設情況下,會使用 DefaultPartHttpMessageReader
,但是這可以使用 ServerCodecConfigurer
來改變。有關 DefaultPartHttpMessageReader
的更多資訊,請參閱 javadoc of DefaultPartHttpMessageReader
。
在服务器端可能需要从多个地方访问多部分表单内容时,ServerWebExchange`提供了一个专门的 `getMultipartData()`方法,该方法通过 `MultipartHttpMessageReader`解析内容,然后缓存结果以供重复访问。请参阅 `WebHandler
API部分中的 Multipart Data。
一旦使用 getMultipartData()
,便无法再从请求正文中读取原始原始内容。因此,应用程序必须始终使用 getMultipartData()
对各个部分进行重复的映射式的访问,或者依赖 SynchronossPartHttpMessageReader
对 Flux<Part>
进行一次性访问。
Protocol Buffers
ProtobufEncoder
和 ProtobufDecoder
支持对 “application/x-protobuf”、“application/octet-stream” 和 “application/vnd.google.protobuf” 内容进行解码和编码,用于 com.google.protobuf.Message
类型。如果内容使用内容类型中的 “delimited” 参数(例如 “application/x-protobuf;delimited=true”)进行接收或发送,它们还支持值流。这需要使用 “com.google.protobuf:protobuf-java” 库,版本为 3.29 及更高版本。
ProtobufJsonDecoder
和 ProtobufJsonEncoder
變體支援讀取和寫入 JSON 文件到 Protobuf 訊息和從 Protobuf 訊息讀取和寫入 JSON 文件。它們需要 "com.google.protobuf:protobuf-java-util" 相依性。請注意,JSON 變體不支援讀取訊息串流,請參閱 javadoc of ProtobufJsonDecoder
以取得更多詳細資訊。
Limits
可以在記憶體中設定緩衝某些或全部輸入串流的 Decoder
和 HttpMessageReader
實現對要緩衝的位元組的最大數量加以限制。在某些情況下,緩衝發生是因為輸入聚集在一起並表示為一個單一物件 - 例如,具有 @RequestBody byte[]
的控制器方法、x-www-form-urlencoded
資料等等。在分拆輸入串流時,緩衝也可能發生串流,例如區隔文字、JSON 物件的串流等等。對於那些串流情況,限制適用於串流中與一個物件相關聯的位元組數。
要配置缓冲区大小,您可以检查指定的 Decoder
或 HttpMessageReader
是否公开了 maxInMemorySize
属性,如果是,则 Javadoc 将包含有关默认值详细信息。在服务器端,ServerCodecConfigurer
提供了一个可以设置所有编解码器的位置,请参阅 HTTP message codecs。在客户端,WebClient.Builder 中可以更改所有编解码器的限制。
对于 Multipart parsing,maxInMemorySize
属性限制非文件部分的大小。对于文件部分,它确定将部分写入磁盘的阈值。对于写入磁盘的文件部分,有一个附加的 maxDiskUsagePerPart
属性来限制每个部分的磁盘空间量。还有一个 maxParts
属性来限制 multipart 请求中部分的总数。要在 WebFlux 中配置所有三个属性,您需要向 ServerCodecConfigurer
提供预配置的 MultipartHttpMessageReader
实例。
Streaming
当流式传输到 HTTP 响应(例如 text/event-stream
、application/x-ndjson
)时,重要的是定期发送数据,以比迟发现断开的客户端更早地可靠地检测到断开的客户端。这样的发送可以是仅包含注释的空 SSE 事件或任何其他无操作数据,实际上可以作为心跳。
DataBuffer
DataBuffer
是 WebFlux 中位元組緩衝區的表示式。此參考的 Spring Core 部分在有關 Data Buffers and Codecs 的部分中提供了更多資訊。要了解的一個重點是,在像 Netty 那樣的某些伺服器上,位元組緩衝區是分池的且具有參考計算功能,并且必須在使用時釋放,以避免記憶體外洩。
除非直接使用数据缓冲区(而不是依赖编解码器从高级对象转换),或除非选择创建自定义编解码器,否则 WebFlux 应用程序通常不需要关注此类问题。对于此类情况,请查看 Data Buffers and Codecs 中的信息,特别是 Using DataBuffer 部分。
Logging
Spring WebFlux 中的 DEBUG
级别日志记录旨在精简、简洁且对用户友好。它侧重于有用的高价值信息片段,一遍遍地重复使用与只在调试特定问题时有用的信息片段相反。
TRACE
级别日志记录通常遵循与 DEBUG
相同的原则(例如,也不应该是消防软管),但可用于调试任何问题。此外,某些日志消息在 TRACE
和 DEBUG
级别显示的详情级别不同。
良好的日志记录得益于使用日志的经验。如果你发现任何不符合既定目标的信息,请告知我们。
Log Id
在 WebFlux 中,单个请求可以在多个线程上运行,而线程 ID 对于关联属于特定请求的日志消息没有用。这就是 WebFlux 日志消息默认以特定请求的 ID 为前缀的原因。
在服务器端,日志 ID 存储在 ServerWebExchange
属性(LOG_ID_ATTRIBUTE
) 中,可以从 ServerWebExchange#getLogPrefix()
中获得基于该 ID 的格式完整的修饰符。在 WebClient
端,日志 ID 存储在 ClientRequest
属性(LOG_ID_ATTRIBUTE
) 中,可以从 ClientRequest#logPrefix()
中获得格式完整的修饰符。
Sensitive Data
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()
Appenders
SLF4J 和 Log4J 2 等日志库提供异步记录器,可避免阻塞。虽然这些库有自己的缺点,例如可能会丢弃无法排队进行日志记录的消息,但它们是当前在响应式非阻塞应用程序中使用的最佳可用选项。
Custom codecs
应用程序可以注册自定义编解码器,以支持默认编解码器不支持的附加媒体类型或特定行为。
开发人员表达的一些配置选项在默认编解码器中强制执行。自定义编解码器可能希望有机会符合这些首选项,例如 enforcing buffering limits 或 logging sensitive data。
以下示例演示如何对客户端请求执行此操作:
-
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()