【WebClient】コピペで使える実装集

WebClientを使ったアプリケーションを本番運用するために意識しておきたい設定とその設定方法をまとめてみました。

自分の現場で意識していることが中心になりますが、コピペでそのまま本番アプリケーションに使うようなコードを残しておきます!

依存ライブラリ

implementation 'org.springframework.boot:spring-boot-starter-webflux:2.5.0'


共通の設定

ある程度共通で実装するであろう設定をまとめておきます

    @Bean
    public WebClient webClient(MetricsWebClientCustomizer metricsWebClientCustomizer) {
        var connectionProvider = ConnectionProvider.builder("sample")
                .maxConnections(100) // コネクションプール数
                .maxIdleTime(Duration.ofSeconds(59))  // keep aliveタイムアウト
                .metrics(true) // コネクション数のメトリクスを有効化
                .build();

        var httpClient = HttpClient.create(connectionProvider)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1_000) // connection timeout
                .responseTimeout(Duration.ofMillis(500)); // read timeout

        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .baseUrl("http://localhost:8080")
                .apply(metricsWebClientCustomizer::customize)  // http clientのメトリクスを収集
                .build();
    }

URLへの変数の埋め込み

可変な部分はクエリパラメータも含めてuriの第二引数以降で指定しないとダメ。
可変なURLをそのまま指定するとメトリクスがURLごと保持され、OOMEの原因にもなり得ます。

webClient.get()
    .uri("/sample/{variable}", "hoge")  // /sample/hoge
    .retrieve()
    .bodyToMono(String.class);

keepaliveの無効化

keep-aliveを無効化するにはHttpClient生成時にkeepAlivefalseを設定します。
※デフォルトは有効

var httpClient = HttpClient.create(connectionProvider)
    ...
    .keepAlive(false)
    ....

Proxyヘッダ

以下、認証があるproxyに対してWebClientを使って接続するときの実装方法です。

    @Bean
    public WebClient proxyWebClient() {
        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(HttpClient.create()
                        .proxy(typeSpec -> {
                            typeSpec.type(ProxyProvider.Proxy.HTTP)
                                    .host("localhost")
                                    .port(8080)
                                    // basic認証は対応している
                                    //.username("username")
                                    //.password(s -> "password")
                                    .httpHeaders(headers -> {
                                        // ここで認証用のヘッダを乗せる
                                        headers.add("uuid", UUID.randomUUID().toString());
                                    });
                        })
                ))
                .build();
    }

単体テスト

WebClientの単体テストにはOkHttpを使うとよいと思います。

以下のようなWebClientに依存するコンポーネントをサンプルにテストコードを書いてみます。

@RequiredArgsConstructor
@Component
public class SampleApiClient {
    private final WebClient webClient;

    public Mono<String> sample() {
        return webClient.get()
                .uri("/sample")
                .retrieve()
                .bodyToMono(SampleResponse.class)
                .map(res -> res.getMessage());
    }

    @Data
    public static class SampleResponse {
        private String message;
    }
}

OkHttpを使用するために、依存を追加します。

dependencies {
  ...
  // 依存を追加
  testImplementation 'com.squareup.okhttp3:okhttp'
	testImplementation 'com.squareup.okhttp3:mockwebserver'
}

Mock用のHTTPサーバを起動してテストします。

public class SampleApiClientTest {
    private static MockWebServer mockServer;
    private ObjectMapper objectMapper = new ObjectMapper();

    private SampleApiClient sampleApiClient;

    @BeforeEach
    void beforeEach() {
        var webClient = WebClient.builder()
                .baseUrl("http://localhost:" + mockServer.getPort())
                .build();

        sampleApiClient = new SampleApiClient(webClient);
    }

    @BeforeAll
    static void startMock() throws IOException {
        mockServer = new MockWebServer();
        mockServer.start();
    }

    @AfterAll
    static void shutdownMock() throws IOException {
        mockServer.shutdown();
    }

    @Test
    @SneakyThrows
    void test() {
        // Mockサーバのレスポンスを設定する
        var responseBody = new SampleApiClient.SampleResponse();
        responseBody.setMessage("Hello, world.");
        var mockedResponse = new MockResponse()
                .setBody(objectMapper.writeValueAsString(responseBody))
                .addHeader("Content-Type", "application/json");
        mockServer.enqueue(mockedResponse);

        // リクエスト送信
        var actual = sampleApiClient.sample();

        StepVerifier.create(actual)
                .expectNext("Hello, world.")
                .verifyComplete();
    }
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です