1. 概要

多くのフレームワークとプロジェクトがリアクティブプログラミングと非同期リクエスト処理を導入しています。 その結果、 Spring 5 は、WebFluxフレームワークの一部としてリアクティブなWebClient実装を導入しました。

このチュートリアルでは、WebClientを使用してRESTAPIエンドポイントをリアクティブに使用する方法を説明します。

2. RESTAPIエンドポイント

まず、サンプル RESTAPI次のGETエンドポイントで定義しましょう。

  • / products –すべての製品を入手
  • / products / {id} –IDで製品を取得
  • / products / {id} / attributes / {attributeId} –IDで商品属性を取得
  • / products /?name = {name}&deliveryDate = {deliveryDate}&color = {color} –製品を探す
  • / products /?tag [] = {tag1}&tag [] = {tag2} –タグで商品を入手する
  • / products /?category = {category1}&category = {category2} –カテゴリ別に製品を入手する

したがって、いくつかの異なるURIを定義しました。 すぐに、WebClientを使用して各タイプのURIを作成して送信する方法を理解します。

タグおよびカテゴリ別に製品を取得するためのURIには、クエリパラメータとして配列が含まれていることに注意してください。 ただし、構文は異なります。 なので、URIで配列を表現する方法の厳密な定義はありません。 主に、これはサーバー側の実装に依存します。 したがって、両方のケースについて説明します。

3. WebClientセットアップ

まず、WebClientのインスタンスを作成する必要があります。 この記事では、有効なURIが要求されていることを確認する必要がある限り、モックオブジェクトを使用します。

クライアントと関連するモックオブジェクトを定義しましょう。

exchangeFunction = mock(ExchangeFunction.class);
ClientResponse mockResponse = mock(ClientResponse.class);
when(mockResponse.bodyToMono(String.class))
  .thenReturn(Mono.just("test"));

when(exchangeFunction.exchange(argumentCaptor.capture()))
  .thenReturn(Mono.just(mockResponse));

webClient = WebClient
  .builder()
  .baseUrl("https://example.com/api")
  .exchangeFunction(exchangeFunction)
  .build();

さらに、クライアントからのすべてのリクエストの前に追加されるベースURLを渡しました。

最後に、特定のURIが基になる ExchangeFunction インスタンスに渡されたことを確認するために、次のヘルパーメソッドを使用してみましょう。

private void verifyCalledUrl(String relativeUrl) {
    ClientRequest request = argumentCaptor.getValue();
    assertEquals(String.format("%s%s", BASE_URL, relativeUrl), request.url().toString());
    
    verify(this.exchangeFunction).exchange(request);
    verifyNoMoreInteractions(this.exchangeFunction);
}

WebClientBuilder クラスには、 UriBuilderインスタンスを引数として提供するuri()メソッドがあります。 通常、API呼び出しは次の方法で行われます。

webClient.get()
  .uri(uriBuilder -> uriBuilder
    //... building a URI
    .build())
  .retrieve()
  .bodyToMono(String.class)
  .block();

このガイドでは、 UriBuilder を幅広く使用して、URIを作成します。 他の方法を使用してURIを作成し、生成されたURIを文字列として渡すことができることは注目に値します。

4. URIパスコンポーネント

パスコンポーネントは、スラッシュ(/)で区切られた一連のパスセグメントで構成されます。 まず、URIに変数セグメント /productsがない単純なケースから始めましょう。

webClient.get()
  .uri("/products")
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products");

その場合、引数としてStringを渡すことができます。

次に、 / products / {id} エンドポイントを取得して、対応するURIを作成しましょう。

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}")
    .build(2))
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/2");

上記のコードから、実際のセグメント値が build()メソッドに渡されていることがわかります。 同様の方法で、 / products / {id} / attributes /{attributeId}エンドポイントの複数のパスセグメントを持つURIを作成できます。

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}/attributes/{attributeId}")
    .build(2, 13))
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/2/attributes/13");

URIには、必要な数のパスセグメントを含めることができます。 もちろん、最終的なURIの長さが制限を超えていない場合。 最後に、 build()メソッドに渡される実際のセグメント値の正しい順序を維持することを忘れないでください。

5. URIクエリパラメータ

通常、クエリパラメータは、 title =Baeldungのような単純なキーと値のペアです。 そのようなURIを構築する方法を見てみましょう。

5.1. 単一値パラメーター

