1. 概要

Spring 5は、完全に新しいフレームワーク– Spring WebFlux を追加しました。これは、Webアプリケーションでのリアクティブプログラミングをサポートします。 HTTPリクエストを実行するには、 WebClient インターフェースを使用できます。これは、 ReactorProjectに基づく機能的なAPIを提供します。

このチュートリアルでは、WebClientタイムアウト設定に焦点を当てます。 アプリケーション全体でグローバルに、またリクエストに固有の、さまざまな方法、さまざまなタイムアウトを適切に設定する方法について説明します。

2. WebClientおよびHTTPクライアント

先に進む前に、簡単に要約してみましょう。 Spring WebFluxには、独自のクライアント WebClientクラスが含まれており、リアクティブな方法でHTTPリクエストを実行します。 WebClient も、正しく機能するにはHTTPクライアントライブラリが必要です。 Spring は、それらの一部に組み込みのサポートを提供しますが、デフォルトでは ReactorNettyが使用されます。

タイムアウトを含むほとんどの構成は、これらのクライアントを使用して実行できます。

3. HTTPクライアントを介したタイムアウトの構成

前述したように、アプリケーションでさまざまな WebClient タイムアウトを設定する最も簡単な方法は、基盤となるHTTPクライアントを使用してグローバルに設定することです。これは、これを行う最も効率的な方法でもあります。

NettyはSpringWebFluxのデフォルトのクライアントライブラリであるため、 ReactorNettyHttpClientクラスを使用して例を説明します。

3.1. 応答タイムアウト

応答タイムアウトは、要求の送信後にが応答を受信するのを待機する時間です。 responseTimeout()メソッドを使用して、クライアント用に構成できます。

HttpClient client = HttpClient.create()
  .responseTimeout(Duration.ofSeconds(1)); 

この例では、タイムアウトを1秒に設定します。 Nettyは、デフォルトでは応答タイムアウトを設定しません。

その後、HttpClientをSpringWebClientに提供できます。

WebClient webClient = WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(httpClient))
  .build();

その後、 WebClient は、送信されたすべての要求に対して、基盤となるHttpClientによって提供されるすべての構成を継承します。

3.2. 接続タイムアウト

接続タイムアウトはクライアントとサーバー間の接続を確立する必要がある期間。 さまざまなチャネルオプションキーとオプション() 構成を実行する方法:

HttpClient client = HttpClient.create()
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

// create WebClient...

提供される値はミリ秒単位であるため、タイムアウトを10秒に構成しました。 Nettyは、その値をデフォルトで30秒に設定します。

さらに、接続がアイドル状態のときにTCPチェックプローブを送信するkeep-aliveオプションを構成できます。

HttpClient client = HttpClient.create()
  .option(ChannelOption.SO_KEEPALIVE, true)
  .option(EpollChannelOption.TCP_KEEPIDLE, 300)
  .option(EpollChannelOption.TCP_KEEPINTVL, 60)
  .option(EpollChannelOption.TCP_KEEPCNT, 8);

// create WebClient...

そのため、アイドル状態から5分後、60秒間隔でプローブするキープアライブチェックを有効にしました。 また、接続が切断される前のプローブの最大数を8に設定しました。

接続が特定の時間内に確立されないか、切断されると、ConnectTimeoutExceptionがスローされます。

3.3. 読み取りおよび書き込みタイムアウト

読み取りタイムアウトは、一定時間内にデータが読み取られなかった場合に発生し、は、書き込み操作が特定の時間に終了できない場合に書き込みタイムアウトが発生します。 HttpClient [X194X ]これらのタイムアウトを構成するために、追加のハンドラーを構成できます。

HttpClient client = HttpClient.create()
  .doOnConnected(conn -> conn
    .addHandler(new ReadTimeoutHandler(10, TimeUnit.SECONDS))
    .addHandler(new WriteTimeoutHandler(10)));

// create WebClient...

この状況では、 doOnConnected()メソッドを介して接続されたコールバックを構成し、追加のハンドラーを作成しました。 タイムアウトを構成するために、ReadTimeOutHandlerおよびWriteTimeOutHandlerインスタンスを追加しました。 両方とも10秒に設定しました。

これらのハンドラーのコンストラクターは、パラメーターの2つのバリアントを受け入れます。 1つ目は、 TimeUnit 仕様の数値を提供し、2つ目は指定された数値を秒に変換します。

基盤となるNettyライブラリは、エラーを処理するためにReadTimeoutExceptionクラスとWriteTimeoutExceptionクラスを提供します

3.4. SSL/TLSタイムアウト

ハンドシェイクタイムアウトは、システムが操作を停止する前にSSL接続を確立しようとする時間です。 secure()メソッドを介してSSL構成を設定できます。

