響應式核心

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 HttpClientApache HttpComponents 的介面卡。應用程式中使用的更高層 WebClient 構建在此基本契約之上。

  • 對於客戶端和伺服器,用於 HTTP 請求和響應內容序列化和反序列化的編解碼器

HttpHandler

HttpHandler 是一個簡單的契約,只有一個方法用於處理請求和響應。它有意地保持最小化,其主要和唯一目的是作為不同 HTTP 伺服器 API 的一個最小抽象。

下表描述了支援的伺服器 API

伺服器名稱 使用的伺服器 API Reactive Streams 支援

Netty

Netty API

Reactor Netty

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 應用程式中常用的一組更廣泛的功能,例如

  • 帶有屬性的使用者會話。

  • 請求屬性。

  • 請求的解析後的 LocalePrincipal

  • 訪問已解析和快取的表單資料。

  • Multipart 資料抽象。

  • 等等..

特殊 Bean 型別

下表列出了 WebHttpHandlerBuilder 可以在 Spring ApplicationContext 中自動檢測到的元件,或者可以直接向其註冊的元件

Bean 名稱 Bean 型別 計數 描述

<any>

WebExceptionHandler

0..N

提供處理 WebFilter 例項鏈和目標 WebHandler 丟擲的異常。更多詳細資訊,請參閱異常

<any>

WebFilter

0..N

在其餘過濾器鏈和目標 WebHandler 之前和之後應用攔截式邏輯。更多詳細資訊,請參閱過濾器

webHandler

WebHandler

1

請求處理器。

webSessionManager

WebSessionManager

0..1

透過 ServerWebExchange 上的方法暴露的 WebSession 例項管理器。預設為 DefaultWebSessionManager

serverCodecConfigurer

ServerCodecConfigurer

0..1

用於訪問 HttpMessageReader 例項,以解析表單資料和 multipart 資料,這些資料隨後透過 ServerWebExchange 上的方法暴露。預設為 ServerCodecConfigurer.create()

localeContextResolver

LocaleContextResolver

0..1

透過 ServerWebExchange 上的方法暴露的 LocaleContext 解析器。預設為 AcceptHeaderLocaleContextResolver

forwardedHeaderTransformer

ForwardedHeaderTransformer

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-datamultipart/mixedmultipart/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-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-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}

代理沒有字首,而應用程式 app1app2 分別具有路徑字首 /app1/app2。代理可以傳送 X-Forwarded-Prefix: ,以便空字首覆蓋伺服器字首 /app1/app2

這種部署場景的一個常見情況是,許可證按生產應用伺服器付費,因此更傾向於每臺伺服器部署多個應用以降低費用。另一個原因是同一臺伺服器上執行更多應用,以共享伺服器執行所需的資源。

在這些場景中,應用程式需要非空的上下文根,因為同一臺伺服器上有多個應用程式。但是,這不應在公共 API 的 URL 路徑中可見,公共 API 中應用程式可以使用不同的子域,這提供了諸如以下好處

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

  • 應用程式的獨立擴縮容(不同域指向不同 IP 地址)

場景 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

安全注意事項

轉發頭部存在安全問題,因為應用程式無法得知這些頭部是由代理(按預期)新增,還是由惡意客戶端新增。這就是為什麼信任邊界上的代理應配置為移除來自外部的不可信轉發流量。您還可以將 ForwardedHeaderTransformer 配置為 removeOnly=true,在這種情況下,它只會移除頭部而不使用它們。

過濾器

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 實現

異常處理器 描述

ResponseStatusExceptionHandler

透過將響應設定為異常的 HTTP 狀態碼,提供對 ResponseStatusException 型別異常的處理。

WebFluxResponseStatusExceptionHandler

ResponseStatusExceptionHandler 的擴充套件,它還可以確定任何異常上 @ResponseStatus 註解的 HTTP 狀態碼。

此處理器在 WebFlux 配置 中宣告。

編解碼器

spring-webspring-core 模組透過 Reactive Streams 反壓機制的非阻塞 I/O,提供對位元組內容與更高級別物件之間的序列化和反序列化支援。以下描述了此支援

  • EncoderDecoder 是與 HTTP 無關的內容編碼和解碼的低級別契約。

  • HttpMessageReaderHttpMessageWriter 是編碼和解碼 HTTP 訊息內容的契約。

  • 一個 Encoder 可以用 EncoderHttpMessageWriter 進行包裝以適應 Web 應用中的使用,而一個 Decoder 可以用 DecoderHttpMessageReader 進行包裝。

  • DataBuffer 抽象了不同的位元組緩衝區表示(例如,Netty ByteBufjava.nio.ByteBuffer 等),並且是所有編解碼器工作的基礎。有關此主題的更多資訊,請參閱“Spring Core”部分中的 資料緩衝區和編解碼器

spring-core 模組提供了 byte[]ByteBufferDataBufferResourceString 編碼器和解碼器實現。spring-web 模組提供了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 以及其他編碼器和解碼器,以及用於表單資料、multipart 內容、伺服器傳送事件等的僅用於 Web 的 HTTP 訊息讀取器和寫入器實現。

ClientCodecConfigurerServerCodecConfigurer 通常用於配置和自定義應用中使用的編解碼器。請參閱配置 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-ndjsonapplication/stream+x-jackson-smile)的多值釋出者,使用 行分隔 JSON 格式對每個值進行單獨編碼、寫入和重新整理。可以向編碼器註冊其他流式媒體型別。

  • 對於 SSE,每發生一個事件就會呼叫 Jackson2Encoder,並且會重新整理輸出以確保及時傳送。

