リアクティブコア

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 を示します。

サーバー名 使用されるサーバー APIReactive Streams サポート

Netty

Netty API

Reactor Netty [GitHub] (英語)

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>

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 インスタンスへのアクセス用。このデータは、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 にします。デフォルトでは、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-datamultipart/mixedmultipart/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-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-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 パスでは、これを表示しないでください。

  • 同一生成元ポリシーなどのセキュリティ強化

  • アプリケーションの独立したスケーリング (異なるドメインは異なる IP アドレスを指します)

シナリオ 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 が使用されます。

セキュリティに関する考慮事項

アプリケーションは、ヘッダーが意図したとおりにプロキシによって追加されたのか、それとも悪意のあるクライアントによって追加されたのかを知ることができないため、転送されたヘッダーにはセキュリティ上の考慮事項があります。このため、信頼の境界にあるプロキシは、外部から来る信頼できない転送トラフィックを削除するように構成する必要があります。ForwardedHeaderTransformer を removeOnly=true で構成することもできます。その場合、ヘッダーは削除されますが、使用されません。

フィルター

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 実装を説明しています。

例外ハンドラー 説明

ResponseStatusExceptionHandler

例外の HTTP ステータスコードへのレスポンスを設定することにより、型 ResponseStatusException (Javadoc) の例外の処理を提供します。

WebFluxResponseStatusExceptionHandler

@ResponseStatus アノテーションの HTTP ステータスコードも例外で決定できる ResponseStatusExceptionHandler の拡張。

このハンドラーは 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、ByteBufjava.nio.ByteBuffer など) を抽象化し、すべてのコーデックが動作するものです。このトピックの詳細については、「Spring コア」セクションのデータバッファとコーデックを参照してください。

spring-core モジュールは、byte[]ByteBufferDataBufferResourceString エンコーダーおよびデコーダーの実装を提供します。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 はイベントごとに呼び出され、出力はフラッシュされ、遅延なく配信されます。

デフォルトでは、Jackson2Encoder と Jackson2Decoder は両方とも型 String の要素をサポートしていません。代わりに、デフォルトの仮定は、CharSequenceEncoder によってレンダリングされる直列化された JSON コンテンツを表すストリングまたはストリングのシーケンスです。Flux<String> から JSON 配列をレンダリングする必要がある場合は、Flux#collectToList() を使用して Mono<List<String>> をエンコードします。

フォームデータ

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-streamapplication/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()