HttpClient.create()
  .secure(spec -> spec.sslContext(SslContextBuilder.forClient())
    .defaultConfiguration(SslProvider.DefaultConfigurationType.TCP)
    .handshakeTimeout(Duration.ofSeconds(30))
    .closeNotifyFlushTimeout(Duration.ofSeconds(10))
    .closeNotifyReadTimeout(Duration.ofSeconds(10)));

// create WebClient...

上記のように、ハンドシェイクタイムアウトを30秒(デフォルト:10秒)に設定し、 close_notify フラッシュ(デフォルト:3秒)および読み取り(デフォルト:0秒)タイムアウトを10秒に設定します。 すべてのメソッドは、SslProvider.Builderインターフェイスによって提供されます。

SslHandshakeTimeoutExceptionは、設定されたタイムアウトが原因でハンドシェイクが失敗した場合に使用されます。

3.5. プロキシタイムアウト

HttpClientはプロキシ機能もサポートしています。 ピアへの接続確立の試行がプロキシタイムアウト内に終了しない場合、接続試行は失敗します proxy()構成中にこのタイムアウトを設定します。

HttpClient.create()
  .proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
    .host("proxy")
    .port(8080)
    .connectTimeoutMillis(30000));

// create WebClient...

connectTimeoutMillis()を使用して、デフォルト値が10の場合にタイムアウトを30秒に設定しました。

Nettyライブラリは、失敗した場合に備えて独自のProxyConnectExceptionも実装します

4. リクエストレベルのタイムアウト

前のセクションでは、HttpClientを使用してさまざまなタイムアウトをグローバルに構成しました。 ただし、グローバル設定とは関係なく、応答要求固有のタイムアウトを設定することもできます。

4.1. 応答タイムアウト– HttpClientRequestを使用

以前と同様に、応答タイムアウトを要求レベルでも構成できます。

webClient.get()
  .uri("https://baeldung.com/path")
  .httpRequest(httpRequest -> {
    HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
    reactorRequest.responseTimeout(Duration.ofSeconds(2));
  });

上記の場合、WebClientの httpRequest()メソッドを使用して、基になるNettyライブラリからネイティブHttpClientRequestにアクセスしました。 次に、これを使用してタイムアウト値を2秒に設定しました。

この種の応答タイムアウト設定は、HttpClientレベルの応答タイムアウトをオーバーライドします。 この値をnullに設定して、以前に構成された値を削除することもできます。

4.2. リアクティブタイムアウト–ReactorCoreの使用

Reactor Nettyは、ReactiveStreamsの実装としてReactorCoreを使用します。 別のタイムアウトを構成するには、Monoおよび Flux publishersが提供するtimeout()演算子を使用できます。

webClient.get()
  .uri("https://baeldung.com/path")
  .retrieve()
  .bodyToFlux(JsonNode.class)
  .timeout(Duration.ofSeconds(5));

その場合、指定された5秒以内にアイテムがに到着しない場合、TimeoutExceptionが表示されます。

Reactor Nettyで利用可能な、より具体的なタイムアウト構成オプションを使用することをお勧めします。これらは、特定の目的とユースケースに対してより詳細な制御を提供するためです。

timeout()メソッドは、リモートピアへの接続の確立から応答の受信まで、操作全体に適用されます。 HttpClient関連の設定を上書きしません。

5. 例外処理

さまざまなタイムアウト構成について学習しました。 次に、例外処理について簡単に説明します。 各タイプのタイムアウトは専用の例外を提供するため、RactiveStreamsとonErrorブロックを使用して簡単に処理できます

webClient.get()
  .uri("https://baeldung.com/path")
  .retrieve()
  .bodyToFlux(JsonNode.class)
  .timeout(Duration.ofSeconds(5))
  .onErrorMap(ReadTimeoutException.class, ex -> new HttpTimeoutException("ReadTimeout"))
  .onErrorReturn(SslHandshakeTimeoutException.class, new TextNode("SslHandshakeTimeout"))
  .doOnError(WriteTimeoutException.class, ex -> log.error("WriteTimeout"))
  ...

前述の例外を再利用し、Reactorを使用して独自の処理メソッドを作成できます。

さらに、HTTPステータスに応じてロジックを追加することもできます。

webClient.get()
  .uri("https://baeldung.com/path")
  .onStatus(HttpStatus::is4xxClientError, resp -> {
    log.error("ClientError {}", resp.statusCode());
    return Mono.error(new RuntimeException("ClientError"));
  })
  .retrieve()
  .bodyToFlux(JsonNode.class)
  ...

6. 結論

このチュートリアルでは、Nettyの例を使用して、WebClientでSpringWebFluxのタイムアウトを構成する方法を学習しました。

さまざまなタイムアウトと、それらを HttpClient レベルで正しく設定する方法、およびそれらをグローバル設定に適用する方法について簡単に説明しました。 次に、単一のリクエストを使用して、リクエスト固有のレベルで応答タイムアウトを設定しました。 最後に、発生する例外を処理するためのさまざまな方法を示しました。

この記事で言及されているすべてのコードスニペットは、GitHubにあります。