響應式核心
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} -> https://:8080/app1/{path}
字首是捕獲組 {path}
之前的路徑的開始部分。對於代理,字首是 /api
,而對於伺服器,字首是 /app1
。在這種情況下,代理可以傳送 X-Forwarded-Prefix: /api
,以便原始字首 /api
覆蓋伺服器字首 /app1
。
場景 2:刪除路徑字首
有時,應用程式可能希望刪除字首。例如,考慮以下代理到伺服器的對映
https://app1.example.com/{path} -> https://:8080/app1/{path} https://app2.example.com/{path} -> https://:8080/app2/{path}
代理沒有字首,而應用程式 app1
和 app2
分別具有路徑字首 /app1
和 /app2
。代理可以傳送 X-Forwarded-Prefix:
,以便空字首覆蓋伺服器字首 /app1
和 /app2
。
這種部署場景的一個常見情況是,許可證按生產應用伺服器付費,因此更傾向於每臺伺服器部署多個應用以降低費用。另一個原因是同一臺伺服器上執行更多應用,以共享伺服器執行所需的資源。 在這些場景中,應用程式需要非空的上下文根,因為同一臺伺服器上有多個應用程式。但是,這不應在公共 API 的 URL 路徑中可見,公共 API 中應用程式可以使用不同的子域,這提供了諸如以下好處
|
場景 3:插入路徑字首
在其他情況下,可能需要前置一個字首。例如,考慮以下代理到伺服器的對映
https://example.com/api/app1/{path} -> https://: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()