反應式核心
spring-web
模組包含以下針對反應式 Web 應用程式的基本支援
-
對於伺服器請求處理,有兩個層級的支援。
-
HttpHandler:HTTP 請求處理的基本契約,具有非阻塞 I/O 和反應式串流回壓,以及 Reactor Netty、Undertow、Tomcat、Jetty 和任何 Servlet 容器的適配器。
-
WebHandler
API:稍微高階、通用的 Web API,用於請求處理,在其之上建構了具體的程式設計模型,例如註解控制器和函數式端點。
-
-
對於用戶端,有一個基本的
ClientHttpConnector
契約,用於執行具有非阻塞 I/O 和反應式串流回壓的 HTTP 請求,以及 Reactor Netty、反應式 Jetty HttpClient 和 Apache HttpComponents 的適配器。應用程式中使用的更高階 WebClient 建構於此基本契約之上。 -
對於用戶端和伺服器,用於序列化和反序列化 HTTP 請求和回應內容的 編解碼器。
HttpHandler
HttpHandler 是一個簡單的契約,具有單一方法來處理請求和回應。它刻意保持最小化,其主要且唯一目的是作為不同 HTTP 伺服器 API 的最小抽象化。
下表描述了支援的伺服器 API
伺服器名稱 | 使用的伺服器 API | 反應式串流支援 |
---|---|---|
Netty |
Netty API |
|
Undertow |
Undertow API |
spring-web:Undertow 到反應式串流橋接器 |
Tomcat |
Servlet 非阻塞 I/O;Tomcat API 用於讀取和寫入 ByteBuffers 而非 byte[] |
spring-web:Servlet 非阻塞 I/O 到反應式串流橋接器 |
Jetty |
Servlet 非阻塞 I/O;Jetty API 用於寫入 ByteBuffers 而非 byte[] |
spring-web:Servlet 非阻塞 I/O 到反應式串流橋接器 |
Servlet 容器 |
Servlet 非阻塞 I/O |
spring-web:Servlet 非阻塞 I/O 到反應式串流橋接器 |
下表描述了伺服器相依性(另請參閱 支援的版本)
伺服器名稱 | 群組 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 容器,您可以擴充並包含 AbstractReactiveWebInitializer
在 WAR 中。該類別使用 ServletHttpHandlerAdapter
包裝 HttpHandler
,並將其註冊為 Servlet
。
WebHandler
API
org.springframework.web.server
套件建構於 HttpHandler
契約之上,以透過多個 WebExceptionHandler
、多個 WebFilter
和單一 WebHandler
組件的鏈結來處理請求,從而提供通用的 Web API。鏈結可以使用 WebHttpHandlerBuilder
組裝在一起,方法是簡單地指向 Spring ApplicationContext
,組件在其中 自動偵測,和/或使用建構器註冊組件。
雖然 HttpHandler
的簡單目標是抽象化不同 HTTP 伺服器的使用,但 WebHandler
API 旨在提供更廣泛的功能集,這些功能通常在 Web 應用程式中使用,例如
-
具有屬性的使用者 Session。
-
請求屬性。
-
針對請求解析的
Locale
或Principal
。 -
存取已剖析和快取的表單資料。
-
Multipart 資料的抽象化。
-
以及更多..
特殊 Bean 類型
下表列出了 WebHttpHandlerBuilder
可以在 Spring ApplicationContext 中自動偵測或直接向其註冊的組件
Bean 名稱 | 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>
DefaultServerWebExchange
使用組態的 HttpMessageReader
將表單資料 (application/x-www-form-urlencoded
) 剖析為 MultiValueMap
。依預設,FormHttpMessageReader
組態為由 ServerCodecConfigurer
Bean 使用(請參閱 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
,其沒有任何第三方相依性。或者,可以使用 SynchronossPartHttpMessageReader
,其基於 Synchronoss NIO Multipart 程式庫。兩者都透過 ServerCodecConfigurer
Bean 組態(請參閱 Web Handler API)。
若要以串流方式剖析 multipart 資料,您可以使用從 PartEventHttpMessageReader
傳回的 Flux<PartEvent>
,而不是使用 @RequestPart
,因為這表示類似 Map
的方式存取依名稱區分的個別部分,因此需要完整剖析 multipart 資料。相反地,您可以使用 @RequestBody
將內容解碼為 Flux<PartEvent>
,而無需收集到 MultiValueMap
。
轉發的標頭
當請求通過負載平衡器等 Proxy 時,主機、連接埠和 Scheme 可能會變更,這使得從用戶端角度建立指向正確主機、連接埠和 Scheme 的連結成為挑戰。
RFC 7239 定義了 Forwarded
HTTP 標頭,Proxy 可以使用它來提供有關原始請求的資訊。
非標準標頭
還有其他非標準標頭,包括 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
的請求傳送到 Proxy,Proxy 將請求轉發到 localhost:8080/resource
,則可以傳送 X-Forwarded-Host: example.com
標頭,以告知伺服器原始主機是 example.com
。
X-Forwarded-Port
雖然不是標準,但 X-Forwarded-Port: <port>
是一個事實上的標準標頭,用於將原始連接埠傳達給下游伺服器。例如,如果將 example.com/resource
的請求傳送到 Proxy,Proxy 將請求轉發到 localhost:8080/resource
,則可以傳送 X-Forwarded-Port: 443
標頭,以告知伺服器原始連接埠是 443
。
X-Forwarded-Proto
雖然不是標準,但 X-Forwarded-Proto: (https|http)
是一個事實上的標準標頭,用於將原始協定(例如,https/http)傳達給下游伺服器。例如,如果將 example.com/resource
的請求傳送到 Proxy,Proxy 將請求轉發到 localhost:8080/resource
,則可以傳送 X-Forwarded-Proto: https
標頭,以告知伺服器原始協定是 https
。
X-Forwarded-Ssl
雖然不是標準,但 X-Forwarded-Ssl: (on|off)
是一個事實上的標準標頭,用於將原始協定(例如,https/http)傳達給下游伺服器。例如,如果將 example.com/resource
的請求傳送到 Proxy,Proxy 將請求轉發到 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://127.0.0.1:8080/app1/{path}
前綴是擷取群組 {path}
之前的路徑開頭。對於 Proxy,前綴是 /api
,而對於伺服器,前綴是 /app1
。在這種情況下,Proxy 可以傳送 X-Forwarded-Prefix: /api
,讓原始前綴 /api
覆寫伺服器前綴 /app1
。
情境 2:移除路徑前綴
有時,應用程式可能希望移除前綴。例如,請考慮以下 Proxy 到伺服器的對應
https://app1.example.com/{path} -> https://127.0.0.1:8080/app1/{path} https://app2.example.com/{path} -> https://127.0.0.1:8080/app2/{path}
Proxy 沒有前綴,而應用程式 app1
和 app2
分別具有路徑前綴 /app1
和 /app2
。Proxy 可以傳送 X-Forwarded-Prefix:
,讓空前綴覆寫伺服器前綴 /app1
和 /app2
。
此部署情境的常見情況是,許可證是按生產應用程式伺服器付費的,並且最好在每個伺服器上部署多個應用程式以減少費用。另一個原因是為了在同一伺服器上執行更多應用程式,以便共用伺服器執行所需的資源。 在這些情境中,應用程式需要非空的 Context Root,因為同一伺服器上有多個應用程式。但是,這不應在公用 API 的 URL 路徑中可見,在公用 API 中,應用程式可以使用不同的子網域,從而提供諸如以下優點:
|
情境 3:插入路徑前綴
在其他情況下,可能需要預先加入前綴。例如,考慮以下 Proxy 到伺服器的對應
https://example.com/api/app1/{path} -> https://127.0.0.1:8080/app1/{path}
在此情況下,Proxy 的前綴為 /api/app1
,而伺服器的前綴為 /app1
。Proxy 可以傳送 X-Forwarded-Prefix: /api/app1
,讓原始前綴 /api/app1
覆寫伺服器前綴 /app1
。
ForwardedHeaderTransformer
ForwardedHeaderTransformer
是一個組件,它會根據轉發的標頭修改請求的主機、埠和 scheme,然後移除這些標頭。如果您將其宣告為名稱為 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 處理器
您可能希望您的控制器端點比對 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 Config 時,註冊 WebExceptionHandler
就像將其宣告為 Spring bean 一樣簡單,並且 (選擇性地) 可以透過在 bean 宣告上使用 @Order
或實作 Ordered
來表達優先順序。
下表描述了可用的 WebExceptionHandler
實作
例外處理器 | 描述 |
---|---|
|
透過將回應設定為例外的 HTTP 狀態碼,為 |
|
此處理器在 WebFlux Config 中宣告。 |
編解碼器
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 和其他編碼器和解碼器,以及用於表單資料、多部分內容、伺服器發送事件等的僅限 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
來存取快取的表單資料,而不是從原始請求正文中讀取。
多部分
MultipartHttpMessageReader
和 MultipartHttpMessageWriter
支援解碼和編碼 "multipart/form-data"、"multipart/mixed" 和 "multipart/related" 內容。反過來,MultipartHttpMessageReader
委派給另一個 HttpMessageReader
進行實際剖析,以產生 Flux<Part>
,然後簡單地將這些部分收集到 MultiValueMap
中。預設情況下,使用 DefaultPartHttpMessageReader
,但可以透過 ServerCodecConfigurer
變更此設定。有關 DefaultPartHttpMessageReader
的更多資訊,請參閱 DefaultPartHttpMessageReader
的 javadoc。
在伺服器端,多部分表單內容可能需要從多個位置存取,ServerWebExchange
提供專用的 getMultipartData()
方法,該方法透過 MultipartHttpMessageReader
剖析內容,然後快取結果以供重複存取。請參閱 多部分資料 在 WebHandler
API 章節中。
一旦使用了 getMultipartData()
,就無法再從請求正文中讀取原始原始內容。因此,應用程式必須始終如一地使用 getMultipartData()
進行重複的、類似地圖的部分存取,否則依賴 SynchronossPartHttpMessageReader
進行一次性存取 Flux<Part>
。
Protocol Buffers
ProtobufEncoder
和 ProtobufDecoder
支援解碼和編碼 com.google.protobuf.Message
類型的 "application/x-protobuf"、"application/octet-stream" 和 "application/vnd.google.protobuf" 內容。如果使用帶有內容類型 (例如 "application/x-protobuf;delimited=true") 的 "delimited" 參數接收/傳送內容,它們也支援值串流。這需要 "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 中變更所有編解碼器的限制。
對於 多部分剖析,maxInMemorySize
屬性限制了非檔案部分的大小。對於檔案部分,它決定了將部分寫入磁碟的閾值。對於寫入磁碟的檔案部分,還有一個額外的 maxDiskUsagePerPart
屬性來限制每個部分的磁碟空間量。還有一個 maxParts
屬性來限制多部分請求中的總部分數。要在 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()
附加器
諸如 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()