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 HttpClientApache HttpComponents 的适配器。应用程序中使用的更高级别的 WebClient 构建在这个基本合约之上。

  • 对于客户端和服务器,codecs 用于 HTTP 请求和响应内容的序列化和反序列化。

HttpHandler

HttpHandler 是一个简单的约定,其中有一个方法用于处理请求和响应。它故意是最小的,其主要也是唯一目的是成为不同 HTTP 服务器 API 上的一个最小抽象。

下表描述了受支持的服务器 API:

Server name Server API used Reactive Streams support

Netty

Netty API

Reactor Netty

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.

  • 针对请求设置的 LocalePrincipal

  • 访问已解析和缓存的表单数据。

  • Abstractions for multipart data.

  • and more..

Special bean types

下表列出了 WebHttpHandlerBuilder 可以自动在 Spring ApplicationContext 中检测到的组件,或者可以直接对其进行注册的组件:

Bean name Bean type Count Description

<any>

WebExceptionHandler

0..N

WebFilter 实例和目标 WebHandler 的异常链提供处理。有关更多详细信息,请参见 Exceptions

<any>

WebFilter

0..N

对过滤器链和目标 WebHandler 剩余部分的之前和之后应用拦截式逻辑。有关更多详细信息,请参见 Filters

webHandler

WebHandler

1

处理请求的程序。

webSessionManager

WebSessionManager

0..1

通过 ServerWebExchange 上的某个方法公开的 WebSession 实例的管理器。默认为 DefaultWebSessionManager

serverCodecConfigurer

ServerCodecConfigurer

0..1

用于访问 HttpMessageReader 实例,以便解析表单数据和 multipart 数据,然后,这些数据通过 ServerWebExchange 上的方法公开。默认值为 ServerCodecConfigurer.create()

localeContextResolver

LocaleContextResolver

0..1

用于解析 LocaleContext 的解析器,该解析器通过 ServerWebExchange 上的某个方法公开。默认值为 AcceptHeaderLocaleContextResolver

forwardedHeaderTransformer

ForwardedHeaderTransformer

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-datamultipart/mixed`和 `multipart/related`内容解析为 `MultiValueMap。默认情况下,这是 DefaultPartHttpMessageReader,它没有任何第三方依赖项。或者,可以使用 SynchronossPartHttpMessageReader,它基于 Synchronoss NIO Multipart库。两者均通过 `ServerCodecConfigurer`bean 配置(请参阅 Web Handler API)。

要解析流式传输方式的多部件数据,可以使用 PartEventHttpMessageReader 返回的 Flux<PartEvent>,而不是使用 @RequestPart,因为这意味着按名称访问类似于 Map 的各个部件,因此需要完全解析多部件数据。相比之下,你可以使用 @RequestBody 将内容解码到 Flux<PartEvent>,而不收集到 MultiValueMap

Forwarded Headers

Non-standard Headers

当请求经过负载平衡器等代理时,主机、端口和协议可能会更改,而且这对于从客户端角度创建指向正确的主机、端口和协议的链接来说是个挑战。

RFC 7239 定义了 Forwarded HTTP 标头,代理可以使用它来提供有关原始请求的信息。

Non-standard Headers

也有一些其他非标准头,包括 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-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路径中可见,这些子域提供的优点包括:

  • 增加安全性,例如相同的源策略

  • 应用程序的独立扩展(不同的域指向不同的 IP 地址)

场景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 中的 ForwardedHeaderFilter 已弃用,并被 ForwardedHeaderTransformer 取代,以便转发头信息可以在交换创建之前得到处理。如果无论如何配置筛选器,则会将其从筛选器列表中取出,并改用 ForwardedHeaderTransformer

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 的过滤器链之前。

请参阅有关 CORSCORS WebFilter的部分以了解更多详细信息。

Exceptions

WebHandler API中,你可以使用 `WebExceptionHandler`来处理来自 `WebFilter`实例和目标 `WebHandler`的链的异常。当使用 WebFlux Config时,注册 `WebExceptionHandler`就像将其声明为 Spring bean,并且(可选)通过在 bean 声明中使用 `@Order`或通过实现 `Ordered`来说明优先级。

下表描述了可用的 WebExceptionHandler 实现:

Exception Handler Description

ResponseStatusExceptionHandler

通过设置响应为异常的 HTTP 状态码,为 ResponseStatusException 类型的异常提供处理。

WebFluxResponseStatusExceptionHandler

扩展 ResponseStatusExceptionHandler 的功能,后者还可以确定任何异常上的 @ResponseStatus 注释的 HTTP 状态代码。此处理程序在 WebFlux 配置 中声明。

Codecs

spring-webspring-core 模块通过带有 Reactive Streams 背压的非阻塞 I/O 提供将字节内容序列化和反序列化为更高级别对象的支持。以下对此支持进行说明:

  • EncoderDecoder 是低级别合约,用于对独立于 HTTP 的内容进行编码和解码。

  • HttpMessageReaderHttpMessageWriter 是合约,用于对 HTTP 消息内容进行编码和解码。

  • 可以用 EncoderHttpMessageWriter 包装 Encoder,以使其适用于 Web 应用程序,而可以用 DecoderHttpMessageReader 包装 Decoder

  • DataBuffer 抽象了不同的字节缓冲区表示(例如,Netty ByteBufjava.nio.ByteBuffer 等),这是所有编解码器在上面工作的。有关此主题的更多信息,请参见“Spring Core”部分中的 Data Buffers and Codecs