預設情況下,Jackson2EncoderJackson2Decoder 都不支援 String 型別元素。相反,預設的假設是字串或字串序列表示序列化的 JSON 內容,由 CharSequenceEncoder 渲染。如果需要從 Flux<String> 渲染 JSON 陣列,請使用 Flux#collectToList() 並編碼一個 Mono<List<String>>

表單資料

FormHttpMessageReaderFormHttpMessageWriter 支援解碼和編碼 application/x-www-form-urlencoded 內容。

在伺服器端,表單內容通常需要從多個位置訪問,ServerWebExchange 提供了一個專用的 getFormData() 方法,該方法透過 FormHttpMessageReader 解析內容,然後快取結果以便重複訪問。請參閱 WebHandler API 部分中的 表單資料

一旦使用了 getFormData(),原始的原始內容就不能再從請求體中讀取了。因此,應用程式應該始終透過 ServerWebExchange 訪問快取的表單資料,而不是直接從原始請求體中讀取。

Multipart

MultipartHttpMessageReaderMultipartHttpMessageWriter 支援解碼和編碼 "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

ProtobufEncoderProtobufDecoder 支援解碼和編碼用於 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 及更高版本。

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

限制

對輸入流進行部分或全部緩衝的 DecoderHttpMessageReader 實現可以配置記憶體中最大緩衝位元組數的限制。在某些情況下,緩衝是因為輸入被聚合並表示為單個物件——例如,使用 @RequestBody byte[]x-www-form-urlencoded 資料等的控制器方法。緩衝也可以發生在流式傳輸中,當拆分輸入流時——例如,分隔文字、JSON 物件流等。對於這些流式傳輸情況,限制適用於流中與一個物件關聯的位元組數。

要配置緩衝區大小,您可以檢查給定的 DecoderHttpMessageReader 是否暴露了 maxInMemorySize 屬性,如果暴露了,Javadoc 中將包含預設值的詳細資訊。在伺服器端,ServerCodecConfigurer 提供了一個設定所有編解碼器的統一入口,請參閱 HTTP 訊息編解碼器。在客戶端,可以在 WebClient.Builder 中更改所有編解碼器的限制。

對於 Multipart 解析maxInMemorySize 屬性限制非檔案部分的尺寸。對於檔案部分,它決定了將該部分寫入磁碟的閾值。對於寫入磁碟的檔案部分,還有一個額外的 maxDiskUsagePerPart 屬性,用於限制每個部分的磁碟空間量。還有一個 maxParts 屬性,用於限制 multipart 請求中的總部件數量。要在 WebFlux 中配置這三個屬性,您需要向 ServerCodecConfigurer 提供預先配置的 MultipartHttpMessageReader 例項。

流式傳輸

當向 HTTP 響應進行流式傳輸(例如,text/event-streamapplication/x-ndjson)時,定期傳送資料非常重要,以便更及時可靠地檢測到斷開連線的客戶端。這樣的傳送可以是僅包含註釋的空 SSE 事件,或任何其他能夠有效充當心跳的“無操作”資料。

DataBuffer

DataBuffer 是 WebFlux 中位元組緩衝區的表示。本參考文件的 Spring Core 部分在 資料緩衝區和編解碼器 一節中有更多介紹。關鍵要理解的是,在 Netty 等一些伺服器上,位元組緩衝區是池化和引用計數的,使用後必須釋放以避免記憶體洩漏。

WebFlux 應用程式通常無需關注此類問題,除非它們直接使用或產生資料緩衝區,而不是依賴編解碼器在更高級別的物件之間進行轉換,或者除非它們選擇建立自定義編解碼器。對於此類情況,請檢視 資料緩衝區和編解碼器 中的資訊,尤其是 使用 DataBuffer 一節。

日誌

Spring WebFlux 中的 DEBUG 級別日誌被設計為緊湊、最小化且對人友好。它側重於反覆有用的高價值資訊片段,而不是僅在除錯特定問題時有用的其他資訊。

TRACE 級別日誌通常遵循與 DEBUG 相同的原則(例如也不應是資訊洪流),但可用於除錯任何問題。此外,某些日誌訊息在 TRACEDEBUG 級別可能顯示不同程度的詳細資訊。

良好的日誌記錄來自於使用日誌的經驗。如果您發現任何不符合既定目標的地方,請告知我們。

日誌 ID

在 WebFlux 中,單個請求可以在多個執行緒上執行,而執行緒 ID 對於關聯屬於特定請求的日誌訊息沒有用。因此,WebFlux 日誌訊息預設會帶有請求特定的 ID 字首。

在伺服器端,日誌 ID 儲存在 ServerWebExchange 屬性 (LOG_ID_ATTRIBUTE) 中,而基於該 ID 的完全格式化字首可透過 ServerWebExchange#getLogPrefix() 獲取。在 WebClient 端,日誌 ID 儲存在 ClientRequest 屬性 (LOG_ID_ATTRIBUTE) 中,而完全格式化字首可透過 ClientRequest#logPrefix() 獲取。

敏感資料

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 等日誌庫提供非同步日誌記錄器,可避免阻塞。雖然它們有自己的缺點,例如可能丟棄無法排隊記錄的訊息,但它們是當前用於反應式、非阻塞應用程式的最佳可用選項。

自定義編解碼器

應用程式可以註冊自定義編解碼器,以支援額外的媒體型別,或支援預設編解碼器不支援的特定行為。

開發者表達的一些配置選項在預設編解碼器上強制執行。自定義編解碼器可能希望有機會與這些偏好保持一致,例如 強制執行緩衝限制記錄敏感資料

以下示例展示瞭如何對客戶端請求進行此操作

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