响应式核心
spring-web
模块包含以下对响应式 Web 应用程序的基础支持
-
对于服务器请求处理,有两种级别的支持。
-
HttpHandler:使用非阻塞 I/O 和 Reactive Streams 反压进行 HTTP 请求处理的基本契约,以及 Reactor Netty、Undertow、Tomcat、Jetty 和任何 Servlet 容器的适配器。
-
WebHandler
API:更高一级、通用的 Web API,用于处理请求,在其之上构建了具体的编程模型,例如注解控制器和函数式端点。
-
-
对于客户端,有一个基本的
ClientHttpConnector
契约,用于使用非阻塞 I/O 和 Reactive Streams 反压执行 HTTP 请求,以及 Reactor Netty、响应式 Jetty HttpClient 和 Apache HttpComponents 的适配器。应用程序中使用的更高层 WebClient 构建在此基本契约之上。 -
对于客户端和服务器,用于 HTTP 请求和响应内容序列化和反序列化的编解码器。
HttpHandler
HttpHandler 是一个简单的契约,只有一个方法用于处理请求和响应。它有意地保持最小化,其主要和唯一目的是作为不同 HTTP 服务器 API 的一个最小抽象。
下表描述了支持的服务器 API
服务器名称 | 使用的服务器 API | Reactive Streams 支持 |
---|---|---|
Netty |
Netty API |
|
Undertow |
Undertow API |
spring-web: Undertow 到 Reactive Streams 的桥接 |
Tomcat |
Servlet 非阻塞 I/O;Tomcat API 用于读写 ByteBuffers 而非 byte[] |
spring-web: Servlet 非阻塞 I/O 到 Reactive Streams 的桥接 |
Jetty |
Servlet 非阻塞 I/O;Jetty API 用于写入 ByteBuffers 而非 byte[] |
spring-web: Servlet 非阻塞 I/O 到 Reactive Streams 的桥接 |
Servlet 容器 |
Servlet 非阻塞 I/O |
spring-web: Servlet 非阻塞 I/O 到 Reactive Streams 的桥接 |
下表描述了服务器依赖项(另请参阅支持的版本)
服务器名称 | Group id | Artifact 名称 |
---|---|---|
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
。该类将 HttpHandler
封装在 ServletHttpHandlerAdapter
中,并将其注册为 Servlet
。
WebHandler
API
org.springframework.web.server
包构建在HttpHandler
契约之上,提供了一个通用的 Web API,用于通过多个 WebExceptionHandler
、多个 WebFilter
和一个 WebHandler
组件组成的链来处理请求。可以通过 WebHttpHandlerBuilder
将此链组合起来,只需指向一个 Spring ApplicationContext
(组件在此处自动检测),和/或向构建器注册组件。
尽管 HttpHandler
的目标是抽象不同 HTTP 服务器的使用,但 WebHandler
API 旨在提供 Web 应用程序中常用的一组更广泛的功能,例如
-
带有属性的用户会话。
-
请求属性。
-
请求的解析后的
Locale
或Principal
。 -
访问已解析和缓存的表单数据。
-
Multipart 数据抽象。
-
等等..
特殊 Bean 类型
下表列出了 WebHttpHandlerBuilder
可以在 Spring ApplicationContext 中自动检测到的组件,或者可以直接向其注册的组件
Bean 名称 | Bean 类型 | 计数 | 描述 |
---|---|---|---|
<any> |
|
0..N |
提供处理 |
<any> |
|
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
。默认情况下,ServerCodecConfigurer
bean 配置了 FormHttpMessageReader
(请参阅Web Handler API)。
Multipart 数据
ServerWebExchange
暴露以下方法用于访问 multipart 数据
-
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
,它没有任何第三方依赖。或者,可以使用基于 Synchronoss NIO Multipart 库的 SynchronossPartHttpMessageReader
。两者都通过 ServerCodecConfigurer
bean 进行配置(请参阅Web Handler API)。
要以流式方式解析 multipart 数据,可以使用 PartEventHttpMessageReader
返回的 Flux<PartEvent>
,而不是使用 @RequestPart
,因为 @RequestPart
意味着按名称对单个部分进行类似 Map 的访问,因此需要完整解析 multipart 数据。相比之下,可以使用 @RequestBody
将内容解码为 Flux<PartEvent>
,而无需收集到 MultiValueMap
。
Forwarded 头部
当请求经过负载均衡器等代理时,主机、端口和方案可能会发生变化,这使得从客户端角度创建指向正确主机、端口和方案的链接成为一项挑战。
RFC 7239 定义了 Forwarded
HTTP 头部,代理可以使用它来提供关于原始请求的信息。
非标准头部
还有其他非标准头部,包括 X-Forwarded-Host
、X-Forwarded-Port
、X-Forwarded-Proto
、X-Forwarded-Ssl
和 X-Forwarded-Prefix
。
X-Forwarded-Host
尽管不是标准头部,X-Forwarded-Host: <host>
是一个事实上的标准头部,用于将原始主机告知下游服务器。例如,如果将 example.com/resource
的请求发送到代理,代理将请求转发到 localhost:8080/resource
,则可以发送 X-Forwarded-Host: example.com
头部,以通知服务器原始主机是 example.com
。
X-Forwarded-Port
尽管不是标准头部,X-Forwarded-Port: <port>
是一个事实上的标准头部,用于将原始端口告知下游服务器。例如,如果将 example.com/resource
的请求发送到代理,代理将请求转发到 localhost:8080/resource
,则可以发送 X-Forwarded-Port: 443
头部,以通知服务器原始端口是 443。
X-Forwarded-Proto
尽管不是标准头部,X-Forwarded-Proto: (https|http)
是一个事实上的标准头部,用于将原始协议(例如 https / http)告知下游服务器。例如,如果将 example.com/resource
的请求发送到代理,代理将请求转发到 localhost:8080/resource
,则可以发送 X-Forwarded-Proto: https
头部,以通知服务器原始协议是 https。
X-Forwarded-Ssl
尽管不是标准头部,X-Forwarded-Ssl: (on|off)
是一个事实上的标准头部,用于将原始协议(例如 https / http)告知下游服务器。例如,如果将 example.com/resource
的请求发送到代理,代理将请求转发到 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 路径中可见,公共 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
是一个组件,它根据转发的头部修改请求的主机、端口和方案,然后移除这些头部。如果您将其声明为名为 forwardedHeaderTransformer
的 Bean,它将被检测并使用。
在 5.1 版本中,ForwardedHeaderFilter 被弃用,并由 ForwardedHeaderTransformer 取代,以便在交换创建之前更早地处理转发头部。如果仍然配置了该过滤器,它将从过滤器列表中移除,并改用 ForwardedHeaderTransformer 。 |
过滤器
在WebHandler
API 中,您可以使用 WebFilter
在过滤器处理链和目标 WebHandler
的其余部分之前和之后应用拦截式逻辑。使用WebFlux Config 时,注册 WebFilter
就像将其声明为 Spring Bean 并(可选地)通过在 Bean 声明上使用 @Order
或实现 Ordered
来表达优先级一样简单。
CORS
Spring WebFlux 通过控制器上的注解提供了对 CORS 配置的细粒度支持。但是,当您将其与 Spring Security 一起使用时,我们建议依赖内置的 CorsFilter
,它必须排在 Spring Security 的过滤器链之前。
更多详细信息请参阅CORS 部分和CORS WebFilter
。
URL Handler
您可能希望控制器端点匹配 URL 路径中带或不带尾部斜杠的路由。例如,“GET /home”和“GET /home/”都应该由使用 @GetMapping("/home")
注解的控制器方法处理。
将尾部斜杠变体添加到所有映射声明不是处理此用例的最佳方法。UrlHandlerFilter
Web 过滤器就是为此目的设计的。它可以配置为
-
当接收到带有尾部斜杠的 URL 时,使用 HTTP 重定向状态进行响应,将浏览器发送到不带尾部斜杠的 URL 变体。
-
修改请求,使其表现得像请求不带尾部斜杠发送一样,并继续处理请求。
以下是如何实例化和配置博客应用程序的 UrlHandlerFilter
-
Java
-
Kotlin
UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").mutateRequest()
.build();
val urlHandlerFilter = UrlHandlerFilter
// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
// will mutate the request to "/admin/user/account/" and make it as "/admin/user/account"
.trailingSlashHandler("/admin/**").mutateRequest()
.build()
异常
在 WebHandler
API 中,您可以使用 WebExceptionHandler
来处理来自 WebFilter
实例链和目标 WebHandler
的异常。使用 WebFlux 配置 时,注册 WebExceptionHandler
非常简单,只需将其声明为 Spring bean,并(可选地)通过在 bean 声明上使用 @Order
或实现 Ordered
来表达优先级。
下表描述了可用的 WebExceptionHandler
实现
异常处理器 | 描述 |
---|---|
|
通过将响应设置为异常的 HTTP 状态码,提供对 |
|
是 此处理器在 WebFlux 配置 中声明。 |
编解码器
spring-web
和 spring-core
模块通过 Reactive Streams 反压机制的非阻塞 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、Protocol Buffers 以及其他编码器和解码器,以及用于表单数据、multipart 内容、服务器发送事件等的仅用于 Web 的 HTTP 消息读取器和写入器实现。
ClientCodecConfigurer
和 ServerCodecConfigurer
通常用于配置和自定义应用中使用的编解码器。请参阅配置 HTTP 消息编解码器 的章节。
Jackson JSON
存在 Jackson 库时,JSON 和二进制 JSON (Smile) 都受支持。
Jackson2Decoder
的工作原理如下
-
Jackson 的异步、非阻塞解析器用于将字节块流聚合成
TokenBuffer
,每个TokenBuffer
代表一个 JSON 对象。 -
每个
TokenBuffer
都传递给 Jackson 的ObjectMapper
以创建更高级别的对象。 -
当解码为单值发布者(例如,
Mono
)时,会有一个TokenBuffer
。 -
当解码为多值发布者(例如,
Flux
)时,一旦接收到足够形成完整对象的字节,每个TokenBuffer
都会传递给ObjectMapper
。输入内容可以是 JSON 数组,或者任何 行分隔 JSON 格式,如 NDJSON、JSON Lines 或 JSON Text Sequences。
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
API 部分中的 表单数据。
一旦使用了 getFormData()
,原始的原始内容就不能再从请求体中读取了。因此,应用程序应该始终通过 ServerWebExchange
访问缓存的表单数据,而不是直接从原始请求体中读取。
Multipart
MultipartHttpMessageReader
和 MultipartHttpMessageWriter
支持解码和编码 "multipart/form-data"、"multipart/mixed" 和 "multipart/related" 内容。进而,MultipartHttpMessageReader
将实际解析为 Flux<Part>
的工作委托给另一个 HttpMessageReader
,然后简单地将各部分收集到一个 MultiValueMap
中。默认情况下使用 DefaultPartHttpMessageReader
,但这可以通过 ServerCodecConfigurer
进行更改。有关 DefaultPartHttpMessageReader
的更多信息,请参阅 DefaultPartHttpMessageReader
的 javadoc。
在服务器端,multipart 表单内容可能需要从多个位置访问,ServerWebExchange
提供了一个专用的 getMultipartData()
方法,该方法通过 MultipartHttpMessageReader
解析内容,然后缓存结果以便重复访问。请参阅 WebHandler
API 部分中的 Multipart 数据。
一旦使用了 getMultipartData()
,原始的原始内容就不能再从请求体中读取了。因此,应用程序必须始终使用 getMultipartData()
进行对各部分的重复的、类似 map 的访问,或者依赖 SynchronossPartHttpMessageReader
进行对 Flux<Part>
的一次性访问。
Protocol Buffers
ProtobufEncoder
和 ProtobufDecoder
支持解码和编码用于 com.google.protobuf.Message
类型的 "application/x-protobuf"、"application/octet-stream" 和 "application/vnd.google.protobuf" 内容。如果内容在与内容类型一起接收/发送时带有 "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 变体不支持读取消息流,更多详细信息请参阅 ProtobufJsonDecoder
的 javadoc。
限制
对输入流进行部分或全部缓冲的 Decoder
和 HttpMessageReader
实现可以配置内存中最大缓冲字节数的限制。在某些情况下,缓冲是因为输入被聚合并表示为单个对象——例如,使用 @RequestBody byte[]
、x-www-form-urlencoded
数据等的控制器方法。缓冲也可以发生在流式传输中,当拆分输入流时——例如,分隔文本、JSON 对象流等。对于这些流式传输情况,限制适用于流中与一个对象关联的字节数。
要配置缓冲区大小,您可以检查给定的 Decoder
或 HttpMessageReader
是否暴露了 maxInMemorySize
属性,如果暴露了,Javadoc 中将包含默认值的详细信息。在服务器端,ServerCodecConfigurer
提供了一个设置所有编解码器的统一入口,请参阅 HTTP 消息编解码器。在客户端,可以在 WebClient.Builder 中更改所有编解码器的限制。
对于 Multipart 解析,maxInMemorySize
属性限制非文件部分的尺寸。对于文件部分,它决定了将该部分写入磁盘的阈值。对于写入磁盘的文件部分,还有一个额外的 maxDiskUsagePerPart
属性,用于限制每个部分的磁盘空间量。还有一个 maxParts
属性,用于限制 multipart 请求中的总部件数量。要在 WebFlux 中配置这三个属性,您需要向 ServerCodecConfigurer
提供预先配置的 MultipartHttpMessageReader
实例。
流式传输
当向 HTTP 响应进行流式传输(例如,text/event-stream
、application/x-ndjson
)时,定期发送数据非常重要,以便更及时可靠地检测到断开连接的客户端。这样的发送可以是仅包含注释的空 SSE 事件,或任何其他能够有效充当心跳的“无操作”数据。
DataBuffer
DataBuffer
是 WebFlux 中字节缓冲区的表示。本参考文档的 Spring Core 部分在 数据缓冲区和编解码器 一节中有更多介绍。关键要理解的是,在 Netty 等一些服务器上,字节缓冲区是池化和引用计数的,使用后必须释放以避免内存泄漏。
WebFlux 应用程序通常无需关注此类问题,除非它们直接使用或产生数据缓冲区,而不是依赖编解码器在更高级别的对象之间进行转换,或者除非它们选择创建自定义编解码器。对于此类情况,请查看 数据缓冲区和编解码器 中的信息,尤其是 使用 DataBuffer 一节。
日志
Spring WebFlux 中的 DEBUG
级别日志被设计为紧凑、最小化且对人友好。它侧重于反复有用的高价值信息片段,而不是仅在调试特定问题时有用的其他信息。
TRACE
级别日志通常遵循与 DEBUG
相同的原则(例如也不应是信息洪流),但可用于调试任何问题。此外,某些日志消息在 TRACE
与 DEBUG
级别可能显示不同程度的详细信息。
良好的日志记录来自于使用日志的经验。如果您发现任何不符合既定目标的地方,请告知我们。
日志 ID
在 WebFlux 中,单个请求可以在多个线程上运行,而线程 ID 对于关联属于特定请求的日志消息没有用。因此,WebFlux 日志消息默认会带有请求特定的 ID 前缀。
在服务器端,日志 ID 存储在 ServerWebExchange
属性 (LOG_ID_ATTRIBUTE
) 中,而基于该 ID 的完全格式化前缀可通过 ServerWebExchange#getLogPrefix()
获取。在 WebClient
端,日志 ID 存储在 ClientRequest
属性 (LOG_ID_ATTRIBUTE
) 中,而完全格式化前缀可通过 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()
Appenders
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()