Spring Web Reactive | 2. WebClient | 2.1. 構成

WebClientを作成する最も簡単な方法は、静的ファクトリメソッドのいずれかを使用することです。

  • WebClient.create()
  • WebClient.create(String baseUrl)

追加オプションとともにWebClient.builder()を使用できます。

  • uriBuilderFactory: ベースURLとして使用するためにカスタマイズしたUriBuilderFactory
  • defaultUriVariables: URIテンプレートを展開するときに使用するデフォルト値。
  • defaultHeader: すべてのリクエストで使用されるヘッダー。
  • defaultCookie: すべてのリクエストで使用されるCookie。
  • defaultRequest: Consumerで各リクエストをカスタマイズします。
  • filter: すべてのリクエストのクライアントフィルター。
  • exchangeStrategies: HTTPメッセージreader/writerのカスタマイズ。
  • clientConnector: HTTPクライアントライブラリの設定。

たとえば、次のようになります。

Java

WebClient client = WebClient.builder()
        .codecs(configurer -> ... )
        .build();

Kotlin

val webClient = WebClient.builder()
        .codecs { configurer -> ... }
        .build()

いったん構築されると、WebClientは不変です。ただし、次のように複製して変更されたコピーを作成できます。

Java

WebClient client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build();

WebClient client2 = client1.mutate()
        .filter(filterC).filter(filterD).build();

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

Kotlin

val client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build()

val client2 = client1.mutate()
        .filter(filterC).filter(filterD).build()

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

2.1.1. MaxInMemorySize

コーデックには、アプリケーションのメモリ問題に対応するため、メモリにデータをバッファリングする制限があります。デフォルトでは256KBに設定されています。これが不足すると、次のようなエラーが発生します。

org.springframework.core.io.buffer.DataBufferLimitException : Exceeded limit on max bytes to buffer

デフォルトのコーデック制限を変更するには、次のように設定します。

Java

WebClient webClient = WebClient.builder()
        .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
        .build();

Kotlin

val webClient = WebClient.builder()
        .codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) }
        .build()

2.1.2. Reactor Netty

Reactor Nettyの設定をカスタマイズするには、事前構成済みのHttpClientを提供します。

Java

HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);

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

Kotlin

val httpClient = HttpClient.create().secure { ... }

val webClient = WebClient.builder()
    .clientConnector(ReactorClientHttpConnector(httpClient))
    .build()

リソース(Resources)

デフォルトでは、HttpClientはイベントループスレッドとコネクションプールを含むreactor.netty.http.HttpResourcesのグローバルReactor Nettyリソースを使用します。これは、イベントループの同時実行には固定された共有リソースが望ましいため、推奨されるモードです。このモードでは、プロセスが終了するまでグローバルリソースはアクティブなままです。

サーバープロセスとタイミングが合っている場合、通常は明示的に終了する必要はありません。ただし、サーバーがプロセス内で開始および停止できる場合、たとえばWARとしてデプロイされたSpring MVCアプリケーションでは、globalResources=true(デフォルト)でReactorResourceFactory型のSpring管理Beanを宣言し、Reactor Nettyがグローバルであることを確認できます。次の例に示すように、Spring ApplicationContextが閉じられるとリソースは終了します。

Java

@Bean
public ReactorResourceFactory reactorResourceFactory() {
    return new ReactorResourceFactory();
}

Kotlin

@Bean
fun reactorResourceFactory() = ReactorResourceFactory()

グローバルReactor Nettyリソースを使用しないことも選択できます。ただし、このモードでは、次の例のように、すべてのReactor Nettyクライアントおよびサーバーインスタンスが共有リソースを使用することを保証する責任があります。

Java

@Bean
public ReactorResourceFactory resourceFactory() {
    ReactorResourceFactory factory = new ReactorResourceFactory();
    factory.setUseGlobalResources(false);   // (1)
    return factory;
}

@Bean
public WebClient webClient() {

    Function<HttpClient, HttpClient> mapper = client -> {
        // Further customizations...
    };

    ClientHttpConnector connector =
            new ReactorClientHttpConnector(resourceFactory(), mapper);   // (2)

    return WebClient.builder().clientConnector(connector).build();   // (3)
}

Kotlin

@Bean
fun resourceFactory() = ReactorResourceFactory().apply {
    isUseGlobalResources = false   // (1)
}