単一値パラメーターから始めて、 / products /?name = {name}&deliveryDate = {deliveryDate}&color = {color} 終点。 クエリパラメータを設定するには、 UriBuilderインターフェイスのqueryParam()メソッドを呼び出します。

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");

ここでは、3つのクエリパラメータを追加し、すぐに実際の値を割り当てました。 さらに、正確な値の代わりにプレースホルダーを残すこともできます。

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "{title}")
    .queryParam("color", "{authorId}")
    .queryParam("deliveryDate", "{date}")
    .build("AndroidPhone", "black", "13/04/2019"))
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019");

特に、これはビルダーオブジェクトをチェーンのさらに先に渡すときに役立つ場合があります。 上記の2つのコードスニペットの重要な違いに注意してください。

予想されるURIに注目すると、それらが異なる方法でエンコードされていることがわかります。 特に、最後の例ではスラッシュ文字(/)をエスケープしました。 一般的に、 RFC3986 は、クエリでスラッシュをエンコードする必要はありません。

ただし、一部のサーバー側アプリケーションでは、このような変換が必要になる場合があります。 したがって、このガイドの後半でこの動作を変更する方法を説明します。

5.2. 配列パラメータ

同様に、値の配列を渡す必要がある場合があります。 それでも、クエリ文字列で配列を渡すための厳密な規則はありません。 したがって、クエリ文字列の配列表現はプロジェクトごとに異なり、通常は基盤となるフレームワークに依存します。 最も広く使用されている形式について説明します。

から始めましょう / products /?tag [] = {tag1}&tag [] = {tag2} 終点:

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("tag[]", "Snapdragon", "NFC")
    .build())
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC");

ご覧のとおり、最終的なURIには、複数のタグパラメータと、それに続くエンコードされた角かっこが含まれています。 queryParam()メソッドは変数引数を値として受け入れるため、メソッドを何度も呼び出す必要はありません。

または、 角かっこを省略し、同じキーで複数のクエリパラメータを渡すだけです 、ただし値が異なる– / products /?category = {category1}&category = {category2}

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", "Phones", "Tablets")
    .build())
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/?category=Phones&category=Tablets");

結論として、配列をエンコードするために広く使用されているもう1つの方法は、コンマ区切りの値を渡すことです。 前の例をコンマ区切りの値に変換してみましょう。

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", String.join(",", "Phones", "Tablets"))
    .build())
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/?category=Phones,Tablets");

したがって、 Stringクラスのjoin()メソッドを使用して、コンマ区切りの文字列を作成しています。 もちろん、アプリケーションで期待される他の区切り文字を使用できます。

6. エンコーディングモード

先ほどURLエンコードについて説明したことを思い出してください。

デフォルトの動作が要件に合わない場合は、変更できます。 WebClient インスタンスを構築する際に、UriBuilderFactory実装を提供する必要があります。 この場合、DefaultUriBuilderFactoryクラスを使用します。 エンコーディングを設定するには、 setEncodingMode()メソッドを呼び出します。 次のモードを使用できます。

  • TEMPLATE_AND_VALUES :URIテンプレートを事前にエンコードし、展開時にURI変数を厳密にエンコードします
  • VALUES_ONLY :URIテンプレートをエンコードしませんが、テンプレートに展開した後、URI変数を厳密にエンコードします
  • URI_COMPONENTS :URI変数を消費した後にURIコンポーネント値をエンコードします
  • NONE :エンコードは適用されません

デフォルト値はTEMPLATE_AND_VALUESです。 モードをURI_COMPONENTSに設定しましょう。

DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
webClient = WebClient
  .builder()
  .uriBuilderFactory(factory)
  .baseUrl(BASE_URL)
  .exchangeFunction(exchangeFunction)
  .build();

その結果、次のアサーションが成功します。

webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve()
  .bodyToMono(String.class)
  .block();

verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");

もちろん、完全にカスタムの UriBuilderFactory 実装を提供して、URIの作成を手動で処理することもできます。

7. 結論

このチュートリアルでは、WebClientDefaultUriBuilder。を使用してさまざまなタイプのURIを作成する方法を説明しました。

その過程で、クエリパラメータのさまざまなタイプと形式について説明してきました。 そして、URLビルダーのデフォルトのエンコードモードを変更してまとめました。

この記事のすべてのコードスニペットは、いつものように、GitHubリポジトリ利用できます。