I. 概要
spring-web モジュールには、応答性の高い Web アプリケーションに対する次の基本サポートが含まれています。
- サーバー要求処理には 2 つのレベルのサポートがあります。
- HttpHandler: ノンブロッキング I/O および Reactive Streams バックプレッシャー、および Reactor Netty、Undertow、Tomcat、Jetty、および任意のサーブレット コンテナー用のアダプターを使用した HTTP リクエスト処理の基本規則。
- WebHandler API: リクエストを処理するための少し高いレベルの一般的な Web API。これに基づいて、アノテーション付きコントローラーや機能エンドポイントなどの特定のプログラミング モデルが確立されます。
- クライアントには、ノンブロッキング I/O および Reactive Streams バックプレッシャーを使用して HTTP リクエストを実行する基本的な ClientHttpConnector 規約のほか、Reactor Netty、reactive Jetty HttpClient、および Apache HttpComponents 用のアダプターがあります。アプリケーションで使用される上位レベルの WebClient は、この基本的な規則に基づいて構築されています。
2.HTTPハンドラー
HttpHandler は、リクエストとレスポンスを処理する単一のメソッドを備えた単純なコントラクトです。これは意図的に最小限にされており、その主かつ唯一の目的は、さまざまな HTTP サーバー API の最小限の抽象化であることです。
次の表では、サポートされているサーバー API について説明します。
サーバ |
使用されるサーバーAPI |
リアクティブストリームのサポート |
ネッティ |
Netty API |
リアクター・ネッティ |
逆流 |
Undertow API |
spring-web: Undertow から Reactive Streams へのブリッジ |
トムキャット |
サーブレットのノンブロッキング I/O、Tomcat API の読み取りと書き込み ByteBuffers と byte[] |
spring-web: Reactive Streams ブリッジへのサーブレットのノンブロッキング I/O |
桟橋 |
サーブレットのノンブロッキング I/O、Jetty API の書き込み ByteBuffers と byte[] |
spring-web: Reactive Streams ブリッジへのサーブレットのノンブロッキング I/O |
サーブレットコンテナ |
サーブレットのノンブロッキング I/O |
spring-web: Reactive Streams ブリッジへのサーブレットのノンブロッキング I/O |
次の表にサーバーの依存関係を示します (サポートされているバージョンも参照)。
サーバ |
グループID |
アーティファクト名 |
リアクター・ネッティ |
io.projectreactor.netty |
リアクターネット |
逆流 |
io.アンダートウ |
引き波コア |
トムキャット |
org.apache.tomcat.embed |
tomcat-embed-core |
桟橋 |
org.eclipse.jetty |
桟橋サーバー、桟橋サーブレット |
次のコード スニペットは、各サーバー API での HttpHandler アダプターの使用を示しています。
1、リアクターネッティ
ジャワ
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bindNow();
2、引き波
ジャワ
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
3、トムキャット
ジャワ
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();
4、桟橋
ジャワ
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();
5. サーブレットコンテナ
WAR として任意のサーブレット コンテナにデプロイするには、AbstractReactiveWebInitializer をサブクラス化し、WAR に含めることができます。このクラスは、HttpHandler を ServletHttpHandlerAdapter でラップし、サーブレットとして登録します。
3.WebハンドラーAPI
org.springframework.web.server パッケージは HttpHandler コントラクトに基づいて構築されており、複数の WebExceptionHandler、複数の WebFilter、および単一の WebHandler コンポーネントのチェーンを通じてリクエストを処理する共通の Web API を提供します。このチェーンは、コンポーネントを自動的に検出する Spring ApplicationContext を単に指すことによって、またはコンポーネントをビルダーに登録することによって、WebHttpHandlerBuilder と組み合わせることができます。
HttpHandler には、さまざまな HTTP サーバーの使用を抽象化するという単純な目標がありますが、WebHandler API は、Web アプリケーションで一般的に使用される、次のような広範な機能セットを提供することを目的としています。
- ユーザーセッションと属性。
- リクエスト属性。
- リクエストのロケールまたはプリンシパルを解決します。
- 解析されキャッシュされたフォーム データにアクセスします。
- マルチパートデータの抽象化。
- 待って。。。
1.特殊なBeanの種類
次の表に、WebHttpHandlerBuilder が Spring ApplicationContext で自動的に検出できるコンポーネント、または Spring ApplicationContext に直接登録できるコンポーネントを示します。
豆 |
豆の種類 |
説明する |
説明する |
<任意> |
WebExceptionハンドラ |
0..N |
WebFilter インスタンス チェーンおよびターゲット WebHandler からの例外の処理を提供します。詳細については、「例外」を参照してください。 |
<任意> |
ウェブフィルター |
0..N |
残りのフィルター チェーンとターゲット WebHandler の前後にインターセプト ロジックを適用します。詳細については、「フィルター」を参照してください。 |
ウェブハンドラー |
Webハンドラー |
1 |
リクエストハンドラー。 |
webセッションマネージャー |
Webセッションマネージャー |
0..1 |
WebSession インスタンスのマネージャー。ServerWebExchange のメソッドを通じて公開されます。デフォルトは DefaultWebSessionManager です。 |
サーバーコーデック構成者 |
サーバーコーデックコンフィギュラー |
0..1 |
HttpMessageReader インスタンスにアクセスしてフォーム データとマルチパート データを解析し、ServerWebExchange のメソッドを通じて公開するために使用されます。デフォルトでは、ServerCodecConfigurer.create() が使用されます。 |
localeContextResolver |
LocaleContextResolver |
0..1 |
localeContext パーサーは、ServerWebExchange のメソッドを通じて公開されます。デフォルトは AcceptHeaderLocaleContextResolver です。 |
forwardedHeaderTransformer |
ForwardedHeaderTransformer |
0..1 |
転送されたタイプのヘッダーを処理するために使用され、ヘッダーを抽出して削除することも、単に削除することもできます。デフォルトでは使用されません。 |
2. フォームデータ
ServerWebExchange は、フォーム データにアクセスするための次のメソッドを公開します。
ジャワ
Mono<MultiValueMap<String, String>> getFormData();
DefaultServerWebExchange は、構成された HttpMessageReader を使用して、フォーム データ (application/x-www-form-urlencoded) を解析して MultiValueMap に変換します。デフォルトでは、FormHttpMessageReader は ServerCodecConfigurer Bean によって使用されるように構成されています (Web ハンドラー API を参照)。
3、マルチパートデータ
サーブレット テクノロジー スタックの対応するコンテンツを参照してください。
ServerWebExchange は、マルチパート データにアクセスするための次のメソッドを公開します。
ジャワ
Mono<MultiValueMap<String, Part>> getMultipartData();
DefaultServerWebExchange は、構成された HttpMessageReader<MultiValueMap<String, Part>> を使用して、multipart/form-data、multipart/mixed、および multipart/関連コンテンツを MultiValueMap に解析します。デフォルトでは、これは DefaultPartHttpMessageReader であり、サードパーティの依存関係はありません。あるいは、Synchronoss NIO Multipart ライブラリに基づく SynchronossPartHttpMessageReader を使用することもできます。どちらも ServerCodecConfigurer Bean を通じて設定されます (Web ハンドラー API を参照)。
マルチパート データをストリーミング方式で解析するには、@RequestPart を使用する代わりに PartEventHttpMessageReader から返された Flux<PartEvent> を使用できます。これは、単一のパートに名前で Map のようにアクセスすることを意味するため、マルチパート データは次のことを行う必要があります。完全に解析される必要があります。対照的に、@RequestBody を使用すると、MultiValueMap を収集せずにコンテンツを Flux<PartEvent> にデコードできます。
4、転送ヘッダー
サーブレット テクノロジー スタックの対応するコンテンツを参照してください。
リクエストがプロキシ (ロード バランサーなど) を通過すると、ホスト、ポート、スキームが変更される場合があります。このため、顧客の観点からは、正しいホスト、ポート、スキームへのリンクを作成することが困難になります。
RFC 7239 は、プロキシが元のリクエストに関する情報を提供するために使用できる Forwarded HTTP ヘッダーを定義しています。他にも、X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-Proto、X-Forwarded-SSL、X-Forwarded-Prefix などの非標準ヘッダーがあります。
ForwardedHeaderTransformer は、転送されたヘッダーに基づいてリクエストのホスト、ポート、スキームを変更し、これらのヘッダーを削除するコンポーネントです。forwardedHeaderTransformer という名前の Bean として宣言すると、検出されて使用されます。
アプリケーションはこれらのヘッダーがプロキシによって追加されたのか、それとも悪意のあるクライアントによって追加されたのかを知る方法がないため、転送されたヘッダーにはセキュリティ上の考慮事項があります。このため、信頼境界にあるプロキシは、外部からの信頼できない転送トラフィックをドロップするように構成する必要があります。ForwardedHeaderTransformer をremoveOnly=true で構成することもできます。この場合、これらのヘッダーは削除されますが、使用されません。
バージョン 5.1 では、ForwardedHeaderFilter が非推奨になり、ForwardedHeaderTransformer に置き換えられたため、交換を作成する前に転送ヘッダーを事前に処理できるようになりました。フィルターが構成されている場合、そのフィルターはフィルターのリストから削除され、ForwardedHeaderTransformer が使用されます。 |
4. フィルター
サーブレット テクノロジー スタックの対応するコンテンツを参照してください。
WebHandler API では、WebFilter を使用して、ターゲット WebHandler のフィルターやその他の処理チェーンの前後にインターセプト ロジックを適用できます。WebFlux 構成を使用する場合、WebFilter の登録は、それを Spring Bean として宣言し、(オプションで) Bean 宣言で @Order を使用するか Ordered を実装することで優先順位を表現するだけで簡単です。
1、コルス
サーブレット テクノロジー スタックの対応するコンテンツを参照してください。
Spring WebFlux は、コントローラーのアノテーションを通じて CORS 構成に対するきめ細かいサポートを提供します。ただし、Spring Security で使用する場合は、組み込みの CorsFilter に依存することをお勧めします。これは、Spring Security のフィルター チェーンの前に注文する必要があります。
詳細については、「CORS」および「CORS WebFilter」セクションを参照してください。
5. 例外
サーブレット テクノロジー スタックの対応するコンテンツを参照してください。
WebHandler API では、WebExceptionHandler を使用して、一連の WebFilter インスタンスおよびターゲット WebHandler からの例外を処理できます。WebFlux 構成を使用する場合、WebExceptionHandler の登録は、それを Spring Bean として宣言し、(オプションで) Bean 宣言で @Order を使用するか Ordered を実装することで優先順位を表現するだけで簡単です。
次の表では、利用可能な WebExceptionHandler 実装について説明します。
例外ハンドラー |
説明する |
応答ステータス例外ハンドラ |
ResponseStatusException タイプの例外の処理を提供し、例外の HTTP ステータス コードに対する応答を設定します。 |
WebFluxResponseStatusExceptionHandler |
ResponseStatusExceptionHandler の拡張機能。例外の @ResponseStatus アノテーション付き HTTP ステータス コードも決定できます。 このハンドラーは WebFlux 構成で宣言されます。 |
6. コーデック
サーブレット テクノロジー スタックの対応するコンテンツを参照してください。
spring-web モジュールと spring-core モジュールは、ノンブロッキング I/O および Reactive Streams バックプレッシャーを通じて、高レベル オブジェクトのバイト コンテンツのシリアル化および逆シリアル化のサポートを提供します。このサポートについては以下で説明します。
- エンコーダとデコーダは、HTTP とは独立してコンテンツをエンコードおよびデコードするための低レベルの契約です。
- HttpMessageReader と https://docs.spring.io/spring-framework/docs/6.0.8-SNAPSHOT/javadoc-api/org/springframework/http/codec/HttpMessageWriter.html[HttpMessageWriter] は、コントラクトのエンコードとデコードに使用されます。
- Encoder は EncoderHttpMessageWriter でラップして Web アプリケーションでの使用に適したものにすることができ、Decoder は DecoderHttpMessageReader でラップすることができます。
- DataBuffer は、さまざまなバイト バッファ表現 (Netty ByteBuf、java.nio.ByteBuffer など) を抽象化し、すべてのコーデックの作業オブジェクトです。このトピックの詳細については、「Spring Core」セクションの「データ バッファーとコーデック」を参照してください。
spring-core 模块提供 byte[]、ByteBuffer、DataBuffer、Resource 和 String 编码器和解码器的实现。spring-web 模块提供了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器,以及针对表单数据、multipart 内容、server-sent event 和其他的web专用HTTP消息读写器实现。
ClientCodecConfigurer and ServerCodecConfigurer are typically used to configure and customize the codecs to use in an application. See the section on configuring HTTP消息编解码器.
ClientCodecConfigurer 和 ServerCodecConfigurer 通常被用来配置和定制应用中使用的编解码器。参见配置 HTTP消息编解码器 的章节。
1、Jackson JSON
当Jackson库存在时,JSON和二进制JSON( Smile)都被支持。
Jackson2Decoder 的工作原理如下:
- Jackson的异步、非阻塞解析器被用来将字节块流聚集到 TokenBuffer 中,每个字节块代表一个JSON对象。
- 每个 TokenBuffer 被传递给 Jackson 的 ObjectMapper,以创建一个更高层次的对象。
- 当解码到一个单值 publisher(例如 Mono)时,有一个 TokenBuffer。
- 当解码到一个多值 publisher(如 Flux)时,每个 TokenBuffer 在收到足够的字节时就被传递给 ObjectMapper,以形成一个完整的对象。输入的内容可以是一个JSON数组,或任何 以行为单位的JSON 格式,如NDJSON,JSON行,或JSON文本序列。
Jackson2Encoder 的工作原理如下:
- 对于一个单一的值 publisher(例如 Mono),只需通过 ObjectMapper 将其序列化。
- 对于一个有 application/json 的多值 publisher,默认情况下用 Flux#collectToList() 来收集值,然后将得到的集合序列化。
- 对于具有流媒体类型(如 application/x-ndjson 或 application/stream+x-jackson-smile)的多值 publisher,使用 以行为单位的JSON 格式对每个值进行编码、写入和刷出。其他流媒体类型可以在 encoder 上注册。
- 对于SSE来说,Jackson2Encoder 在每个事件中被调用,output被刷出,以确保无延迟的交付。
默认情况下,Jackson2Encoder 和 Jackson2Decoder 都不支持 String 类型的元素。相反,默认的假设是一个字符串或一个字符串序列代表序列化的JSON内容,由 CharSequenceEncoder 来渲染。如果你需要的是从 Flux<String> 渲染一个JSON数组,使用 Flux#collectToList() 并编码一个 Mono<List<String>。 |
2、Form Data
FormHttpMessageReader 和 FormHttpMessageWriter 支持对 application/x-www-form-urlencoded 内容进行解码和编码。
在服务器端,表单内容经常需要从多个地方访问,ServerWebExchange 提供了一个专门的 getFormData() 方法,它通过 FormHttpMessageReader 解析内容,然后缓存结果以便重复访问。见 WebHandler API 部分的 表单(Form)数据 。
一旦使用 getFormData(),就不能再从请求体中读取原始的内容。由于这个原因,应用程序应该始终通过 ServerWebExchange 来访问缓存的表单数据,而不是从原始请求体中读取。
3、Multipart
MultipartHttpMessageReader 和 MultipartHttpMessageWriter 支持对 "multipart/form-data"、"multipart/mixed" 和 "multipart/related" 内容进行解码和编码。反过来, MultipartHttpMessageReader 委托给另一个 HttpMessageReader 来进行实际的解析到 Flux<Part>,然后简单地将这些 part 收集到一个 MultiValueMap 中。默认情况下,使用 DefaultPartHttpMessageReader,但这可以通过 ServerCodecConfigurer 改变。关于 DefaultPartHttpMessageReader 的更多信息,请参阅 DefaultPartHttpMessageReader 的javadoc。
在服务器端,如果 multipart 表单内容可能需要从多个地方访问,ServerWebExchange 提供了一个专门的 getMultipartData() 方法,该方法通过 MultipartHttpMessageReader 解析内容,然后缓存结果以便重复访问。参见 WebHandler API 部分的 Multipart Data。
一旦使用了 getMultipartData(),就不能再从请求体中读取原始的内容。由于这个原因,应用程序必须坚持使用 getMultipartData() 来重复、类似 map 的访问 part,或者依靠 SynchronossPartHttpMessageReader 来一次性访问 Flux<Part>。
4、边界(Limits)
缓存部分或全部 input stream 的 Decoder 和 HttpMessageReader 实现可以被配置为在内存中缓冲的最大字节数的限制。在某些情况下,缓冲的发生是因为输入被聚合并表示为一个单一的对象—例如,一个带有 @RequestBody byte[] 的 controller 方法,x-www-form-urlencoded 数据,等等。缓冲也可能发生在流媒体中,当分割输入流时—例如,限定的文本,JSON对象的流,等等。对于这些流的情况,限制适用于与流中一个对象相关的字节数。
为了配置缓冲区的大小,你可以检查一个给定的 Decoder 或 HttpMessageReader 是否暴露了一个 maxInMemorySize 属性,如果是的话,Javadoc 会有关于默认值的细节。在服务器端, ServerCodecConfigurer 提供了一个设置所有编解码器的单一位置,参见 HTTP消息编解码器。在客户端,所有编解码器的限制可以在 WebClient.Builder 中改变。
对于 Multipart 解析,maxInMemorySize 属性限制了非文件部分(part)的大小。对于文件 part,它决定了该 part 被写入磁盘的阈值。对于写入磁盘的文件 part,有一个额外的 maxDiskUsagePerPart 属性来限制每个 part 的磁盘空间量。还有一个 maxParts 属性,用于限制 multipart 请求中的总 part 数量。要在 WebFlux 中配置这三个属性,你需要向 ServerCodecConfigurer 提供一个预先配置的 MultipartHttpMessageReader 实例。
5、流(Stream)
参见 Servlet 技术栈中的相应内容
当流式HTTP响应(例如,text/event-stream,application/x-ndjson)时,定期发送数据是很重要的,以便可靠地尽早检测到一个断开连接的客户端,而不是更晚。这样的发送可以是一个仅有 comment 的、空的SSE事件或任何其他 "无操作" 的数据,这将有效地作为一个心跳。
6、DataBuffer
DataBuffer 是 WebFlux 中字节缓冲区的代表。本参考文献的Spring Core部分在 Data Buffer 和 Codec 部分有更多的介绍。需要理解的关键点是,在一些服务器上,如Netty,字节缓冲区是池化和引用计数的,在消耗时必须释放以避免内存泄漏。
WebFlux应用程序一般不需要关注这些问题,除非它们直接消费或生产数据缓冲区(data buffer),而不是依靠编解码器来转换为更高级别的对象,或者它们选择创建自定义编解码器。对于这种情况,请查看 Data Buffer 和 Codec 中的信息,特别是 使用 DataBuffer 的部分。
七、日志
参见 Servlet 技术栈中的相应内容
Spring WebFlux中的 DEBUG 级别日志被设计成紧凑、简约和人性化的。它专注于高价值的信息,这些信息可以反复使用,而其他信息只有在调试某个特定问题时才会有用。
TRACE 级别的日志通常遵循与 DEBUG 相同的原则(例如也不应该是火线),但可以用于调试任何问题。此外,一些日志信息在 TRACE 与 DEBUG 下可能显示不同的细节。
好的日志来自于使用日志的经验。如果你发现任何不符合既定目标的地方,请让我们知道。
1、日志 ID
在WebFlux中,一个请求可以在多个线程上运行,线程ID对于关联属于特定请求的日志消息没有用。这就是为什么WebFlux的日志消息默认以特定请求的ID为前缀。
在服务器端,日志ID存储在 ServerWebExchange 属性中( LOG_ID_ATTRIBUTE),而基于该ID的完全格式化的前缀可以从 ServerWebExchange#getLogPrefix() 获得。在 WebClient 端,日志ID存储在 ClientRequest attribute 中( LOG_ID_ATTRIBUTE),而完全格式化的前缀可以从 ClientRequest#logPrefix() 中获得。
2、敏感数据
参见 Servlet 技术栈中的相应内容
DEBUG 和 TRACE 日志可以记录敏感信息。这就是为什么表单参数和 header 在默认情况下是被屏蔽的,你必须明确地完全启用它们的日志。
下面的例子显示了如何对服务器端的请求进行处理:
Java
@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().enableLoggingRequestDetails(true);
}
}
下面的例子显示了如何对客户端的请求进行处理:
Java
Consumer<ClientCodecConfigurer> consumer = configurer ->
configurer.defaultCodecs().enableLoggingRequestDetails(true);
WebClient webClient = WebClient.builder()
.exchangeStrategies(strategies -> strategies.codecs(consumer))
.build();
3、Appender
SLF4J 和 Log4J 2 等日志库提供了避免阻塞的异步 logger。虽然这些都有自己的缺点,比如可能会丢弃无法排队记录的消息,但它们是目前在响应式、非阻塞式应用中使用的最佳可用选项。
4、自定义编解码器
应用程序可以注册自定义编解码器,以支持额外的媒体类型,或默认编解码器不支持的特定行为。
开发者表达的一些配置选项在默认的编解码器上被强制执行。自定义编解码器可能希望得到一个与这些偏好相一致的机会,比如 强制执行缓冲限制 或 记录敏感数据。
下面的例子显示了如何对客户端的请求进行处理:
Java
WebClient webClient = WebClient.builder()
.codecs(configurer -> {
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();