spring-core 模块提供 byte[]ByteBufferDataBufferResourceString 编码器和解码器实现。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`,并且输出将被刷新以确保立即交付。

默认情况下,Jackson2EncoderJackson2Decoder 都不支持 String 类型的元素。相反,默认假设是一个字符串或一序列字符串表示要由 CharSequenceEncoder 渲染的序列化 JSON 内容。如果你需要从 Flux<String> 渲染一个 JSON 数组,请使用 Flux#collectToList(),并对 Mono<List<String>> 执行编码。

Form Data

FormHttpMessageReaderFormHttpMessageWriter 支持对 application/x-www-form-urlencoded 内容进行解码和编码。

在服务器端经常需要从多个地方访问表单内容时,ServerWebExchange`提供了一个专门的 `getFormData()`方法,该方法通过 `FormHttpMessageReader`解析内容,然后缓存结果以供重复访问。请参阅 `WebHandler API部分中的 Form Data

一旦使用 getFormData(),便无法再从请求正文中读取原始原始内容。因此,预期应用程序将始终通过 ServerWebExchange 访问缓存的表单数据,而不是从原始请求正文读取内容。

Multipart

MultipartHttpMessageReaderMultipartHttpMessageWriter 支援對 "multipart/form-data"、"multipart/mixed" 和 "multipart/related" 內容進行解碼和編碼。反過來,MultipartHttpMessageReader 會委派給另一個 HttpMessageReaderFlux<Part> 進行實際解析,然後只將各個部分收集到一個 MultiValueMap 中。預設情況下,會使用 DefaultPartHttpMessageReader,但是這可以使用 ServerCodecConfigurer 來改變。有關 DefaultPartHttpMessageReader 的更多資訊,請參閱 javadoc of DefaultPartHttpMessageReader

在服务器端可能需要从多个地方访问多部分表单内容时,ServerWebExchange`提供了一个专门的 `getMultipartData()`方法,该方法通过 `MultipartHttpMessageReader`解析内容,然后缓存结果以供重复访问。请参阅 `WebHandler API部分中的 Multipart Data

一旦使用 getMultipartData(),便无法再从请求正文中读取原始原始内容。因此,应用程序必须始终使用 getMultipartData() 对各个部分进行重复的映射式的访问,或者依赖 SynchronossPartHttpMessageReaderFlux<Part> 进行一次性访问。

Protocol Buffers

ProtobufEncoderProtobufDecoder 支持对 “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 及更高版本。

ProtobufJsonDecoderProtobufJsonEncoder 變體支援讀取和寫入 JSON 文件到 Protobuf 訊息和從 Protobuf 訊息讀取和寫入 JSON 文件。它們需要 "com.google.protobuf:protobuf-java-util" 相依性。請注意,JSON 變體不支援讀取訊息串流,請參閱 javadoc of ProtobufJsonDecoder 以取得更多詳細資訊。

Limits

可以在記憶體中設定緩衝某些或全部輸入串流的 DecoderHttpMessageReader 實現對要緩衝的位元組的最大數量加以限制。在某些情況下,緩衝發生是因為輸入聚集在一起並表示為一個單一物件 - 例如,具有 @RequestBody byte[] 的控制器方法、x-www-form-urlencoded 資料等等。在分拆輸入串流時,緩衝也可能發生串流,例如區隔文字、JSON 物件的串流等等。對於那些串流情況,限制適用於串流中與一個物件相關聯的位元組數。

要配置缓冲区大小,您可以检查指定的 DecoderHttpMessageReader 是否公开了 maxInMemorySize 属性,如果是,则 Javadoc 将包含有关默认值详细信息。在服务器端,ServerCodecConfigurer 提供了一个可以设置所有编解码器的位置,请参阅 HTTP message codecs。在客户端,WebClient.Builder 中可以更改所有编解码器的限制。

对于 Multipart parsingmaxInMemorySize 属性限制非文件部分的大小。对于文件部分,它确定将部分写入磁盘的阈值。对于写入磁盘的文件部分,有一个附加的 maxDiskUsagePerPart 属性来限制每个部分的磁盘空间量。还有一个 maxParts 属性来限制 multipart 请求中部分的总数。要在 WebFlux 中配置所有三个属性,您需要向 ServerCodecConfigurer 提供预配置的 MultipartHttpMessageReader 实例。

Streaming

当流式传输到 HTTP 响应(例如 text/event-streamapplication/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 相同的原则(例如,也不应该是消防软管),但可用于调试任何问题。此外,某些日志消息在 TRACEDEBUG 级别显示的详情级别不同。

良好的日志记录得益于使用日志的经验。如果你发现任何不符合既定目标的信息,请告知我们。

Log Id

在 WebFlux 中,单个请求可以在多个线程上运行,而线程 ID 对于关联属于特定请求的日志消息没有用。这就是 WebFlux 日志消息默认以特定请求的 ID 为前缀的原因。

在服务器端,日志 ID 存储在 ServerWebExchange 属性(LOG_ID_ATTRIBUTE) 中,可以从 ServerWebExchange#getLogPrefix() 中获得基于该 ID 的格式完整的修饰符。在 WebClient 端,日志 ID 存储在 ClientRequest 属性(LOG_ID_ATTRIBUTE) 中,可以从 ClientRequest#logPrefix() 中获得格式完整的修饰符。

Sensitive Data

DEBUGTRACE 日志记录可以记录敏感信息。这就是表单参数和标头默认情况下被屏蔽,而你必须明确完全启用其日志记录的原因。

以下示例演示如何对服务器端请求执行此操作:

  • 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 limitslogging 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()