@Bean
fun webClient(): WebClient {

    val mapper: (HttpClient) -> HttpClient = {
        // Further customizations...
    }

    val connector = ReactorClientHttpConnector(resourceFactory(), mapper)   // (2)

    return WebClient.builder().clientConnector(connector).build()   // (3)
}
  • (1) グローバルリソースから独立したリソースを作成します。
  • (2) リソースファクトリでReactorClientHttpConnectorコンストラクターを使用します。
  • (3) コネクターをWebClient.Builderへ接続します。

タイムアウト(Timeouts)

接続タイムアウトを構成するには:

Java

import io.netty.channel.ChannelOption;

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

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

Kotlin

import io.netty.channel.ChannelOption

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

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

読み取りまたは書き込みタイムアウトを構成するには:

Java

import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;

HttpClient httpClient = HttpClient.create()
        .doOnConnected(conn -> conn
                .addHandlerLast(new ReadTimeoutHandler(10))
                .addHandlerLast(new WriteTimeoutHandler(10)));

// Create WebClient...

Kotlin

import io.netty.handler.timeout.ReadTimeoutHandler
import io.netty.handler.timeout.WriteTimeoutHandler

val httpClient = HttpClient.create()
        .doOnConnected { conn -> conn
                .addHandlerLast(new ReadTimeoutHandler(10))
                .addHandlerLast(new WriteTimeoutHandler(10))
        }

// Create WebClient...

すべてのリクエストのレスポンスタイムアウトを設定するには:

Java

HttpClient httpClient = HttpClient.create()
        .responseTimeout(Duration.ofSeconds(2));

// Create WebClient...

Kotlin

val httpClient = HttpClient.create()
        .responseTimeout(Duration.ofSeconds(2));

// Create WebClient...

特定のリクエストのレスポンスタイムアウトを設定するには:

Java

WebClient.create().get()
        .uri("https://example.org/path")
        .httpRequest(httpRequest -> {
            HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
            reactorRequest.responseTimeout(Duration.ofSeconds(2));
        })
        .retrieve()
        .bodyToMono(String.class);

Kotlin

WebClient.create().get()
        .uri("https://example.org/path")
        .httpRequest { httpRequest: ClientHttpRequest ->
            val reactorRequest = httpRequest.getNativeRequest<HttpClientRequest>()
            reactorRequest.responseTimeout(Duration.ofSeconds(2))
        }
        .retrieve()
        .bodyToMono(String::class.java)

2.1.3. Jetty

次の例は、Jetty HttpClient設定をカスタマイズする方法を示しています。

Java

HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);

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

Kotlin

val httpClient = HttpClient()
httpClient.cookieStore = ...

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

デフォルトでは、HttpClientは独自のリソース(ExecutorSchedulerByteBufferPool)を作成し、プロセスが終了するかstop()が呼び出されるまでアクティブなままです。

次の例のように、JettyResourceFactoryをSpring管理Beanとして宣言することで、Jettyクライアントおよびサーバーの複数インスタンス間でリソースを共有し、Spring ApplicationContextが閉じられたときにリソースを確実に終了できます。

Java

@Bean
public JettyResourceFactory resourceFactory() {
    return new JettyResourceFactory();
}

@Bean
public WebClient webClient() {

    HttpClient httpClient = new HttpClient();
    // Further customizations...

    ClientHttpConnector connector =
            new JettyClientHttpConnector(httpClient, resourceFactory());   (1)

    return WebClient.builder().clientConnector(connector).build();   (2)
}

Kotlin

@Bean
fun resourceFactory() = JettyResourceFactory()

@Bean
fun webClient(): WebClient {

    val httpClient = HttpClient()
    // Further customizations...

    val connector = JettyClientHttpConnector(httpClient, resourceFactory())   (1)

    return WebClient.builder().clientConnector(connector).build()   (2)
}
  • (1) リソースファクトリでJettyClientHttpConnectorコンストラクターを使用します。
  • (2) コネクターをWebClient.Builderへ接続します。

2.1.4. HttpComponents

次の例は、Apache HttpComponents HttpClient設定をカスタマイズする方法を示しています。

Java

HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(...);
CloseableHttpAsyncClient client = clientBuilder.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);

WebClient webClient = WebClient.builder().clientConnector(connector).build();

Kotlin

val client = HttpAsyncClients.custom().apply {
    setDefaultRequestConfig(...)
}.build()
val connector = HttpComponentsClientHttpConnector(client)
val webClient = WebClient.builder().clientConnector(connector).build()