パラメータ付きのSpring WebClientリクエスト

1. 概要

多くのフレームワークとプロジェクトが*リアクティブプログラミングと非同期リクエスト処理*を導入しています。 その結果、https://www.baeldung.com/spring-5 [Spring 5]は、https:/の一部としてリアクティブ_https://www.baeldung.com/spring-5-webclient [WebClient] _実装を導入しました。 /www.baeldung.com/spring-webflux[WebFlux]フレームワーク。
このチュートリアルでは、_WebClient_ *を使用してREST APIエンドポイントを*反応的に使用する方法を説明します。

2. REST APIエンドポイント

まず、サンプルのlink:/rest-api-spring-guide[REST API]を*次の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}
    カテゴリー別製品

    そのため、いくつかの異なるURIを定義しました。 すぐに、_WebClient_を使用して各タイプのURIを作成および送信する方法を理解します。
    タグおよびカテゴリごとに製品を取得するためのURIには、クエリパラメータとして配列が含まれていることに注意してください。 ただし、構文は異なります。 *配列をURIでどのように表現するかについての厳密な定義はないため*。 主に、これはサーバー側の実装に依存します。 したがって、両方のケースをカバーします。

3. WebClient Setup

最初に、_WebClient_のインスタンスを作成する必要があります。 この記事では、有効なURIが要求されていることを確認する必要がある限り、https://www.baeldung.com/mockito-series [mocked object]を使用します。
クライアントと関連するモックオブジェクトを定義しましょう:
this.exchangeFunction = mock(ExchangeFunction.class);
ClientResponse mockResponse = mock(ClientResponse.class);
when(this.exchangeFunction.exchange(this.argumentCaptor.capture())).thenReturn(Mono.just(mockResponse));
this.webClient = WebClient
  .builder()
  .baseUrl("https://example.com/api")
  .exchangeFunction(exchangeFunction)
  .build();
さらに、クライアントからのすべてのリクエストの先頭に追加されるベースURLを渡しました。
最後に、特定のURIが基になる_ExchangeFunction_インスタンスに渡されたことを確認するために、次のヘルパーメソッドを使用しましょう。
private void verifyCalledUrl(String relativeUrl) {
    ClientRequest request = this.argumentCaptor.getValue();
    Assert.assertEquals(String.format("%s%s", BASE_URL, relativeUrl), request.url().toString());
    Mockito.verify(this.exchangeFunction).exchange(request);
    verifyNoMoreInteractions(this.exchangeFunction);
}
_WebClientBuilder_クラスには、_UriBuilder_インスタンスを引数として提供する_uri()_メソッドがあります。 通常、API呼び出しは通常、次の方法で行われます。
this.webClient.get()
  .uri(uriBuilder -> uriBuilder
    //... building a URI
    .build())
  .retrieve();
このガイドでは、_UriBuilder_を広範囲に使用してURIを構築します。 他の方法を使用してURIを構築し、生成されたURIをStringとして渡すことができることに注意してください。

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

*パスコンポーネントは、スラッシュ(/)*で区切られた一連のパスセグメントで構成されます。 最初に、URIに変数セグメント_ / products_がない場合の簡単なケースから始めましょう。
this.webClient.get()
  .uri("/products")
  .retrieve();
verifyCalledUrl("/products");
その場合、引数として_String_を渡すだけです。
次に、_ / products / \ {id} _エンドポイントを取得して、対応するURIを作成します。
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}")
    .build(2))
  .retrieve();
verifyCalledUrl("/products/2");
上記のコードから、実際のセグメント値が_build()_メソッドに渡されることがわかります。 +これで、同様の方法で、_ / products / \ {id} / attributes / \ {attributeId} _エンドポイントに複数のパスセグメントを持つURIを作成できます。
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}/attributes/{attributeId}")
    .build(2, 13))
  .retrieve();
verifyCalledUrl("/products/2/attributes/13");
URIには、必要な数のパスセグメントを含めることができます。 もちろん、最終的なURIの長さが制限を超えていない場合。 最後に、_build()_メソッドに渡される実際のセグメント値の正しい順序を維持することを忘れないでください。

5. URIクエリパラメータ

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

5.1. 単一値パラメーター

単一値パラメーターから始めて、_ / products /?name = \ {name}&deliveryDate = \ {deliveryDate}&color = \ {color} _エンドポイントを取得しましょう。 クエリパラメータを設定するには、_UriBuilder_インターフェイスの_queryParam()_メソッドを呼び出します。
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
ここでは、3つのクエリパラメーターを追加し、実際の値をすぐに割り当てました。 さらに、正確な値の代わりにプレースホルダーを残すこともできます。
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "{title}")
    .queryParam("color", "{authorId}")
    .queryParam("deliveryDate", "{date}")
    .build("AndroidPhone", "black", "13/04/2019"))
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019");
特に、チェーン内でビルダーオブジェクトをさらに渡すときに役立ちます。 1つの重要な*上記の2つのコードスニペットの違い*に注意してください。
予想されるURIに注意すると、それらは*異なる方法でエンコードされていることがわかります。 特に、最後の例では、スラッシュ文字*(/)*がエスケープされました。 一般的に、https://www.ietf.org/rfc/rfc3986.txt [RFC3986]では、クエリにスラッシュをエンコードする必要はありません。
ただし、一部のサーバー側アプリケーションでは、このような変換が必要になる場合があります。 したがって、このガイドの後半でこの動作を変更する方法について説明します。

5.2. 配列パラメーター

同様に、値の配列を渡す必要がある場合があります。 それでも、クエリ文字列で配列を渡すための厳密な規則はありません。 そのため、*クエリ文字列の配列表現はプロジェクトごとに異なり、通常は基礎となるフレームワーク*に依存します。 最も広く使用されている形式について説明します。
_ / products /?tag [] = \ {tag1}&tag [] = \ {tag2} _エンドポイントから始めましょう。
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("tag[]", "Snapdragon", "NFC")
    .build())
  .retrieve();
verifyCalledUrl("/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC");
ご覧のとおり、最終的なURIには複数のタグパラメータとそれに続くエンコードされた角括弧が含まれています。 _queryParam()_メソッドは変数引数を値として受け入れるため、メソッドを何度も呼び出す必要はありません。
または、角かっこを省略して、同じキーを持つ複数のクエリパラメーターを渡すことができますが、値は異なります– _ / products /?category = \ {category1}&category = \ {category2} _:
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", "Phones", "Tablets")
    .build())
  .retrieve();
verifyCalledUrl("/products/?category=Phones&category=Tablets");
結論として、配列をエンコードするために広く使用されているもう1つの方法は、コンマ区切りの値を渡すことです。 前の例をコンマ区切りの値に変換しましょう。
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", String.join(",", "Phones", "Tablets"))
    .build())
  .retrieve();
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);
this.webClient = WebClient
  .builder()
  .uriBuilderFactory(factory)
  .baseUrl(BASE_URL)
  .exchangeFunction(exchangeFunction)
  .build();
その結果、次のアサーションが成功します。
this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");
そしてもちろん、URIの作成を手動で処理する完全にカスタムの_UriBuilderFactory_実装を提供できます。

7. 結論

このチュートリアルでは、_WebClient_と_DefaultUriBuilder._を使用してさまざまな種類のURIを作成する方法を見てきました。
途中で、クエリパラメータのさまざまなタイプと形式を取り上げました。 そして、URLビルダーのデフォルトのエンコードモードを変更することで終わりました。
いつものように、記事のコードスニペットはすべてhttps://github.com/eugenp/tutorials/tree/master/spring-5-webflux[GitHubリポジトリ上]から入手できます。