リアクティブコア
spring-web
モジュールには、リアクティブ Web アプリケーションに対する次の基本的なサポートが含まれています。
サーバーリクエスト処理には、2 つのレベルのサポートがあります。
HttpHandler : ノンブロッキング I/O および Reactive Streams バックプレッシャを使用した HTTP リクエスト処理の基本契約、および Reactor Netty、Undertow、Tomcat、Jetty、任意のサーブレットコンテナー用のアダプター。
WebHandler
API : リクエスト処理用のわずかに高いレベルの汎用 Web API。その上に、アノテーション付きコントローラーや関数エンドポイントなどの具体的なプログラミングモデルが構築されます。
クライアント側には、ノンブロッキング I/O と Reactive Streams バックプレッシャーを使用して HTTP リクエストを実行するための基本的な
ClientHttpConnector
契約と、Reactor Netty [GitHub] (英語) 、リアクティブ Jetty HttpClient [GitHub] (英語) および Apache HttpComponents (英語) のアダプターがあります。アプリケーションで使用される上位レベルの WebClient は、この基本契約に基づいて構築されています。クライアントおよびサーバーの場合、HTTP リクエストおよびレスポンスコンテンツのシリアライズおよびデシリアライズ用のコーデック。
HttpHandler
HttpHandler (Javadoc) は、リクエストとレスポンスを処理する単一のメソッドを持つ単純な契約です。これは意図的に最小限に抑えられており、その主な唯一の目的は、さまざまな HTTP サーバー API を最小限に抽象化することです。
次の表に、サポートされているサーバー API を示します。
サーバー名 | 使用されるサーバー API | Reactive Streams サポート |
---|---|---|
Netty | Netty API | |
Undertow | Undertow API | spring-web: Undertow から Reactive Streams へのブリッジ |
Tomcat | サーブレットのノンブロッキング I/O; ByteBuffers と byte[] の読み取りと書き込みを行う TomcatAPI | spring-web: サーブレットのノンブロッキング I/O から Reactive Streams ブリッジへ |
Jetty | サーブレットのノンブロッキング I/O; ByteBuffers と byte[] を書き込むための Jetty API | spring-web: サーブレットのノンブロッキング I/O から Reactive Streams ブリッジへ |
サーブレットコンテナー | サーブレットのノンブロッキング I/O | spring-web: サーブレットのノンブロッキング I/O から Reactive Streams ブリッジへ |
次の表に、サーバーの依存関係を示します(サポートされるバージョン [GitHub] (英語) も参照)。
サーバー名 | グループ id | 成果物の名前 |
---|---|---|
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 |
以下のコードスニペットは、各サーバー API で HttpHandler
アダプターを使用することを示しています。
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()
サーブレットコンテナー
サーブレットコンテナーに WAR としてデプロイするには、AbstractReactiveWebInitializer
(Javadoc) を継承して WAR に含めることができます。そのクラスは、HttpHandler
を ServletHttpHandlerAdapter
でラップし、それを Servlet
として登録します。
WebHandler
API
org.springframework.web.server
パッケージは HttpHandler
契約に基づいており、複数の WebExceptionHandler
(Javadoc) 、複数の WebFilter
(Javadoc) 、単一の WebHandler
(Javadoc) コンポーネントのチェーンを介してリクエストを処理するための汎用 Web API を提供します。チェーンは、コンポーネントが自動検出される Spring ApplicationContext
を指すか、コンポーネントをビルダーに登録するだけで、WebHttpHandlerBuilder
と組み合わせることができます。
HttpHandler
には、さまざまな HTTP サーバーの使用を抽象化するという単純なゴールがありますが、WebHandler
API は、次のような Web アプリケーションで一般的に使用される広範な機能セットを提供することを目的としています。
属性を持つユーザーセッション。
リクエスト属性。
リクエストの
Locale
またはPrincipal
を解決しました。解析およびキャッシュされたフォームデータへのアクセス。
マルチパートデータの抽象化。
さらに ..
特別な 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
にします。デフォルトでは、FormHttpMessageReader
は ServerCodecConfigurer
Bean が使用するように構成されています(Web ハンドラー API を参照)。
マルチパートデータ
ServerWebExchange
は、マルチパートデータにアクセスするための次のメソッドを公開します。
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 マルチパート [GitHub] (英語) ライブラリに基づく SynchronossPartHttpMessageReader
を使用することもできます。どちらも ServerCodecConfigurer
Bean を介して構成されます (Web ハンドラー API を参照)。
ストリーミング方式でマルチパートデータを解析するには、@RequestPart
を使用する代わりに PartEventHttpMessageReader
から返された Flux<PartEvent>
を使用できます。これは、Map
-like が名前で個々のパーツにアクセスすることを意味するため、マルチパートデータを完全に解析する必要があるためです。対照的に、@RequestBody
を使用して、コンテンツを MultiValueMap
に収集せずに Flux<PartEvent>
にデコードできます。
Forwarded ヘッダー
リクエストがロードバランサーなどのプロキシを通過すると、ホスト、ポート、スキームが変更される可能性があるため、クライアントの観点から正しいホスト、ポート、スキームを指すリンクを作成することが困難になります。
RFC 7239 [IETF] (英語) は、プロキシが元のリクエストに関する情報を提供するために使用できる Forwarded
HTTP ヘッダーを定義します。
非標準ヘッダー
X-Forwarded-Host
、X-Forwarded-Port
、X-Forwarded-Proto
、X-Forwarded-Ssl
、X-Forwarded-Prefix
など、他の非標準ヘッダーもあります。
X 転送ホスト
X-Forwarded-Host: <host>
[Mozilla] は標準ではありませんが、元のホストをダウンストリームサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語)
のリクエストが localhost:8080/resource
にリクエストを転送するプロキシに送信された場合、X-Forwarded-Host: example.com
のヘッダーを送信して、元のホストが example.com
であったことをサーバーに通知できます。
X 転送ポート
X-Forwarded-Port: <port>
は標準ではありませんが、元のポートをダウンストリームサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語)
のリクエストが localhost:8080/resource
にリクエストを転送するプロキシに送信される場合、X-Forwarded-Port: 443
のヘッダーを送信して、元のポートが 443
であったことをサーバーに通知できます。
X-Forwarded-Proto
X-Forwarded-Proto: (https|http)
[Mozilla] は標準ではありませんが、元のプロトコル (https/https など) を下流のサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語)
のリクエストがプロキシに送信され、プロキシがリクエストを localhost:8080/resource
に転送する場合、元のプロトコルが https
であったことをサーバーに通知するために X-Forwarded-Proto: https
のヘッダーを送信できます。
X-Forwarded-SSL
X-Forwarded-Ssl: (on|off)
は標準ではありませんが、元のプロトコル (https/https など) を下流のサーバーに通信するために使用される事実上の標準ヘッダーです。例: example.com/resource (英語)
のリクエストがプロキシに送信され、プロキシがリクエストを localhost:8080/resource
に転送する場合、元のプロトコルが https
であったことをサーバーに通知する X-Forwarded-Ssl: on
のヘッダーが使用されます。
X 転送プレフィックス
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
をオーバーライドすることができます。
この デプロイシナリオの一般的なケースは、ライセンスが運用アプリケーションサーバーごとに支払われる場合であり、料金を削減するにはサーバーごとに複数のアプリケーションをデプロイすることが望ましいです。もう 1 つの理由は、サーバーの実行に必要なリソースを共有するために、同じサーバー上でより多くのアプリケーションを実行することです。 これらのシナリオでは、同じサーバー上に複数のアプリケーションが存在するため、アプリケーションには空ではないコンテキストルートが必要です。ただし、アプリケーションが次のような利点を提供するさまざまなサブドメインを使用する可能性があるパブリック API の URL パスでは、これを表示しないでください。
|
シナリオ 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 構成を使用する場合、WebFilter
の登録は、Spring Bean として宣言し、(オプションで)Bean 宣言で @Order
を使用するか、Ordered
を実装することによって優先順位を表すのと同じくらい簡単です。
CORS
Spring WebFlux は、コントローラーのアノテーションを通じて CORS 構成のきめ細かなサポートを提供します。ただし、Spring Security で使用する場合は、Spring Security のチェーンフィルターよりも先に並べ替える必要があるビルトイン CorsFilter
を使用することをお勧めします。
詳細については、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 構成を使用する場合、WebExceptionHandler
の登録は、Spring Bean として宣言し、(オプションで)Bean 宣言で @Order
を使用するか、Ordered
を実装することによって優先順位を表すのと同じくらい簡単です。
次の表は、利用可能な WebExceptionHandler
実装を説明しています。
例外ハンドラー | 説明 |
---|---|
| 例外の HTTP ステータスコードへのレスポンスを設定することにより、型 |
|
このハンドラーは WebFlux 構成で宣言されています。 |
コーデック
spring-web
および spring-core
モジュールは、Reactive Streams バックプレッシャーを使用したノンブロッキング I/O を介して、上位レベルのオブジェクトとの間でバイトコンテンツを直列化および逆直列化するサポートを提供します。このサポートについて以下に説明します。
Encoder
(Javadoc) およびDecoder
(Javadoc) は、HTTP に依存しないコンテンツをエンコードおよびデコードするための低レベルの契約です。HttpMessageReader
(Javadoc) およびHttpMessageWriter
(Javadoc) は、HTTP メッセージコンテンツをエンコードおよびデコードする契約です。Encoder
はEncoderHttpMessageWriter
でラップして Web アプリケーションでの使用に適合させることができ、Decoder
はDecoderHttpMessageReader
でラップできます。DataBuffer
(Javadoc) は、さまざまなバイトバッファー表現 (たとえば、Netty、ByteBuf
、java.nio.ByteBuffer
など) を抽象化し、すべてのコーデックが動作するものです。このトピックの詳細については、「Spring コア」セクションのデータバッファとコーデックを参照してください。
spring-core
モジュールは、byte[]
、ByteBuffer
、DataBuffer
、Resource
、String
エンコーダーおよびデコーダーの実装を提供します。spring-web
モジュールは、Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers、その他のエンコーダーとデコーダー、フォームデータ、マルチパートコンテンツ、サーバー送信イベントなどの Web のみの HTTP メッセージリーダーとライターの実装を提供します。
ClientCodecConfigurer
および ServerCodecConfigurer
は、通常、アプリケーションで使用するコーデックを構成およびカスタマイズするために使用されます。HTTP メッセージコーデックの構成に関するセクションを参照してください。
Jackson JSON
JSON とバイナリ JSON(Smile [GitHub] (英語) )は両方とも、Jackson ライブラリが存在する場合にサポートされます。
Jackson2Decoder
は次のように機能します。
Jackson の非同期のノンブロッキングパーサーは、バイトチャンクのストリームを、それぞれ JSON オブジェクトを表す
TokenBuffer
に集約するために使用されます。各
TokenBuffer
は Jackson のObjectMapper
に渡され、より高いレベルのオブジェクトを作成します。単一値パブリッシャー (たとえば、
Mono
) にデコードする場合、TokenBuffer
は 1 つあります。複数値パブリッシャー (
Flux
など) にデコードする場合、完全な形式のオブジェクトに十分なバイトが受信されるとすぐに、各TokenBuffer
がObjectMapper
に渡されます。入力コンテンツは、JSON 配列、または NDJSON、JSON 行、JSON テキストシーケンスなどの行区切りの JSON [Wikipedia] (英語) 形式にすることができます。
Jackson2Encoder
は次のように機能します。
単一値パブリッシャー (たとえば、
Mono
) の場合は、ObjectMapper
を介して直列化するだけです。application/json
を使用する複数値パブリッシャーの場合、デフォルトではFlux#collectToList()
を使用して値を収集し、結果のコレクションを直列化します。application/x-ndjson
やapplication/stream+x-jackson-smile
などのストリーミングメディア型を持つ複数値のパブリッシャーの場合、行区切りの JSON [Wikipedia] (英語) 形式を使用して、各値を個別にエンコード、書き込み、フラッシュします。他のストリーミングメディア型は、エンコーダに登録される場合があります。SSE の場合、
Jackson2Encoder
はイベントごとに呼び出され、出力はフラッシュされ、遅延なく配信されます。
デフォルトでは、 |
フォームデータ
FormHttpMessageReader
および FormHttpMessageWriter
は、application/x-www-form-urlencoded
コンテンツのデコードおよびエンコードをサポートします。
フォームコンテンツに複数の場所から頻繁にアクセスする必要があるサーバー側では、ServerWebExchange
は、FormHttpMessageReader
を介してコンテンツを解析し、繰り返しアクセスするために結果をキャッシュする専用の getFormData()
メソッドを提供します。WebHandler
API セクションのフォームデータを参照してください。
getFormData()
を使用すると、元の生のコンテンツをリクエスト本文から読み取ることはできなくなります。このため、キャッシュされたフォームデータへのアクセスと生のリクエスト本文からの読み取りでは、アプリケーションは ServerWebExchange
を一貫して通過することが期待されます。
マルチパート
MultipartHttpMessageReader
および MultipartHttpMessageWriter
は、"multipart/form-data","multipart/mixed"、および "multipart/related" コンテンツのデコードとエンコードをサポートします。次に、MultipartHttpMessageReader
は別の HttpMessageReader
に委譲して、実際の解析を Flux<Part>
に渡し、パーツを単純に MultiValueMap
に集めます。デフォルトでは DefaultPartHttpMessageReader
が使用されますが、これは ServerCodecConfigurer
で変更できます。DefaultPartHttpMessageReader
の詳細については、DefaultPartHttpMessageReader
の javadoc を参照してください。
マルチパートフォームコンテンツに複数の場所からアクセスする必要があるサーバー側では、ServerWebExchange
は、MultipartHttpMessageReader
を介してコンテンツを解析し、繰り返しアクセスするために結果をキャッシュする専用の getMultipartData()
メソッドを提供します。WebHandler
API セクションのマルチパートデータを参照してください。
getMultipartData()
を使用すると、元の生のコンテンツをリクエスト本文から読み取ることはできなくなります。このため、アプリケーションは、パーツへのマップのような繰り返しアクセスのために getMultipartData()
を一貫して使用するか、Flux<Part>
への 1 回限りのアクセスのために SynchronossPartHttpMessageReader
に依存する必要があります。
プロトコルバッファ
ProtobufEncoder
および ProtobufDecoder
は、com.google.protobuf.Message
型の "application/x-protobuf","application/octet-stream"、および "application/vnd.google.protobuf" コンテンツのデコードとエンコードをサポートします。また、コンテンツがコンテンツ型 ("application/x-protobuf;delimited=true" など) とともに「区切り」パラメーターを使用して受信 / 送信される場合、値のストリームもサポートします。これには、"com.google.protobuf:protobuf-java" ライブラリ、バージョン 3.29 以上が必要です。
ProtobufJsonDecoder
および ProtobufJsonEncoder
バリアントは、JSON ドキュメントと Protobuf メッセージの読み取りと書き込みをサポートします。これらには "com.google.protobuf:protobuf-java-util" 依存関係が必要です。JSON バリアントはメッセージのストリーム読み取りをサポートしていないことに注意してください。詳細については、ProtobufJsonDecoder
の javadoc を参照してください。
制限
入力ストリームの一部またはすべてをバッファリングする Decoder
および HttpMessageReader
実装は、メモリにバッファリングする最大バイト数の制限を設定できます。入力が集約されて単一のオブジェクト(たとえば、@RequestBody byte[]
や x-www-form-urlencoded
データなどのコントローラーメソッド)として表されるため、バッファリングが発生する場合があります。バッファリングは、入力ストリームを分割するときにストリーミングでも発生する可能性があります。たとえば、区切りテキスト、JSON オブジェクトのストリームなどです。これらのストリーミングの場合、制限はストリーム内の 1 つのオブジェクトに関連付けられたバイト数に適用されます。
バッファサイズを構成するには、特定の Decoder
または HttpMessageReader
が maxInMemorySize
プロパティを公開しているかどうかを確認し、公開している場合は、Javadoc にデフォルト値の詳細が含まれていることを確認します。サーバー側では、ServerCodecConfigurer
は、すべてのコーデックを設定するための単一の場所を提供します。HTTP メッセージコーデックを参照してください。クライアント側では、すべてのコーデックの制限を WebClient.Builder で変更できます。
マルチパート解析の場合、maxInMemorySize
プロパティは非ファイルパーツのサイズを制限します。ファイルパーツの場合は、パーツがディスクに書き込まれるしきい値を決定します。ディスクに書き込まれるファイルパーツの場合、パーツごとのディスクスペースの量を制限する追加の maxDiskUsagePerPart
プロパティがあります。マルチパートリクエストのパートの総数を制限する maxParts
プロパティもあります。WebFlux で 3 つすべてを構成するには、構成済みの MultipartHttpMessageReader
から ServerCodecConfigurer
のインスタンスを提供する必要があります。
ストリーミング
HTTP レスポンス(たとえば、text/event-stream
、application/x-ndjson
)にストリーミングする場合、切断されたクライアントを後でではなく早く確実に検出するために、定期的にデータを送信することが重要です。このような送信は、コメントのみの空の SSE イベント、またはハートビートとして効果的に機能するその他の "no-op" データである可能性があります。
DataBuffer
DataBuffer
は、WebFlux のバイトバッファの表現です。このリファレンスの Spring コアの部分については、データバッファとコーデックのセクションで詳しく説明しています。理解しておくべき重要な点は、Netty のような一部のサーバーでは、バイトバッファーがプールされ、参照がカウントされ、メモリリークを回避するために消費されたときに解放する必要があるということです。
WebFlux アプリケーションは、コーデックに依存してより高いレベルのオブジェクトとの間で変換するのではなく、データバッファーを直接消費または生成しない限り、またはカスタムコーデックを作成することを選択しない限り、一般にそのような課題を考慮する必要はありません。そのような場合は、データバッファとコーデックの情報、特に DataBuffer の使用のセクションを確認してください。
ログ
Spring での DEBUG
レベルのロギング WebFlux は、コンパクト、最小限、人に優しいように設計されています。これは、特定の課題をデバッグする場合にのみ役立つ他の情報と比較して、何度も役立つ情報の価値の高いビットに焦点を当てています。
TRACE
レベルのログは、一般に DEBUG
と同じ原則に従います(たとえば、ファイアホースであってはなりません)。ただし、課題のデバッグに使用できます。さらに、一部のログメッセージでは、TRACE
と DEBUG
で詳細レベルが異なる場合があります。
適切なログは、ログを使用した経験から得られます。記載されているゴールを達成できないものを見つけた場合は、お知らせください。
ログ ID
WebFlux では、単一のリクエストを複数のスレッドで実行できます。スレッド ID は、特定のリクエストに属するログメッセージの関連付けには役立ちません。これがデフォルトで、WebFlux ログメッセージの先頭にリクエスト固有の ID が付けられる理由です。
サーバー側では、ログ ID は ServerWebExchange
属性(LOG_ID_ATTRIBUTE
(Javadoc) )に格納されますが、その ID に基づいて完全にフォーマットされたプレフィックスは ServerWebExchange#getLogPrefix()
から利用できます。WebClient
側では、ログ ID は ClientRequest
属性(LOG_ID_ATTRIBUTE
(Javadoc) )に保存されますが、完全にフォーマットされたプレフィックスは 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 や Log4J2 などのロギングライブラリは、ブロッキングを回避する非同期ロガーを提供します。これらには、ロギング用にキューに入れることができなかったメッセージをドロップする可能性があるなどの独自の欠点がありますが、これらは、リアクティブなノンブロッキングアプリケーションで現在使用できる最良のオプションです。
カスタムコーデック
アプリケーションは、追加のメディア型をサポートするカスタムコーデック、またはデフォルトコーデックでサポートされていない特定の動作を登録できます。
開発者によって表された一部の構成オプションは、デフォルトのコーデックに適用されます。カスタムコーデックは、バッファリング制限の強制や機密データのログ記録など、これらの設定に合わせて調整する機会を得たい場合があります。
次の例は、クライアント側のリクエストに対してこれを行う方法を示しています。
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()