RESTクライアント(HTTPクライアント) ================================================================================ .. only:: html .. contents:: 目次 :depth: 3 :local: | .. _RestClientOverview: Overview -------------------------------------------------------------------------------- | 本節では、RESTful Web Service(REST API)を呼び出す際に利用するRESTクライアントの実装について説明を行う。 | | 本ガイドラインでは、主に外部システム連携の手段としてのRESTを扱う。 | 外部システム連携では多くの場合、同期的な情報のやり取りを行うことが想定されるため、本ガイドラインでも同期的な通信を中心に取り扱う。 | 非同期通信については簡単な実装例を参考情報としてAppendix(\ :ref:`AsyncDescription`\ )に記載しているので必要に応じて参照されたい。 | .. _RestClientTypeAndOverview: RESTクライアント実装の種類 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | Spring Frameworkでは、RESTful Web Service(REST API)を呼び出すためのクライアント実装として、\ ``RestTemplate``\ , \ ``RestClient``\ , \ ``WebClient``\ , \ ``HTTP Service Clients``\ の4種類の提供を行っている。 | 本項では、同期通信で利用するクライアント実装の機能や特徴の違いを説明する。 .. _RestClientTypeAndOverviewRestTemplate: \ ``RestTemplate``\ を利用したクライアント実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestTemplate``\ は、Spring Framework 3.0から導入されているRESTクライアント実装であり、REST APIへの同期通信をサポートしたクライアント実装である。 | 非同期通信に関しては、Spring Framework 6.0にて\ ``AsyncRestTemplate``\ が削除されたことにより、\ ``WebClient``\ を利用した実装が推奨されており、現在は同期通信のみをサポートしている。 | なお、同期通信においてもSpring Framework 7.0にて今後廃止とする宣言がされており、\ **Spring Framework 7.1にて正式に非推奨とし、Spring Framework 8.0にて削除を行う予定**\ とされている。 | そして、\ ``RestTemplate``\ の移行先としては\ ``RestClient``\ が紹介されている。 | \ ``RestTemplate``\ が非推奨となった経緯は、\ :url_spring_io:`The state of HTTP clients in Spring `\ を参照されたい。 | |framework_name| においても、現在利用中のユーザを考慮し、\ ``RestTemplate``\ 関する記載を残しているが、\ **今後、RESTクライアントの実装を行う際はRestClientの利用を推奨する。**\ | また、技術的負債になることを防ぐため、\ **既存のRestTemplateを用いた実装についても、RestClientへの移行を推奨する。**\ | \ ``RestTemplate``\ から\ ``RestClient``\ への移行については、\ :url_spring_reference:`Migrating from RestTemplate to RestClient `\ にて詳しく記載されているので参照されたい。 | 以下に、\ ``RestTemplate``\ を利用したクライアントアプリケーションが、どのようにREST APIを公開しているサーバサイドアプリケーションにアクセスし、RESTによる通信を実現するかを示す。 | \ ``RestTemplate``\ 内で利用されるクラスの詳細な説明は、「\ :ref:`RestClientComponentsOverview`\ 」にてまとめて説明を行っているのでこちらを参照されたい。 .. figure:: ./images_RestClient/RestTemplateOverview.png :alt: Overview of RestTemplate processing :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.20\linewidth}|p{0.60\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 20 60 * - 項番 - 処理 - 説明 * - | (1) - | アプリケーションからの呼び出し - | \ ``RestTemplate``\ のメソッドを実行し、REST API(Web API)の呼び出し依頼を行う。 * - | (2) - | URL生成 - | \ ``UriBuilderFactory``\ の実装クラスが、\ ``UriBuilder``\ の生成を行う。 | 生成した\ ``UriBuilder``\ を使用して、URLのパラメータを置換し送信先のURLの組み立てを行う。 | なお、\ ``UriBuilderFactory``\ の実装クラスを指定していない場合は、デフォルト実装クラスである\ ``DefaultUriBuilderFactory``\ が使用される。 * - | (3) - | リクエスト生成・初期化 - | \ ``ClientHttpRequestFactory``\ の実装クラスが\ ``ClientHttpRequest``\ の生成を行う。 | なお、\ ``ClientHttpRequestFactory``\ の実装クラスを指定していない場合は、デフォルト実装クラスである\ ``SimpleClientHttpRequestFactory``\ が使用される。 | \ ``ClientHttpRequest``\ 生成後、\ ``ClientHttpRequestInitializer``\ を設定している場合、\ ``ClientHttpRequestInitializer``\ の実装クラスを使用して\ ``ClientHttpRequest``\ (リクエスト)の初期化処理を行う。 | | 「\ :ref:`RestClientOverviewClientHttpRequestFactory`\ 」にて詳しく説明を行うが、 |framework_name| では、\ ``ClientHttpRequestFactory``\ の実装クラスは\ ``HttpComponentsClientHttpRequestFactory``\ の使用を推奨とする。 * - | (4) - | リクエスト送信前処理 - | \ ``HttpMessageConverter``\ の実装クラスを使用して、Javaオブジェクトをリクエストボディに設定する電文(JSON等)に変換する。 | なお、実際に利用される\ ``HttpMessageConverter``\ の実装クラスは、Javaオブジェクトの型とメディアタイプをもとに\ ``RestTemplate``\ が決定する。 * - | (5) - | リクエスト送信 - | \ ``ClientHttpRequestInterceptor``\ を設定している場合、\ ``ClientHttpRequestInterceptor``\ の実装クラスを使用してリクエスト送信前後の共通処理を行う。 | \ ``ClientHttpRequest``\ に電文(JSON等)をリクエストボディに設定して、REST API(Web API)にHTTP経由でリクエストを送信する。 | REST API(Web API)からレスポンスを受信後、\ ``ClientHttpRequest``\ が\ ``ClientHttpResponse``\ を生成し、受信したレスポンスデータを設定する。 * - | (6) - | エラーハンドリング - | \ ``ResponseErrorHandler``\ の実装クラスにて、\ ``ClientHttpResponse``\ に設定されているHTTPステータスコードをもとに、エラー判定及びエラー処理を行う。 | なお、\ ``ResponseErrorHandler``\ の実装クラスを指定していない場合は、デフォルト実装クラスである\ ``DefaultResponseErrorHandler``\ が使用される。 * - | (7) - | レスポンス取得後処理 - | レスポンス取得後の後処理として、\ ``ResponseExtractor``\ の実装クラスが実行される。 | なお、\ ``ResponseExtractor``\ の実装クラスを指定していない場合は、デフォルト実装クラスである\ ``HttpMessageConverterExtractor``\ が使用される。 | 後処理時に、\ ``HttpMessageConverter``\ の実装クラスを使用して、レスポンスボディに設定されている電文(JSON等)をJavaオブジェクトに変換する。 | リクエスト送信前処理と同様で\ ``HttpMessageConverter``\ は、Javaオブジェクトの型とメディアタイプをもとに決定される。 * - | (8) - | アプリケーションへ実行結果返却 - | REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。 | .. _RestClientTypeAndOverviewRestClient: \ ``RestClient``\ を利用したクライアント実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestClient``\ は、Spring Framework 6.1から導入されているRESTクライアント実装であり、\ ``RestTemplate``\ の後継として登場した同期通信をサポートしたRESTクライアント実装である。 | 特徴としては流れるようなインターフェース(fluent interface)を用いたAPIを提供しており、設定やリクエストの手続きをメソッドチェーンで記載できる。 | | \ ``RestTemplate``\ で説明した通りに、同期通信に関しては\ ``RestClient``\ を使うことが推奨されている。 | また、移行性についての考慮もされており、\ ``RestTemplate``\ で提供されている機能は\ ``RestClient``\ でも同等に利用できる。 | \ ``RestTemplate``\ から\ ``RestClient``\ への移行については、\ :url_spring_reference:`Migrating from RestTemplate to RestClient `\ にて詳しく記載されているので参照されたい。 | 以下に、\ ``RestClient``\ を利用したクライアントアプリケーションが、どのようにREST APIを公開しているサーバサイドアプリケーションにアクセスし、RESTによる通信を実現するかを示す。 | \ ``RestClient``\ 内で利用されるクラスの詳細な説明は、「\ :ref:`RestClientComponentsOverview`\ 」にてまとめて説明を行っているのでこちらを参照されたい。 .. figure:: ./images_RestClient/RestClientOverview.png :alt: Overview of RestClient processing :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.20\linewidth}|p{0.60\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 20 60 * - 項番 - 処理 - 説明 * - | (1) - | アプリケーションからの呼び出し - | \ ``RestClient``\ のメソッドを実行し、REST API(Web API)の呼び出し依頼を行う。 | \ ``RestClient``\ は、\ ``RequestBodyUriSpec``\ と\ ``ResponseSpec``\ を内部的に保持し、これらを使って流れるようなインターフェース(fluent interface)を実現している。 | メソッドチェーンで呼び出しを行う際にリクエストの設定(エンドポイント、HTTPメソッド、ヘッダ、ボディ等)を行うエントリーポイントとして\ ``RequestBodyUriSpec``\ を提供し、レスポンスの設定(受信時のデータ型、例外ハンドラ等)を行うエントリーポイントとして\ ``ResponseSpec``\ を提供している。 | なお、\ ``RestClient``\ はインターフェースとなっており、実装クラスとしては\ ``DefaultRestClient``\ が使用される。 * - | (2) - | URL生成 - | \ ``UriBuilderFactory``\ の実装クラスが、\ ``UriBuilder``\ の生成を行う。 | 生成した\ ``UriBuilder``\ を使用して、URLのパラメータを置換し送信先のURLの組み立てを行う。 | なお、\ ``UriBuilderFactory``\ の実装クラスを指定していない場合は、デフォルト実装クラスである\ ``DefaultUriBuilderFactory``\ が使用される。 * - | (3) - | リクエスト生成・初期化 - | \ ``ClientHttpRequestFactory``\ の実装クラスが\ ``ClientHttpRequest``\ の生成を行う。 | なお、\ ``ClientHttpRequestFactory``\ の実装クラスを指定していない場合は、使用するHttpClientライブラリによって実装クラスが決定される。 | \ ``ClientHttpRequest``\ 生成後、\ ``ClientHttpRequestInitializer``\ を設定している場合、\ ``ClientHttpRequestInitializer``\ の実装クラスを使用して\ ``ClientHttpRequest``\ (リクエスト)の初期化処理を行う。 | | 「\ :ref:`RestClientOverviewClientHttpRequestFactory`\ 」にて詳しく説明を行うが、 |framework_name| では、\ ``ClientHttpRequestFactory``\ の実装クラスは\ ``HttpComponentsClientHttpRequestFactory``\ の使用を推奨とする。 * - | (4) - | リクエスト送信前処理 - | \ ``HttpMessageConverter``\ の実装クラスを使用して、Javaオブジェクトをリクエストボディに設定する電文(JSON等)に変換する。 | なお、選択される\ ``HttpMessageConverter``\ は、Javaオブジェクトの型とメディアタイプをもとに決定される。 * - | (5) - | リクエスト送信 - | \ ``ClientHttpRequestInterceptor``\ を設定している場合、\ ``ClientHttpRequestInterceptor``\ の実装クラスを使用してリクエスト送信前後の共通処理を行う。 | \ ``ClientHttpRequest``\ に電文(JSON等)をリクエストボディに設定して、REST API(Web API)にHTTP経由でリクエストを送信する。 | REST API(Web API)からレスポンスを受信後、\ ``ClientHttpRequest``\ が\ ``ClientHttpResponse``\ を生成し、受信したレスポンスデータを設定する。 * - | (6) - | レスポンス取得後処理 / エラーハンドリング - | レスポンス取得後の後処理として、\ ``ExchangeFunction``\ が実行される。 | \ ``ExchangeFunction``\ により\ ``StatusHandler``\ が実行され、エラー判定及びエラー処理を行う。 | エラー判定処理後に、\ ``HttpMessageConverter``\ の実装クラスを使用して、レスポンスボディに設定されている電文(JSON等)をJavaオブジェクトに変換する。 | なお、リクエスト送信前処理と同様で\ ``HttpMessageConverter``\ は、Javaオブジェクトの型とメディアタイプをもとに決定される。 * - | (7) - | アプリケーションへ実行結果返却 - | REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。 | .. Spring7.0対応:HTTP Interface⇒HTTP Service Clientsの表記 .. _RestClientTypeAndOverviewHttpServiceClients: \ ``HTTP Service Clients``\ を利用したクライアント実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``HTTP Service Clients``\ は、Spring Framework 6.0から導入された新しいRESTクライアントの実装であり、インターフェースとアノテーションを活用したシンプルな構成が特徴である。 | 従来の\ ``RestTemplate``\ 、\ ``RestClient``\ 、\ ``WebClient``\ とは異なり、インターフェースとアノテーションを定義するだけで、REST API(Web API)へのアクセスを簡潔に記述できるように設計されている。 | この記載方法はSpringの\ ``@RestController``\ によるコントローラ実装と類似しており、直感的で理解しやすい構成である。 | また、インターフェースにREST API(Web API)へのアクセス方法や制約(エンドポイント、HTTPメソッド、ヘッダ等)を定義できるため、利用者はロジックと設定を分離することができ、従来のRESTクライアント実装と比べてコードの見通しが良い。 | \ ``HTTP Service Clients``\ 自体は、\ ``RestTemplate``\ 、\ ``RestClient``\ 、\ ``WebClient``\ の設定を流用できるため、既存の設定を活かしながら追加で導入するといった移行の容易さも特徴である。 | 業務ロジックのコードの見通しを良くしたい場合や、REST API(Web API)のアクセス方法をインターフェースに集約して管理したい場合は、\ ``HTTP Service Clients``\ を利用すると便利である。 | 以下に、\ ``HTTP Service Clients``\ を利用したクライアントアプリケーションが、どのようにREST APIを公開しているサーバサイドアプリケーションにアクセスし、RESTによる通信を実現するかを示す。 | \ ``HTTP Service Clients``\ 内で利用されるクラスの詳細な説明は、「\ :ref:`RestClientComponentsOverview`\ 」にてまとめて説明を行っているのでこちらを参照されたい。 .. figure:: ./images_RestClient/HTTPServiceClientsOverview.png :alt: Overview of RestClient processing :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.20\linewidth}|p{0.60\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 20 60 * - 項番 - 処理 - 説明 * - | (1) - | アプリケーションからの呼び出し - | 開発者が作成した\ ``@HttpExchange``\ アノテーションを付与したインターフェースのメソッドを実行し、REST API(Web API)の呼び出し依頼を行う。 | なお、インターフェースの実装はSpring Frameworkが生成した動的プロキシが使用される。 | | \ ``@HttpExchange``\ アノテーションは、\ ``HTTP Service Clients``\ であることを表現するためのアノテーションであり、エンドポイント、HTTPメソッド等の情報を定義できる。 * - | (2) - | Adapterの実行 - | 動的プロキシから\ ``HttpServiceProxyFactory``\ が呼び出され、実行したインターフェースのメソッドの情報をもとに\ ``HttpServiceMethod``\ を取得する。 | その後、\ ``HttpServiceProxyFactory``\ から\ ``HttpServiceMethod``\ が実行され、リクエスト情報の取得を行う。 | リクエスト情報取得後、\ ``HttpServiceMethod``\ が\ ``HttpExchangeAdapter``\ を実行する。 | なお、\ ``HttpExchangeAdapter``\ の実装クラスは、\ ``RestTemplate``\ 、\ ``RestClient``\ 、\ ``WebClient``\ のどの設定を利用するかによって決定される。 * - | (3) - | RESTクライアントの実行 - | \ ``HttpExchangeAdapter``\ から、各RESTクライアント実装を実行する。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 20 80 * - 各RESTクライアント実装 - 説明 * - | \ ``RestTemplate``\ の場合 - | \ ``RestTemplateAdapter``\ から\ ``RequestEntity``\ を呼び出し、\ ``RestTemplate``\ に引き渡すリクエスト情報を設定する。 | その後、\ ``RestTemplateAdapter``\ から\ ``RestTemplate``\ を実行し、以降の処理は\ ``RestTemplate``\ と同様の処理が実行される。 * - | \ ``RestClient``\ の場合 - | \ ``RestClientAdapter``\ から\ ``RestClient``\ の\ ``RequestBodyUriSpec``\ を呼び出し、\ ``RestClient``\ に引き渡すリクエスト情報を設定する。 | その後、\ ``RestClientAdapter``\ から\ ``RestClient``\ を実行し、以降の処理は\ ``RestClient``\ と同様の処理が実行される。 * - | \ ``WebClient``\ の場合 - | \ ``WebClientAdapter``\ から\ ``WebClient``\ の\ ``RequestBodyUriSpec``\ を呼び出し、\ ``WebClient``\ に引き渡すリクエスト情報を設定する。 | その後、\ ``WebClientAdapter``\ から\ ``WebClient``\ を実行し、以降の処理は\ ``WebClient``\ と同様の処理が実行される。 * - | (4) - | アプリケーションへ実行結果返却 - | REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。 | .. _RestClientComponentsOverview: RESTクライアント実装の構成要素と利用上のポイント ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 上述のように、RESTクライアントの各実装方式は、おおむね同様の処理パターンを持っており、その構成要素についても共通のものが多い。 | 本項では、各RESTクライアント実装で共通な処理内容と、そこで利用される構成要素の概要を示し、利用上、有益と思われるいくつかの内容について記載する。 .. _RestClientOverviewUriBuilderFactory: URLの生成(\ ``UriBuilderFactory``\ ) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``org.springframework.web.util.UriBuilderFactory``\ は、URIテンプレート(プレースホルダ付きのURLの元となる文字列)と、URIテンプレート変数の値(プレースホルダを埋めるためのパラメータ)を受け取り、リクエスト送信先のURLを生成するインターフェースである。 具体的には以下のようなURL文字列を生成する。 * URIテンプレート: \ ``http://example.com/api/users/{userId}``\ * URIテンプレート変数の値: \ ``12345``\ * 生成されるURL: \ ``http://example.com/api/users/12345``\ | \ ``UriBuilderFactory``\ は、\ ``UriBuilder``\ を生成するためのファクトリであり、実際のURL文字列の生成は\ ``UriBuilder``\ で行われる。 | デフォルトでは、\ ``org.springframework.web.util.DefaultUriBuilderFactory``\ から、\ ``DefaultUriBuilder``\ が作られ、URL生成に利用される。 | | URIテンプレート変数の値をURIテンプレートに埋め込む際に、URIテンプレート変数の値に対してフォーマット変換を行いたい場合などは、\ ``UriBuilderFactory``\ を拡張することが考えられる。 | この場合、変換処理が実装された\ ``UriBuilder``\ の実装クラスと、その\ ``UriBuilder``\ の実装クラスが使われるようにした\ ``UriBuilderFactory``\ の実装クラスを作成し、この\ ``UriBuilderFactory``\ の実装クラスが使われるように、Bean定義を行うことで実現できる。 | URLの生成では、\ ``RestClient``\ 、\ ``RestTemplate``\ の両者で \ ``UriBuilderFactory``\ が利用されているため、同様の方法で拡張が可能である。 | \ ``UriBuilderFactory``\ の具体的なカスタマイズの例は、「\ :ref:`RestClientHowToExtendUriBuilderFactory`\ 」を参照されたい。 | .. _RestClientOverviewClientHttpRequestFactory: リクエストの生成(\ ``ClientHttpRequestFactory``\ ) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestClient``\ と\ ``RestTemplate``\ は、サーバとの通信処理を以下の3つのインタフェースの実装クラスに委譲することで実現している。 * \ ``org.springframework.http.client.ClientHttpRequestFactory``\ * \ ``org.springframework.http.client.ClientHttpRequest``\ * \ ``org.springframework.http.client.ClientHttpResponse``\ | \ ``ClientHttpRequestFactory``\ は、サーバとの通信処理を行う\ ``ClientHttpRequest``\ インタフェースの実装クラスの生成を行う。 | その後、\ ``ClientHttpRequest``\ がサーバと通信を行い、通信結果を保持する\ ``ClientHttpResponse``\ インタフェースの実装クラスを生成し、通信結果を設定する。 | | この3つのインタフェースのうち、\ ``ClientHttpRequestFactory``\ については、Spring Framework側でいくつかの実装が用意されており、細かな点で動作が異なることがあるため、利用者側で何を利用するか意識しておく必要がある。 | Spring Frameworkが提供している主な\ ``ClientHttpRequestFactory``\ の実装クラスは以下の通りである。 .. tabularcolumns:: |p{0.05\linewidth}|p{0.25\linewidth}|p{0.70\linewidth}| .. list-table:: \ **Spring Frameworkが提供している主なClientHttpRequestFactoryの実装クラス**\ :header-rows: 1 :widths: 5 25 70 * - 項番 - クラス名 - 説明 * - | (1) - | \ ``org.springframework.http.client.``\ | \ ``HttpComponentsClientHttpRequestFactory``\ - | \ :url_http_client_home:`Apache HttpComponents HttpClient `\ のAPIを使用して同期型の通信処理を行うための実装クラス。(HttpClient 5.2以上が必要) * - | (2) - | \ ``org.springframework.http.client.``\ | \ ``JettyClientHttpRequestFactory``\ - | \ :url_jetty:`Jetty `\ のAPIを使用して同期型の通信処理を行うための実装クラス。 * - | (3) - | \ ``org.springframework.http.client.``\ | \ ``ReactorClientHttpRequestFactory``\ - | \ :url_reactor_reference:`Reactor Netty `\ のAPIを使用して同期型の通信処理を行うための実装クラス。 * - | (4) - | \ ``org.springframework.http.client.``\ | \ ``JdkClientHttpRequestFactory``\ - | Java SE標準の\ :url_javase17:`HttpClient `\ のAPIを使用して同期型の通信処理を行うための実装クラス。 | HttpClientの実装としては、JDK 11にて追加された\ ``java.net.http.HttpClient``\ を利用しており、\ ``SimpleClientHttpRequestFactory``\ よりも高機能な実装クラスである。 * - | (5) - | \ ``org.springframework.http.client.``\ | \ ``SimpleClientHttpRequestFactory``\ - | Java SE標準の\ :url_javase17:`HttpURLConnection `\ のAPIを使用して同期型の通信処理を行うための実装クラス。 | HttpClientの実装としては、JDK初期から存在する\ ``java.net.HttpURLConnection``\ を使用しており、単純な通信設定のみが行える。 | なお、\ ``RestTemplate``\ 利用時にデフォルトで設定される実装クラスである。 | \ **使用するClientHttpRequestFactoryの実装クラスについて**\ | \ ``ClientHttpRequestFactory``\ のデフォルト実装クラスは、\ ``RestClient``\ と\ ``RestTemplate``\ で異なる。 | \ ``RestClient``\ を利用する場合は、クラスパス上に存在するHttpClientライブラリ検出し、優先順位に基づいて\ ``ClientHttpRequestFactory``\ の実装が自動的に決定される。 | \ ``RestTemplate``\ を利用する場合は、\ ``SimpleClientHttpRequestFactory``\ が固定で選択される。 | | 前述の通り、\ ``RestClient``\ ではクラスローダーに読み込まれたライブラリをもとに自動的に決定されるため、競合するHttpClientライブラリが依存関係に存在する場合は、意図しない実装クラスが選択される可能性がある。 | また、\ ``RestTemplate``\ においても、デフォルトで設定される\ ``SimpleClientHttpRequestFactory``\ は、コネクションプールやプロキシ認証等の機能を持たず、他の\ ``ClientHttpRequestFactory``\ の実装と比較すると機能が限定されている。 | したがって、 |framework_name| では、\ **ClientHttpRequestFactoryの実装クラスとしては、高機能な通信設定が行えるHttpComponentsClientHttpRequestFactoryの利用を推奨する。**\ | \ **また、ClientHttpRequestFactoryの実装を固定するために、ClientHttpRequestFactoryの実装クラスを明示的に指定する設定を推奨する。**\ | 以下が、推奨設定のコード例である。 .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .build(); } .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restTemplate") public RestTemplate restTemplate() { return new RestTemplate(new HttpComponentsClientHttpRequestFactory()); } .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml \ **ClientHttpRequestFactoryの設定について**\ | \ ``ClientHttpRequestFactory``\ では、通信設定のカスタマイズが可能となっており、以下のような設定が可能である。 | それぞれの設定の詳細については、以下のリンクを参照されたい。 * タイムアウトの設定:「\ :ref:`RestClientHowToUseTimeoutSettings`\ 」 * SSLの設定:「\ :ref:`RestClientHowToUseHttps`\ 」 * プロキシの設定:「\ :ref:`RestClientProxySettings`\ 」 .. _RestClientOverviewClientHttpRequestFactoryContentLength: .. note:: \ **Content-Lengthヘッダについて**\ .. Spring7.0対応:bufferContentを使ってバッファリングを有効にすることが可能となった。 Spring Framework 6.1よりメモリ使用量を減らすために、ほとんどの\ ``ClientHttpRequestFactory``\ 実装クラスはサーバへ送信する前にリクエストボディをバッファリングしなくなった。これにより、Content-Lengthヘッダが設定されなくなったため、Content-Lengthヘッダを設定する必要がある場合は\ :url_spring_javadoc:`BufferingClientHttpRequestFactory `\ を利用しバッファリングする必要がある。 - \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .bufferContent((uri , method) -> true) // (1) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestClient.Builder.bufferContent(BiPredicate)``\ を使用することで、内部的に\ ``BufferingClientHttpRequestFactory``\ が生成され、バッファリングが有効になる。 | 引数の\ ``BiPredicate``\ には、URIとHTTPメソッドが引き渡されるため、それらを利用してバッファリングする条件を記載することができる。 | 上記の例では、すべてのリクエストに対してバッファリングを有効にしている。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setBufferingPredicate((uri, httpMethod) -> true); // (1) return restTemplate; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestTemplate.setBufferingPredicate(BiPredicate)``\ を使用することで、内部的に\ ``BufferingClientHttpRequestFactory``\ が生成され、バッファリングが有効になる。 | 引数の\ ``BiPredicate``\ には、URIとHTTPメソッドが引き渡されるため、それらを利用してバッファリングする条件を記載することができる。 | 上記の例では、すべてのリクエストに対してバッファリングを有効にしている。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | XML Configで定義を記載する場合は、\ ``BufferingClientHttpRequestFactory``\ を明示的に\ ``RestTemplate``\ に設定する必要がある。 | Java Configと異なりバッファリングする条件を記載できないため、すべてのリクエストに対してバッファリングが有効になる。 | .. _RestClientOverviewClientHttpRequestInitializer: リクエスト初期化(\ ``ClientHttpRequestInitializer``\ ) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``org.springframework.http.client.ClientHttpRequestInitializer``\ は、Httpリクエスト送信前に\ ``ClientHttpRequest``\ をカスタマイズするためのインタフェースである。 | Httpリクエスト送信前のカスタマイズポイントには、 \ ``ClientHttpRequestInitializer``\ の他に、 \ ``ClientHttpRequestInterceptor``\ も存在するが、それぞれ用途が異なる。両者の違いについては「\ :ref:`RestClientOverviewInterceptorInitializer`\ 」を参照されたい。 | \ ``ClientHttpRequestInitializer``\ はユーザが\ ``RestClient``\ や\ ``RestTemplate``\ に設定した場合のみ動作し、\ ``ClientHttpRequestFactory``\ による\ ``ClientHttpRequest``\ の生成後、\ ``ClientHttpRequestInitializer``\ に実装したユーザ定義の初期化処理が実行される。 | したがって、\ ``ClientHttpRequestInitializer``\ はデフォルト実装はなく、拡張ポイントとして提供されているインターフェースである。 | また、\ ``ClientHttpRequestInitializer``\ は\ ``@FunctionalInterface``\ で提供されているため、\ ``RestClient``\ ではラムダ式を使用して簡潔に記述することが可能である。 | | \ ``ClientHttpRequestInitializer``\ の利用が適しているケースには、リクエストヘッダにカスタムヘッダを一括で設定したい場合などがある。 | \ ``RestClient``\ を利用する場合、このようなケースには\ ``defaultHeader``\ メソッドも利用可能である。 | 一方で、\ ``RestTemplate``\ 利用する場合は\ ``RestClient``\ のような代替手段が無いため \ ``ClientHttpRequestInitializer``\ の利用が適切である。 | リクエストヘッダの一括設定の方法については、「\ :ref:`RestClientHowToUseRequestHeader`\ 」を参照されたい。 | .. _RestClientOverviewHttpMessageConverter: メッセージ変換(\ ``HttpMessageConverter``\ ) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``org.springframework.http.converter.HttpMessageConverter``\ は、アプリケーションで扱うJavaオブジェクトとサーバと通信するための電文(JSON等)を相互に変換するためのインタフェースである。 | Spring Frameworkは主要なメディアタイプ向けの\ ``HttpMessageConverter``\ 実装を提供しており、\ ``RestClient``\ および\ ``RestTemplate``\ が使用する\ ``HttpMessageConverter``\ は、これらのクライアント生成時に登録され、リクエスト/レスポンスのボディ変換に利用される。 | 自動で登録される\ ``HttpMessageConverter``\ には、標準で登録される\ ``HttpMessageConverter``\ と、依存ライブラリの有無により登録される\ ``HttpMessageConverter``\ が存在する。 | 加えて、独自で変換処理を実装した\ ``HttpMessageConverter``\ を必要に応じて追加登録することも可能である。 | なお、\ ``HttpMessageConverter``\ はクライアント側とサーバ側のそれぞれで登録されるため、本節で説明している\ ``HttpMessageConverter``\ はクライアント側で利用されるものであることに留意されたい。 | これらの\ ``HttpMessageConverter``\ は、標準の構成では以下の順で登録される。 #. 独自で変換処理を実装した\ ``HttpMessageConverter``\ #. 標準で登録される\ ``HttpMessageConverter``\ #. 依存ライブラリの有無により登録される\ ``HttpMessageConverter``\ | メッセージ変換時には、登録されている\ ``HttpMessageConverter``\ の順序に従って、Javaオブジェクトの型とメディアタイプに適用可能な\ ``HttpMessageConverter``\ が選択される。 | | 各\ ``HttpMessageConverter``\ が対応するJavaオブジェクトの型とメディアタイプについては、以下のJavaDocを参照されたい。 \ **標準で登録されるHttpMessageConverter**\ * \ :url_spring_javadoc:`ByteArrayHttpMessageConverter `\ * \ :url_spring_javadoc:`StringHttpMessageConverter `\ * \ :url_spring_javadoc:`ResourceHttpMessageConverter `\ * \ :url_spring_javadoc:`ResourceRegionHttpMessageConverter `\ * \ :url_spring_javadoc:`AllEncompassingFormHttpMessageConverter `\ \ **依存ライブラリの有無により登録されるHttpMessageConverter**\ * \ :url_spring_javadoc:`JacksonJsonHttpMessageConverter `\ * \ :url_spring_javadoc:`Jaxb2RootElementHttpMessageConverter `\ 上記以外の\ ``HttpMessageConverter``\ に関しては、\ :url_spring_javadoc:`HttpMessageConverter `\ の実装クラスの一覧を参照されたい。 | \ **ガイドラインで使用するHttpMessageConverterについて**\ | 本節で説明する実装は、クライアント、サーバの双方で、Spring Frameworkが提供する\ ``HttpMessageConverter``\ を同等の構成で登録していることを前提とする。 | また、JSON形式の通信を前提としており、\ ``org.springframework.http.converter.json.JacksonJsonHttpMessageConverter``\ による変換処理を例に説明を行う。 | 各Restクライアント実装の送受信で使用するJavaBeanのフィールド名は、JSONのプロパティ名と一致していることを前提とし、特別なマッピング等は行っていない。 | .. _RestClientOverviewClientHttpRequestInterceptor: リクエスト前後処理(\ ``ClientHttpRequestInterceptor``\ ) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``org.springframework.http.client.ClientHttpRequestInterceptor``\ は、サーバとの通信の前後に共通的な処理を実装するためのインタフェースである。 | 類似した拡張ポイントとして\ ``ClientHttpRequestInitializer``\ もあるため、違いについては「\ :ref:`RestClientOverviewInterceptorInitializer`\ 」を参照されたい。 | \ ``ClientHttpRequestInterceptor``\ を使用すると、サーバとの通信時のリクエストやレスポンスのログを出力するといった共通的な処理を\ ``RestTemplate``\ や、\ ``RestClient``\ に適用することができる。 | \ ``ClientHttpRequestInterceptor``\ を使用した共通処理の実装方法については、「\ :ref:`RestClientHowToExtendClientHttpRequestInterceptor`\ 」を参照されたい。 | \ **ClientHttpRequestInterceptorの動作仕様**\ | \ ``ClientHttpRequestInterceptor``\ は複数適用することができ、\ ``RestTemplate``\ や、\ ``RestClient``\ に登録した順番でチェーン実行される。 | これはサーブレットフィルタの動作によく似ており、最後に実行されるチェーン先として\ ``ClientHttpRequest``\ によるHTTP通信処理が登録されている。 | \ ``ClientHttpRequestInterceptor``\ を複数適用した場合の実装例は、「\ :ref:`RestClientHowToExtendMultiClientHttpRequestInterceptor`\ 」を参照されたい。 .. 現状コード例がないため、コメントアウト。戻すかどうかはわからないが、HowToUseかExtendsにコード例ができたら文章や導線は検討する。 | 例えば、ある条件に一致した際にサーバとの通信処理をキャンセルしたいという要件があった場合は、チェーン先を呼びださず例外をスローすることで実現できる。 | この仕組みを活用すると、以下の共通処理を実装することも可能である。 * サーバとの通信の閉塞 * 通信処理のリトライ | .. warning:: \ **ClientHttpRequestInterceptor使用時の注意点**\ \ ``ClientHttpRequestInterceptor``\ を利用すると、リクエストボディが引数として渡される関係でボディ部がメモリ上に全て展開されてしまう。したがって、リクエストボディに大量のデータを含む場合はメモリ使用量が増大する可能性があるので留意して使用する必要がある。 | .. _RestClientOverviewInterceptorInitializer: \ ``ClientHttpRequestInterceptor``\ と\ ``ClientHttpRequestInitializer``\ の使い分けについて """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``ClientHttpRequestInterceptor``\ と\ ``ClientHttpRequestInitializer``\ は、どちらもリクエスト送信時の共通処理を記述するためのインタフェースであるが、以下のように目的と使用方法が異なるため、使い分けが重要である。 .. tabularcolumns:: |p{0.05\linewidth}|p{0.25\linewidth}|p{0.70\linewidth}| .. list-table:: :header-rows: 1 :widths: 5 20 75 * - 項番 - 種類 - 説明 * - | (1) - | \ ``ClientHttpRequestInitializer``\ - | \ ``ClientHttpRequestFactory``\ が\ ``ClientHttpRequest``\ を生成した後に実行される。 | リクエストボディを読み込まず、\ ``ClientHttpRequest``\ のみに対してカスタマイズを行える拡張ポイントであるため、リクエストヘッダに対して特定のヘッダを追加する等の\ ``ClientHttpRequest``\ に限定された共通処理を記述するのに適している。 * - | (2) - | \ ``ClientHttpRequestInterceptor``\ - | 実行タイミングとしては、\ ``ClientHttpRequest``\ がサーバとの通信を行う前後に実行される。 | したがって、リクエスト前後の共通処理であるログ出力等を追加したい場合に利用するのに適している。 .. | したがって、リクエスト前後の共通処理であるログ出力やリトライ処理等を追加したい場合に利用するのに適している。 | 「\ :ref:`RestClientOverviewClientHttpRequestInterceptor`\ 」でも述べたとおり、\ ``ClientHttpRequestInterceptor``\ を利用するとリクエストボディがメモリ上に展開されるため、メモリ使用量が増大し、パフォーマンスに影響を与える可能性がある。 | したがって、リクエストヘッダの設定のみを行いたい場合等は、責務の問題もあるがリソース的にも\ ``ClientHttpRequestInitializer``\ を利用する方が適切である。 | .. _RestClientOverviewResponseErrorHandler: エラーハンドリング(\ ``StatusHandler``\ 、\ ``ResponseErrorHandler``\ ) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Spring Frameworkには、RESTクライアントによるサーバ通信時のエラーに対応するためのエラーハンドラとして\ :url_spring_javadoc:`StatusHandler `\ と、\ :url_spring_javadoc:`ResponseErrorHandler `\ が存在しており、RESTクライアントの実装ごとに利用するエラーハンドラが異なる。 | 各RESTクライアント実装では、デフォルトでHTTPステータスコードに応じたエラーハンドリングが実装されたエラーハンドラが設定されている。詳細は以下の通り。 * \ ``RestClient``\ : 利用可能なエラーハンドラの実装として\ ``StatusHandler``\ を使用した実装と、\ ``ResponseErrorHandler``\ を使用した実装の2つが存在する。デフォルトでは、\ ``StatusHandler``\ が利用される。 * \ ``RestTemplate``\ :利用可能なエラーハンドラ実装は \ ``ResponseErrorHandler``\ を使用したもののみ。デフォルトでは、\ ``DefaultResponseErrorHandler``\ が利用される。 | デフォルトで使用されるエラーハンドラー実装はHTTPステータスコードに応じて、以下のようなハンドリングを行う。 | デフォルト実装のハンドリング仕様は、\ ``StatusHandler``\ と\ ``DefaultResponseErrorHandler``\ で同様である。 .. tabularcolumns:: |p{0.05\linewidth}|p{0.10\linewidth}|p{0.35\linewidth}|p{0.50\linewidth}| .. list-table:: \ **エラーハンドラーの動作仕様**\ :header-rows: 1 :widths: 5 20 75 :class: longtable * - 項番 - HTTPステータスコード - 動作仕様 * - | (1) - | 2xx(正常系) - | エラー処理は行わない。 * - | (2) - | 4xx(クライアントエラー系) - | \ ``org.springframework.web.client.HttpClientErrorException``\ を発生させる。 * - | (3) - | 5xx(サーバエラー系) - | \ ``org.springframework.web.client.HttpServerErrorException``\ を発生させる。 | 上記の動作仕様を変更したい場合は、 \ ``StatusHandler``\ か\ ``ResponseErrorHandler``\ の実装を作成して、各RESTクライアントに設定する必要がある。 | \ ``StatusHandler``\ 、\ ``ResponseErrorHandler``\ のカスタマイズ方法と設定については、「\ :ref:`RestClientHowToUseErrorHandling`\ 」を参照されたい。 | .. _RestClientOverviewResponseExtractor: レスポンス取得後処理(\ ``ExchangeFunction``\ 、\ ``ResponseExtractor``\ ) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | Spring Frameworkでは、RESTクライアントでサーバからのレスポンスを取得したあとの、ボディの抽出や変換などの処理(レスポンス取得後処理)を実現する仕組みとして、 \ ``ExchangeFunction``\ と\ ``ResponseExtractor``\ を提供している。 | また、RESTクライアントの実装方法によって、利用されるレスポンス取得後処理の仕組みが異なる。詳細は以下の通り。 .. figure:: ./images_RestClient/ExchangeFunction.png :alt: Comparison of ExchangeFunction and ResponseExtractor :width: 100% \ **ExchangeFunction ( RestClient ) の場合**\ | \ ``org.springframework.web.client.ExchangeFunction``\ を使用して後処理を実装する。 | デフォルトで利用される実装が提供されており、\ ``org.springframework.web.client.DefaultRestClient``\ 内で定義されている\ ``ExchangeFunction``\ の関数実装が利用される。 | 動作としては、\ ``DefaultRestClient``\ がレスポンスを受信した後に実行され、後処理としてHTTPステータスのエラー判定処理、メッセージの変換処理を行う。 | \ ``RestTemplate``\ との違いは、HTTPステータスのエラー判定処理をレスポンス受信後処理(\ ``ExchangeFunction``\) で実装する必要がある点にある。 \ **ResponseExtractor ( RestTemplate ) の場合**\ | \ ``org.springframework.web.client.ResponseExtractor``\ を使用して後処理を実装する。 | デフォルトで利用される実装が提供されており、 \ ``org.springframework.web.client.HttpMessageConverterExtractor``\ が利用される。 | 動作としては、\ ``RestTemplate``\ がレスポンスを受信し、HTTPステータスのエラー判定処理を行った後に実行され、後処理としてメッセージの変換処理を行う。 受信したレスポンスを操作する必要がある場合などは \ ``ExchangeFunction``\ や\ ``ResponseExtractor``\ をカスタマイズする。実際の方法は、「\ :ref:`RestClientHowToUseFileDownload`\ 」で説明しているので、必要に応じてこちらも参照されたい。 | .. _RestClientHowToUse: How to use(同期通信) -------------------------------------------------------------------------------- | 本節では、\ ``RestClient``\ と\ ``RestTemplate``\ を使用した、同期通信を行うRESTクライアントの実装方法について説明する。 | \ ``RestClient``\ を用いた実装におけるBean定義例では、Spring FrameworkにおいてBuilderパターンを利用したメソッドチェーンによる実装を前提としているため、Java Configを利用した設定例のみを示す。 | | 本ガイドラインでは、GETメソッドとPOSTメソッドを使用したクライアント処理の実装例のみを紹介するが、Spring Frameworkが提供するRESTクライアント実装は他のHTTPメソッド(PUT, PATCH, DELETE, HEAD, OPTIONSなど)もサポートしており、同様に実装することができる。 | 他のHTTPメソッドを利用する場合も含め、本ガイドラインで紹介する以外の利用方法については、\ :url_spring_javadoc:`RestClientのJavadoc `\ 、\ :url_spring_javadoc:`RestTemplateのJavadoc `\ を参照されたい。 | | RESTクライアントを利用した同期通信のための基本的な設定と、データ取得、登録を行うには以下のような手順で実装を行う。 * \ :ref:`RestClientHowToUseSetup`\ * \ :ref:`RestClientHowToUseBaseSettings`\ * \ :ref:`RestClientHowToUseGet`\ * \ :ref:`RestClientHowToUsePost`\ * \ :ref:`RestClientHowToUseErrorHandling`\ | この他、以下のような内容についても実装例を示しているが、アプリケーションの要件に依存すると思われるので、必要に応じて参照してほしい。 * \ :ref:`RestClientHowToUseRequestHeader`\ * \ :ref:`RestClientHowToUseAuthorizationHeader`\ * \ :ref:`RestClientHowToUseTimeoutSettings`\ * \ :ref:`RestClientHowToUseFileUpload`\ * \ :ref:`RestClientHowToUseFileDownload`\ | .. _RestClientHowToUseSetup: 同期通信のセットアップ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 同期通信を行う場合は、\ ``RestClient``\ または\ ``RestTemplate``\ をDIコンテナに登録し、RESTクライアントを利用するコンポーネントにインジェクションする。 | 依存ライブラリ設定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestClient``\ / \ ``RestTemplate``\ を使用するために\ ``pom.xml``\ に、Spring Frameworkのspring-webライブラリと、HttpClientの実装としてApache HttpComponents HttpClientライブラリを追加する。 | マルチプロジェクト構成の場合は、domainプロジェクトの\ ``pom.xml``\ に追加する。 .. code-block:: xml org.springframework spring-web org.apache.httpcomponents.client5 httpclient5 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | Spring Frameworkの\ ``spring-web``\ ライブラリをdependenciesに追加する。 * - | (2) - | Apache HttpComponents HttpClient を依存ライブラリに追加する。 | 本設定は、\ ``ClientHttpRequestFactory``\ のインターフェース実装として、\ ``HttpComponentsClientHttpRequestFactory``\ を利用するための設定である。 | RESTクライアントのBean定義 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestClient``\ / \ ``RestTemplate``\ のBean定義を行い、DIコンテナに登録する。これらのBeanは、クライアントアプリケーションにおける、リクエスト送信の起点となる。 | また、\ ``RestClient``\ / \ ``RestTemplate``\ のBean定義時には\ ``ClientHttpRequestFactory``\ を通して実際のリクエスト作成で利用する実装を選択することができる。 | 本ガイドラインでは原則として、Apache HttpComponents HttpClientを使用してリクエストを作成する\ ``ClientHttpRequestFactory``\ 実装である\ ``HttpComponentsClientHttpRequestFactory``\ の利用を前提とする。 | | \ ``HttpComponentsClientHttpRequestFactory``\ は、Spring Framework 6.1よりサーバへのリクエスト送信を行う前にリクエストボディをバッファリングしないようになったため、リクエストのContent-Lengthヘッダが設定されなくなった。 | Content-Lengthヘッダを設定したい場合は、「\ :ref:`Content-Lengthヘッダについて `\」に記載している設定を追加する必要がある。 | \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) // (1) .build(); // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestClient.Builder``\ の\ ``requestFactory``\ メソッドに、\ ``org.springframework.http.client.HttpComponentsClientHttpRequestFactory``\ を設定する。 | \ ``HttpComponentsClientHttpRequestFactory``\ を利用した通信設定については「\ :ref:`RestClientHowToExtendClientHttpRequestFactory`\ 」を参照されたい。 * - | (2) - | \ ``RestClient.Builder``\ の\ ``build``\ メソッドを使用して\ ``RestClient``\ のBeanを生成し、DIコンテナに登録する。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restTemplate") public RestTemplate restTemplate() { return new RestTemplate(new HttpComponentsClientHttpRequestFactory()); // (1) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestTemplate``\ のコンストラクタの引数に、\ ``org.springframework.http.client.HttpComponentsClientHttpRequestFactory``\ を設定し、Beanを生成、DIコンテナへ登録する。 | \ ``HttpComponentsClientHttpRequestFactory``\ を利用した通信設定については「\ :ref:`RestClientHowToExtendClientHttpRequestFactory`\ 」を参照されたい。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestTemplate``\ のコンストラクタの引数に、\ ``org.springframework.http.client.HttpComponentsClientHttpRequestFactory``\ を設定し、Beanを生成、DIコンテナへ登録する。 | \ ``HttpComponentsClientHttpRequestFactory``\ を利用した通信設定については「\ :ref:`RestClientHowToExtendClientHttpRequestFactory`\ 」を参照されたい。 代表的な\ ``RestClient``\ / \ ``RestTemplate``\ のBeanのカスタマイズ方法は、下記で実装例を示しているため、必要に応じて参照されたい。 * \ :ref:`RestClientHowToExtendUriBuilderFactory`\ * \ :ref:`RestClientHowToExtendClientHttpRequestFactory`\ * \ :ref:`RestClientHowToExtendHttpMessageConverter`\ * \ :ref:`RestClientHowToExtendClientHttpRequestInterceptor`\ | RESTクライアントの利用 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" RESTクライアントを利用する場合は、DIコンテナに登録されている\ ``RestClient``\ / \ ``RestTemplate``\ をインジェクションする。 \ **RESTクライアントのインジェクション例**\ .. tabs:: .. group-tab:: RestClient .. code-block:: java @Service public class AccountServiceImpl implements AccountService { @Inject RestClient restClient; // omitted } .. group-tab:: RestTemplate .. code-block:: java @Service public class AccountServiceImpl implements AccountService { @Inject RestTemplate restTemplate; // omitted } | .. _RestClientHowToUseBaseSettings: 基準となるURLを設定する ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``baseUrl``\ を利用すると、REST APIにアクセスする際の基準となるURLをあらかじめ設定しておくことができる。 | | 例えば以下のURLにおいて、先頭の「\ ``http://localhost:8080/api/v1``\ 」を\ ``baseUrl``\ として設定しておくことで、クライアント側の実装では後続のパス「\ ``/users``\ 」のみを指定することで、完全なURLが自動的に生成される。 | なお、\ ``baseUrl``\ を指定していても、RESTクライアントを使用する際に指定するURIに絶対URLを明示的に指定した場合は、\ ``baseUrl``\ の設定は無効となる。 | これにより、共通的なベースURLを設定しつつ、特定のリクエストでのみ異なるURL(別ドメイン等)へ送信することも可能となっている。 **URLの例** * URL: \ ``http://localhost:8080/api/v1/users``\ * baseUrl: \ ``http://localhost:8080/api/v1``\ * クライアントで指定するパス: \ ``/users``\ | baseUrlの設定方法は、利用するRESTクライアント実装によって異なる。 * \ ``RestClient``\ : \ ``RestClient.builder().baseUrl(...)``\ メソッドを使用して設定する。 * \ ``RestTemplate``\ : \ ``DefaultUriBuilderFactory``\ に\ ``baseUrl``\ を設定して、\ ``RestTemplate``\ に適用する必要がある。 | 以下に設定例を示す。 .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Value("${api.baseUrl:http://localhost:8080/api/v1}") // (1) private String url; @Bean("restClient") public RestClient restClient() { return RestClient.builder().baseUrl(url) // (2) .requestFactory(new HttpComponentsClientHttpRequestFactory()) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | プロパティファイルから基準となるURLを取得し設定を行う。 * - | (2) - | \ ``RestClient.Builder``\ の\ ``baseUrl``\ メソッドを使用して、(1)のURLを設定する。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Value("${api.baseUrl:http://localhost:8080/api/v1}") // (1) private String url; @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(url)); // (2) return restTemplate; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | プロパティファイルから基準となるURLを取得し設定を行う。 * - | (2) - | \ ``DefaultUriBuilderFactory``\ のコンストラクタに(1)のURLを設定してインスタンスを生成する。 | \ ``RestTemplate``\ の\ ``setUriTemplateHandler``\ メソッドを使用して、生成した\ ``DefaultUriBuilderFactory``\ のインスタンスを設定する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | プロパティファイルから基準となるURLを取得する。 | \ ``DefaultUriBuilderFactory``\ のコンストラクタに取得したURLを指定してBeanを生成する。 * - | (2) - | \ ``RestTemplate``\ の\ ``uriTemplateHandler``\ プロパティを使用して、生成した\ ``DefaultUriBuilderFactory``\ のBeanを設定する。 利用時のURLは以下の通りとなる。 .. tabs:: .. group-tab:: RestClient .. code-block:: java private String uri = "/users/{id}"; // (1) @Inject private RestClient restClient; public User getUser(String id) { return restClient.get().uri(uri, id) .retrieve() .body(User.class); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``baseUrl``\ で指定したURL以降のパスのみを指定する。 .. group-tab:: RestTemplate .. code-block:: java private String uri = "/users/{id}"; // (1) @Inject private RestTemplate restTemplate; public User getUser(String id) { return restTemplate.getForObject(uri, User.class, id); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``baseUrl``\ で指定したURL以降のパスのみを指定する。 \ ``baseUrl``\ を設定しない、または絶対URLを指定して\ ``baseUrl``\ の設定を上書きする場合は、以下の実装を行う。 .. tabs:: .. group-tab:: RestClient .. code-block:: java @Value("${api.baseUrl:http://localhost:8080/api/v2}/users/{id}") // (1) private String uri; @Inject private RestClient restClient; public User getUser(String id) { return restClient.get().uri(uri, id) .retrieve() .body(User.class); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | プロパティファイルから絶対パスで記載されたURLを取得し、利用するURLとして指定する。 .. group-tab:: RestTemplate .. code-block:: java @Value("${api.baseUrl:http://localhost:8080/api/v2}/users/{id}") // (1) private String uri; @Inject private RestTemplate restTemplate; public User getUser(String id) { return restTemplate.getForObject(uri, User.class, id); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | プロパティファイルから絶対パスで記載されたURLを取得し、利用するURLとして指定する。 | .. _RestClientHowToUseGet: データの取得(GETリクエスト送信) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``RestClient``\ / \ ``RestTemplate``\ は、GETリクエスト送信を行うためのメソッドを複数提供している。代表的なメソッドを以下に示す。 | これ以外のメソッドについては、「\ :url_spring_javadoc:`RestClientのJavadoc `\ 」、「\ :url_spring_javadoc:`RestTemplateのJavadoc `\ 」を参照されたい。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.30\linewidth}|p{0.30\linewidth}|p{0.30\linewidth}| .. list-table:: \ **代表的なメソッド一覧**\ :header-rows: 1 :widths: 10 30 30 30 * - 項番 - 用途 - RestClientのメソッド - RestTemplateのメソッド * - | (1) - | レスポンスボディを任意のデータ型で取得する - | \ ``RestClient.get().uri(...).retrieve().body(...)``\ - | \ ``RestTemplate.getForObject(...)``\ * - | (2) - | レスポンス情報を含む結果を取得する - | \ ``RestClient.get().uri(...).retrieve().toEntity(...)``\ - | \ ``RestTemplate.getForEntity(...)``\ | .. _RestClientHowToUseGetForObject: GETメソッドを指定したリクエスト送信の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | GETメソッドでリクエスト送信を行い、レスポンスボディを任意のデータ型で取得する実装の方法を以下に示す。 | 以下の実装例は、\ ``User``\ を表現するJavaBeanクラスにてレスポンスボディを受け取る実装である。 .. tabs:: .. group-tab:: RestClient \ **bodyメソッドの使用例**\ フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private String uri; メソッド内部 .. code-block:: java User user = restClient.get() // (1) .uri(uri) // (2) .retrieve() // (3) .body(User.class); // (4) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``get``\ メソッドを使用してHTTPメソッドにGETを設定し、\ ``RequestHeadersUriSpec``\ を取得する。 * - | (2) - | \ ``RequestHeadersUriSpec.uri``\ メソッドを使用して、リクエスト送信先のURLを設定する。 | | 他にもリクエストに対する設定を行う必要がある場合は、\ ``RequestHeadersUriSpec``\ を利用して、リクエストの設定を行う。 * - | (3) - | \ ``RequestHeadersUriSpec``\ でリクエストに対する設定を行った後、\ :url_spring_javadoc:`retrieve `\ メソッドを呼び出し、レスポンスの設定(受信時のデータ型、例外ハンドラ等)を行うための\ ``ResponseSpec``\ を取得する。 | \ ``retrieve``\ メソッドでは、\ ``ResponseSpec``\ の生成、取得までを行い、リクエストの送信は行われない。 | リクエストの送信は、\ ``ResponseSpec``\ のメソッド(\ ``body``\ 、\ ``toEntity``\ 等)が呼び出されたタイミングで行われる。 * - | (4) - | \ ``ResponseSpec.body``\ メソッドにてレスポンスボディを受け取る型を指定し、リクエストの送信を行う。 | レスポンスボディのデータは\ ``HttpMessageConverter``\ によって\ ``body``\ の引数に指定したJavaクラスへ変換された後、返却される。 .. group-tab:: RestTemplate \ **getForObjectメソッドの使用例**\ フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private URI uri; メソッド内部 .. code-block:: java User user = restTemplate.getForObject(uri, User.class); // (1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``getForObject``\ メソッドを使用した場合は、戻り値はレスポンスボディの値になる。 | レスポンスボディのデータは\ ``HttpMessageConverter``\ によって第2引数に指定したJavaクラスへ変換された後、返却される。 | .. _RestClientHowToGetToEntity: ステータスコードやレスポンスヘッダを含めたレスポンスの取得 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | HTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合は、以下のように実装する。 | 実装例はGETメソッドを使用した場合であるが、POSTメソッドやPUTメソッドなど他のHTTPメソッドを使用した場合も同様に実装できる。 .. tabs:: .. group-tab:: RestClient \ **toEntityメソッドの使用例**\ インポート宣言 .. code-block:: java import org.springframework.http.ResponseEntity; フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private String uri; メソッド内部 .. code-block:: java ResponseEntity responseEntity = restClient.get().uri(uri) // (1) .retrieve() .toEntity(User.class); // (2) HttpStatusCode statusCode = responseEntity.getStatusCode(); // (3) HttpHeaders header = responseEntity.getHeaders(); // (4) User user = responseEntity.getBody(); // (5) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestClient.get``\ メソッドを使用してHTTPメソッドにGETを設定する。 * - | (2) - | \ ``ResponseSpec.toEntity``\ メソッドを使用した場合は、戻り値は\ ``org.springframework.http.ResponseEntity``\ となる。 * - | (3) - | HTTPステータスコードは\ ``ResponseEntity``\ の\ ``getStatusCode``\ メソッドを用いて取得する。 * - | (4) - | レスポンスヘッダは\ ``ResponseEntity``\ の\ ``getHeaders``\ メソッドを用いて取得する。 * - | (5) - | レスポンスボディは\ ``ResponseEntity``\ の\ ``getBody``\ メソッドを用いて取得する。 .. group-tab:: RestTemplate \ **getForEntityメソッドの使用例**\ インポート宣言 .. code-block:: java import org.springframework.http.ResponseEntity; フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private URI uri; メソッド内部 .. code-block:: java ResponseEntity responseEntity = restTemplate.getForEntity(uri, User.class); // (1) HttpStatusCode statusCode = responseEntity.getStatusCode(); // (2) HttpHeaders header = responseEntity.getHeaders(); // (3) User user = responseEntity.getBody(); // (4) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``getForEntity``\ メソッドを使用した場合は、戻り値は\ ``org.springframework.http.ResponseEntity``\ となる。 * - | (2) - | HTTPステータスコードは\ ``ResponseEntity``\ の\ ``getStatusCode``\ メソッドを用いて取得する。 * - | (3) - | レスポンスヘッダは\ ``ResponseEntity``\ の\ ``getHeaders``\ メソッドを用いて取得する。 * - | (4) - | レスポンスボディは\ ``ResponseEntity``\ の\ ``getBody``\ メソッドを用いて取得する。 .. note:: \ **ResponseEntityとは**\ \ ``ResponseEntity``\ はHTTPレスポンスを表すクラスで、HTTPステータスコード、レスポンスヘッダ、レスポンスボディの情報を取得することができる。 詳細は\ :url_spring_javadoc:`ResponseEntityのJavadoc `\ を参照されたい。 | .. _RestClientHowToUseGetCollection: コレクション形式のデータ取得 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" サーバから応答されるレスポンスボディの電文(JSON等)がコレクション形式の場合は、以下の実装を行う。 \ **コレクション形式のデータの取得例**\ .. tabs:: .. group-tab:: RestClient .. code-block:: java List users = //(1) restClient.get().uri(uri) .retrieve() .body(new ParameterizedTypeReference>(){}); //(2) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``List``\ <レスポンスデータの型>を指定する。 * - | (2) - | \ ``ResponseSpec.body``\ メソッドの引数に\ ``org.springframework.core.ParameterizedTypeReference``\ のインスタンスを指定し、型パラメータに\ ``List``\ <レスポンスデータの型>を指定する。 .. group-tab:: RestTemplate | \ ``getForObject``\ メソッドでは、コレクション形式のデータをレスポンスの型として指定できないため、\ ``exchange``\ メソッドを使用してリクエストを送信する必要がある。 | \ ``exchange``\ メソッドの使い方については、「\ :ref:`RestClientHowToUseRequestHeaderRequest`\ 」にて説明を行っているのでこちらを参照されたい。 .. code-block:: java ResponseEntity> responseEntity = //(1) restTemplate.exchange(requestEntity, new ParameterizedTypeReference>(){}); //(2) List userList = responseEntity.getBody();//(3) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ResponseEntity``\ の型パラメータに\ ``List``\ <レスポンスデータの型>を指定する。 * - | (2) - | \ ``exchange``\ メソッドの第二引数に\ ``org.springframework.core.ParameterizedTypeReference``\ のインスタンスを指定し、型パラメータに\ ``List``\ <レスポンスデータの型>を指定する。 * - | (3) - | \ ``getBody``\ メソッドで、レスポンスボディのデータを取得する。 | .. _RestClientHowToUsePost: データの登録(POSTリクエスト送信) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``RestClient``\ / \ ``RestTemplate``\ は、POSTリクエスト送信を行うためのメソッドを複数提供している。代表的なメソッドの一覧を以下に示す。 | これ以外のメソッドについては、「\ :url_spring_javadoc:`RestClientのJavadoc `\ 」、「\ :url_spring_javadoc:`RestTemplateのJavadoc `\ 」を参照されたい。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.30\linewidth}|p{0.30\linewidth}|p{0.30\linewidth}| .. list-table:: \ **代表的なメソッド一覧**\ :header-rows: 1 :widths: 10 30 30 30 * - 項番 - 用途 - RestClientのメソッド - RestTemplateのメソッド * - | (1) - | レスポンスボディを任意のデータ型で取得する - | \ ``RestClient.post().uri(...).body(...).retrieve().body(...)``\ - | \ ``RestTemplate.postForObject(...)``\ * - | (2) - | レスポンス情報を含む結果を取得する - | \ ``RestClient.post().uri(...).body(...).retrieve().toEntity(...)``\ - | \ ``RestTemplate.postForEntity(...)``\ | 「レスポンス情報を含む結果を取得する」方法については、GETメソッドを指定した実装と同様のため、「\ :ref:`RestClientHowToGetToEntity`\ 」を参照されたい。 | POSTメソッドを指定したリクエスト送信の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | POSTメソッドでリクエスト送信を行い、レスポンスボディを任意のデータ型で取得する実装の方法を以下に示す。 | 以下の実装例は、\ ``User``\ を表現するJavaBeanクラスにてレスポンスボディを受け取る実装である。 .. tabs:: .. group-tab:: RestClient \ **bodyメソッドの使用例**\ フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private String uri; メソッド内部 .. code-block:: java var user = new User(); // omitted User user = restClient.post() // (1) .uri(uri) // (2) .body(user) // (3) .retrieve() // (4) .body(User.class); // (5) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``post``\ メソッドを使用してHTTPメソッドにPOSTを設定し、\ ``RequestBodyUriSpec``\ を取得する。 * - | (2) - | \ ``RequestBodyUriSpec.uri``\ メソッドを使用して、リクエスト送信先のURLを設定する。 | | 他にもリクエストに対する設定を行う必要がある場合は、\ ``RequestBodyUriSpec``\ を利用して、リクエストの設定を行う。 * - | (3) - | \ ``RequestBodyUriSpec.body``\ メソッドを使用してリクエストボディを設定する。 | 設定されたリクエストボディのオブジェクトは、\ ``HttpMessageConverter``\ によって電文形式(JSON等)に変換される。 * - | (4) - | \ ``RequestBodyUriSpec``\ でリクエストに対する設定を行った後、\ :url_spring_javadoc:`retrieve `\ メソッドを呼び出し、レスポンスの設定(受信時のデータ型、例外ハンドラ等)を行うための\ ``ResponseSpec``\ を取得する。 | \ ``retrieve``\ メソッドでは、\ ``ResponseSpec``\ の生成、取得までを行い、リクエストの送信は行われない。 | リクエストの送信は、\ ``ResponseSpec``\ のメソッド(\ ``body``\ 、\ ``toEntity``\ 等)が呼び出されたタイミングで行われる。 * - | (5) - | \ ``ResponseSpec.body``\ メソッドにてレスポンスボディを受け取る型を指定し、リクエストの送信を行う。 | レスポンスボディのデータは\ ``HttpMessageConverter``\ によって\ ``body``\ の引数に指定したJavaクラスへ変換された後、返却される。 .. group-tab:: RestTemplate \ **postForObjectメソッドの使用例**\ フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private URI uri; メソッド内部 .. code-block:: java var user = new User(); // omitted User user = restTemplate.postForObject(uri, user, User.class); // (1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``postForObject``\ メソッドは、簡易にPOSTリクエストを実装できる。 | 第二引数には、\ ``HttpMessageConverter``\ によってリクエストボディに変換されるJavaオブジェクトを設定する。 | \ ``postForObject``\ メソッドを使用した場合は、戻り値はレスポンスボディの値になる。 | .. _RestClientHowToUseErrorHandling: エラーハンドリング ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. _RestClientHowToUseErrorHandlingHandleException: 例外ハンドリング(デフォルトの動作) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | デフォルトで利用するエラーハンドラーは「\ :ref:`RestClientOverviewResponseErrorHandler`\ 」で記載している通り、HTTPステータスコードに応じて特定の例外オブジェクトを返却する。 | 本項では、デフォルトのエラーハンドラが返却する例外オブジェクトのハンドリング方法について説明する。 | なお、以降で示している例外ハンドリングに関する実装例はあくまでも例外処理の方法を説明するための例であり、例外ハンドリングのベストプラクティスを示すものではない。アプリケーション実装の際には、要件に応じて\ **適切な例外ハンドリングを行うこと。**\ \ **例外ハンドリングの実装例**\ .. tabs:: .. group-tab:: RestClient フィールド宣言部 .. code-block:: java @Value("${api.retry.maxCount}") int retryMaxCount; @Value("${api.retry.retryWaitTimeCoefficient}") int retryWaitTimeCoefficient; メソッド内部 .. code-block:: java int retryCount = 0; while (true) { try { User user = restClient.get().uri(uri) .retrieve() .body(User.class); break; } catch (HttpServerErrorException e) { // (1) if (retryCount == retryMaxCount) { throw e; } retryCount++; if (logger.isWarnEnabled()) { logger.warn("An error ({}) occurred on the server. (The number of retries:{} Times)", e.getStatusCode(), retryCount); // (2) } try { Thread.sleep(retryWaitTimeCoefficient * retryCount); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } // omitted } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 例外をキャッチしてエラー処理を行う。 | 上記の例では、サーバエラー(500系)時に\ ``throw``\ される\ ``HttpServerErrorException``\ をキャッチしてリトライ処理を行っている。 * - | (2) - | 必要に応じて例外オブジェクトからレスポンスデータを取得し、ログ出力や処理の判定等に利用する。 | エラー時のレスポンスデータ(HTTPステータスコード、レスポンスヘッダ、レスポンスボディなど)は、例外クラスのgetterメソッドを呼び出すことで取得することができる。 | 取得可能なレスポンスデータについては、\ :url_spring_javadoc:`RestClientResponseException `\ を参照されたい。 .. group-tab:: RestTemplate フィールド宣言部 .. code-block:: java @Value("${api.retry.maxCount}") int retryMaxCount; @Value("${api.retry.retryWaitTimeCoefficient}") int retryWaitTimeCoefficient; メソッド内部 .. code-block:: java int retryCount = 0; while (true) { try { User user = restTemplate.getForObject(uri, User.class); break; } catch (HttpServerErrorException e) { // (1) if (retryCount == retryMaxCount) { throw e; } retryCount++; if (logger.isWarnEnabled()) { logger.warn("An error ({}) occurred on the server. (The number of retries:{} Times)", e.getStatusCode(), retryCount); // (2) } try { Thread.sleep(retryWaitTimeCoefficient * retryCount); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } // omitted } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 例外をキャッチしてエラー処理を行う。 | 上記の例では、サーバエラー(500系)時に\ ``throw``\ される\ ``HttpServerErrorException``\ をキャッチしてリトライ処理を行っている。 | .. _RestClientHowToUseErrorHandlingResponseEntity: HTTPステータスコードでハンドリング(エラーハンドラの拡張) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | エラーハンドラを拡張し、HTTPステータスコードでハンドリングする方法について説明を行う。 | エラーハンドラの拡張用途は、HTTPステータスコードでハンドリングする方法以外にもあるため、本項で説明するのは一例である。 | 「\ :ref:`RestClientOverviewResponseErrorHandler`\ 」で記載している通り、エラーハンドラの実装方法は\ ``RestClient``\ と\ ``RestTemplate``\ で異なる。 | \ ``RestClient``\ では、\ ``StatusHandler``\ と、\ ``ResponseErrorHandler``\ インタフェースの実装クラスを使った2つのエラーハンドリングの実装方法があるので、どちらかを拡張し\ ``RestClient``\ に設定することで、独自のエラー処理を行うことができる。 | \ ``RestTemplate``\ では、\ ``ResponseErrorHandler``\ インタフェースの実装クラスを\ ``RestTemplate``\ に設定することで、独自のエラー処理を行うことができる。 | | 以下の例では、サーバエラー及びクライアントエラーが発生した場合でも\ ``ResponseEntity``\ を返却し、HTTPステータスコードでエラーハンドリングが行えるように拡張をしている。 .. tabs:: .. group-tab:: RestClient \ **Bean定義ファイルの定義例(StatusHandler)**\ | 以下は、\ ``StatusHandler``\ を利用した実装例である。 - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .defaultStatusHandler(HttpStatusCode::isError, (request, response) -> {}) // (1) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestClient.Builder``\ の\ :url_spring_javadoc:`defaultStatusHandler `\ メソッドに\ ``Predicate``\ と、\ ``ResponseSpec.ErrorHandler``\ を設定する。 | 上記の実装例では、\ ``HttpStatusCode``\ がサーバエラー(5xx系)及びクライアントエラー(4xx系)の場合、例外を発生させずに処理を継続するようにしている。 | \ ``HttpStatusCode``\ で利用できるメソッドについては、「\ :url_spring_javadoc:`HttpStatusCodeのJavadoc `\ 」を参照されたい。 | | なお、上記の実装は処理を継続し正常終了扱いとしているため、デフォルトの動作と異なり後続のメッセージ変換処理(\ ``HttpMessageConverter``\) は動作する。 | そのため、サーバ側から返却するレスポンスのBody部は、サーバエラー(5xx系)及びクライアントエラー(4xx系)が発生した際も\ ``HttpMessageConverter``\ で型変換できるような値を返却する必要がある。 \ **クライアント処理の実装例**\ .. code-block:: java int retryCount = 0; while (true) { responseEntity = restClient.get().uri(uri) .retrieve() .toEntity(User.class); if (responseEntity.getStatusCode() == HttpStatus.OK) { // (1) break; } else if (responseEntity.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE) { // (2) if (retryCount == retryMaxCount) { break; } retryCount++; if (logger.isWarnEnabled()) { logger.warn("An error ({}) occurred on the server. (The number of retries:{} Times)", responseEntity.getStatusCode(), retryCount); } try { Thread.sleep(retryWaitTimeCoefficient * retryCount); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } // omitted } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 上記の実装例では、エラー時にも\ ``ResponseEntity``\ を返すようにエラーハンドラを拡張しているので、返却された\ ``ResponseEntity``\ からHTTPステータスコードを取得して、処理結果が正常であったか確認する必要がある。 * - | (2) - | エラー発生時も返却された\ ``ResponseEntity``\ からHTTPステータスコードを取得して、その値に応じて処理を制御することができる。 .. group-tab:: RestTemplate \ **エラーハンドラの実装クラスの作成例**\ .. code-block:: java import java.io.IOException; import java.net.URI; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.DefaultResponseErrorHandler; public class CustomErrorHandler extends DefaultResponseErrorHandler { // (1) // (2) @Override public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { // Don't throw Exception. } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ResponseErrorHandler``\ インタフェースの実装クラスを作成する。 | 上記の例では、エラーハンドラのデフォルト実装クラスである\ ``DefaultResponseErrorHandler``\ を拡張している。 * - | (2) - | \ ``handleError``\ メソッドには、拡張元である\ ``DefaultResponseErrorHandler``\ の\ ``hasError``\ メソッドでエラーと判断されたHTTPステータスコードに対するエラー処理を実装する。 | \ ``DefaultResponseErrorHandler``\ の\ ``hasError``\ メソッドがエラーとみなすHTTPステータスコードは、サーバエラー(5xx系)及びクライアントエラー(4xx系)が対象である。 | 詳細は「\ :url_spring_javadoc:`DefaultResponseErrorHandlerのJavadoc `\ 」を参照されたい。 | | 上記の例では、サーバエラー(5xx系)及びクライアントエラー(4xx系)が発生した場合でも例外を発生させないことで、\ ``RestTemplate``\ の呼び出し側で\ ``ResponseEntity``\ を受け取れるようにしている。 \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("customErrorHandler") public CustomErrorHandler customErrorHandler() { return new CustomErrorHandler(); } @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setErrorHandler(customErrorHandler()); // (2) return restTemplate; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ResponseErrorHandler``\ の実装クラスのBean定義を行う。 * - | (2) - | \ ``RestTemplate``\ の\ ``setErrorHandler``\ メソッドに、(1)で生成した\ ``ResponseErrorHandler``\ の実装クラスのBeanを設定する。 .. note:: \ **Spring Frameworkが提供するNoOpResponseErrorHandlerについて**\ サーバエラー及びクライアントエラーが発生した場合でも例外を発生させずに\ ``ResponseEntity``\ を返却する実装であれば、同様の動作を行う\ ``NoOpResponseErrorHandler``\ が \ ``ResponseErrorHandler``\ の実装クラスとしてSpring Frameworkから提供されているため、こちらで代替することも可能である。 - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("noOpResponseErrorHandler") public NoOpResponseErrorHandler noOpResponseErrorHandler() { return new NoOpResponseErrorHandler(); } @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setErrorHandler(noOpResponseErrorHandler()); // (2) return restTemplate; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.springframework.web.client.NoOpResponseErrorHandler``\ のBean定義を行う。 * - | (2) - | \ ``RestTemplate``\ の\ ``setErrorHandler``\ メソッドに\ ``NoOpResponseErrorHandler``\ のBeanを設定する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ResponseErrorHandler``\ の実装クラスのBean定義を行う。 * - | (2) - | \ ``errorHandler``\ プロパティに、(1)で生成した\ ``ResponseErrorHandler``\ の実装クラスのBeanをインジェクションする。 .. note:: \ **Spring Frameworkが提供するNoOpResponseErrorHandlerについて**\ サーバエラー及びクライアントエラーが発生した場合でも例外を発生させずに\ ``ResponseEntity``\ を返却する実装であれば、同様の動作を行う\ ``NoOpResponseErrorHandler``\ が \ ``ResponseErrorHandler``\ の実装クラスとしてSpring Frameworkから提供されているため、こちらで代替することも可能である。 - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.springframework.web.client.NoOpResponseErrorHandler``\ のBean定義を行う。 * - | (2) - | \ ``errorHandler``\ プロパティに\ ``NoOpResponseErrorHandler``\ のBeanをインジェクションする。 \ **クライアント処理の実装例**\ .. code-block:: java int retryCount = 0; while (true) { responseEntity = restTemplate.exchange(requestEntity, User.class); if (responseEntity.getStatusCode() == HttpStatus.OK) { // (1) break; } else if (responseEntity.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE) { // (2) if (retryCount == retryMaxCount) { break; } retryCount++; if (logger.isWarnEnabled()) { logger.warn("An error ({}) occurred on the server. (The number of retries:{} Times)", responseEntity.getStatusCode(), retryCount); } try { Thread.sleep(retryWaitTimeCoefficient * retryCount); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } // omitted } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 上記の実装例では、エラー時にも\ ``ResponseEntity``\ を返すようにエラーハンドラを拡張しているので、返却された\ ``ResponseEntity``\ からHTTPステータスコードを取得して、処理結果が正常であったか確認する必要がある。 * - | (2) - | エラー発生時も返却された\ ``ResponseEntity``\ からHTTPステータスコードを取得して、その値に応じて処理を制御することができる。 | \ ``RestClient``\ 呼び出し毎のエラーハンドリング(エラーハンドラの拡張) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestClient``\ のみの実装となるが、共通のエラーハンドラとは別に\ ``RestClient``\ の呼び出し毎にエラーハンドラの拡張が行える。 | 本項では、\ ``RestClient``\ の\ ``onStatus``\ メソッドを使用して、\ ``RestClient``\ の呼び出し毎でエラーハンドリングを行う方法について説明する。 \ **RestClient呼び出し毎のエラーハンドリングの実装例**\ .. code-block:: java User user = restClient.get().uri(uri) .retrieve() .onStatus(HttpStatusCode::is5xxServerError, (request, response) -> { // (1) // omitted }) .body(User.class); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``defaultStatusHandler``\ の実装と同様に、\ :url_spring_javadoc:`onStatus `\ メソッドに\ ``Predicate``\ と\ ``ResponseSpec.ErrorHandler``\ を設定する。 | 上記の実装例では、サーバエラー(5xx系)のHTTPステータスコードが返却された場合のエラーハンドリングを行っている。 | \ ``HttpStatusCode``\ で利用できるメソッドについては、「\ :url_spring_javadoc:`HttpStatusCodeのJavadoc `\ 」を参照されたい。 | | なお、\ ``onStatus``\ メソッドの記載がある場合は、\ ``defaultStatusHandler``\ の実装より優先されて処理される。 | 上記の実装例の動作としては、サーバエラー(5xx系)のHTTPステータスコードが返却された場合にのみ適用され、クライアントエラー(4xx系)のHTTPステータスコードが返却された場合は\ ``defaultStatusHandler``\ の実装が適用される。 | .. _RestClientHowToUseRequestHeader: リクエストヘッダの設定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | リクエストヘッダを設定する方法には、アプリケーション全体で共通のヘッダを設定する方法と、リクエスト送信単位でヘッダを設定する方法がある。 | クライアントアプリケーション内で、複数回のリクエスト送信処理を記述するとき、各リクエストのヘッダが同一で良い場合には、リクエスト送信処理ごとにリクエストヘッダを設定する処理を書いていると冗長なコードを記述することになる。 | このような場合にはアプリケーション全体で共通のリクエストヘッダを設定するようにし、共通ではないリクエストヘッダを設定する必要がある実装箇所のみ、リクエスト送信単位でリクエストヘッダの設定を行ったほうがよい。 | .. _RestClientHowToUseRequestHeaderCommon: アプリケーション全体で共通のリクエストヘッダを設定する方法 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 「\ :ref:`RestClientOverviewClientHttpRequestInitializer`\ 」で記載の通り、\ ``RestClient``\ では\ ``defaultHeader``\ 、\ ``RestTemplate``\ では\ ``ClientHttpRequestInitializer``\ を使用することで共通のリクエストヘッダの設定が可能である。 | リクエスト送信単位でリクエストヘッダの設定を変えたい場合は、「\ :ref:`RestClientHowToUseRequestHeaderRequest`\ 」を参照されたい。 | | 以下は、リクエストヘッダに共通のカスタムヘッダを付与する例である。 .. tabs:: .. group-tab:: RestClient \ **Bean定義ファイルの定義例**\ - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .defaultHeader("Custom-Header", "CustomHeaderValue") // (1) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestClient.Builder``\ の\ ``defaultHeader``\ メソッドを使用してリクエストヘッダにカスタムヘッダを付与している。 | 上記の例では、\ ``Custom-Header``\ というキーで\ ``CustomHeaderValue``\ という値を設定している。 | 複数のリクエストヘッダを設定する場合は、\ ``RestClient.Builder``\ の\ ``defaultHeaders``\ メソッドを使用して実装することができる。 .. note:: \ **ClientHttpRequestInitializerを使った実装**\ | \ ``RestClient``\ の場合も、以下のように\ ``requestInitializer``\ メソッドを使って\ ``ClientHttpRequestInitializer``\ を使った実装も可能である。 | \ ``RestClient``\ を利用する場合、リクエストヘッダの設定には、\ ``ClientHttpRequestInitializer``\ を利用する事もできるが、リクエストヘッダのみを設定したい際には\ ``defaultHeader``\ メソッドの利用を推奨する。 | \ ``defaultHeader``\ メソッドを利用するほうが、 ``ClientHttpRequestInitializer``\ による実装よりも簡潔に記述でき、設定の意味も明瞭になる。 .. group-tab:: RestTemplate \ **ClientHttpRequestInitializerの実装例**\ .. code-block:: java // (1) public class CustomHeaderInitializer implements ClientHttpRequestInitializer { // (2) @Override public void initialize(ClientHttpRequest request) { request.getHeaders().add("Custom-Header", "CustomHeaderValue"); // (3) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInitializer``\ の実装クラスを作成する。 * - | (2) - | \ ``initialize``\ メソッドに\ ``ClientHttpRequest``\ の初期化処理を実装する。 | 上記の例では、リクエストヘッダにカスタムヘッダを付与している。 * - | (3) - | リクエストヘッダの名前と値を設定する。 | 上記の例では、\ ``Custom-Header``\ というキーで\ ``CustomHeaderValue``\ という値を設定している。 \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("customHeaderInitializer") public CustomHeaderInitializer customHeaderInitializer() { return new CustomHeaderInitializer(); } @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setClientHttpRequestInitializers(List.of(customHeaderInitializer())); // (2) return restTemplate; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInitializer``\ のBean定義を行う。 * - | (2) - | \ ``RestTemplate``\ の\ ``setClientHttpRequestInitializers``\ メソッドに\ ``ClientHttpRequestInitializer``\ のBeanを設定する。 | \ ``setClientHttpRequestInitializers``\ メソッドは、\ ``List``\ 型で引数が定義されているため、複数の\ ``ClientHttpRequestInitializer``\ を設定することができる。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInitializer``\ のBean定義を行う。 * - | (2) - | \ ``RestTemplate``\ の\ ``clientHttpRequestInitializers``\ プロパティに\ ``ClientHttpRequestInitializer``\ のBeanを設定する。 | \ ``clientHttpRequestInitializers``\ プロパティは、\ ``List``\ 型で定義されているため、複数の\ ``ClientHttpRequestInitializer``\ を設定することができる。 | .. _RestClientHowToUseRequestHeaderRequest: リクエスト送信単位でリクエストヘッダを設定する方法 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 本項の設定は、リクエスト送信単位でヘッダの設定を変えたい場合に利用する。 | リクエストの設定は、基本的には\ :ref:`RestClientHowToUseRequestHeaderCommon`\ を用いて設定を行えばよいが、特定のリクエスト送信のみヘッダのカスタマイズを行いたい場合は、本項で説明する方法を用いて実装を行う。 | 実装方法は、\ ``RestClient``\ と\ ``RestTemplate``\ で異なる。 | | \ ``RestClient``\ で実装する場合、\ ``Content-Type``\ や\ ``Accept``\ といった代表的なHTTPヘッダについては、\ ``RequestBodyUriSpec``\ に専用の設定メソッド(\ ``contentType``\ 、\ ``accept``\)が用意されているため、これを利用すると良い。 | 一方で、設定用のメソッドが用意されていない、リクエストヘッダを設定する場合は、\ ``RequestBodyUriSpec``\ の\ ``header``\ メソッドを使用して設定することができる。 | 詳細は\ :url_spring_javadoc:`RequestBodyUriSpecのJavadoc `\ を参照されたい。 | | \ ``RestTemplate``\ で実装する場合は、\ ``RequestEntity``\ のメソッドを使用してHTTPヘッダを設定する。 | こちらにも \ ``RestClient``\ と同様に代表的なHTTPヘッダ用の設定メソッドがあるため、\ ``Content-Type``\ や\ ``Accept``\ といったヘッダの設定にはこちらを用いるのが良い。 | 設定用のメソッドが無いヘッダは、\ ``RequestEntity.HeadersBuilder``\ の\ ``header``\ メソッドを使用して設定する。 | 詳細は\ :url_spring_javadoc:`RequestEntityのJavadoc `\ を参照されたい。 | 本ガイドラインでは、以下のHTTPヘッダについて設定方法を示す。 * \ :ref:`RestClientHowToUseRequestHeaderContentType`\ * \ :ref:`RestClientHowToUseRequestHeaderAccept`\ * \ :ref:`RestClientHowToUseRequestHeaderAnyHeader`\ | .. _RestClientHowToUseRequestHeaderContentType: Content-Typeヘッダの設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | サーバへデータを送信する場合は、通常Content-Typeヘッダの指定が必要となる。 | Content-Typeヘッダをリクエスト送信時に設定するには、以下の通り実装を行う。 \ **Content-Typeヘッダの設定例**\ .. tabs:: .. group-tab:: RestClient インポート宣言 .. code-block:: java import org.springframework.http.ResponseEntity; フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private String uri; メソッド内部 .. code-block:: java var user = new User(); // omitted ResponseEntity responseEntity = restClient.post().uri(uri) .contentType(MediaType.APPLICATION_JSON) // (1) .body(user) .retrieve() .toEntity(User.class); User user = responseEntity.getBody(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestBodyUriSpec.contentType``\ メソッドを使用し、Content-Typeヘッダの値を指定する。 | 上記の実装例では、送信時のデータ形式がJSONであることを示す「\ ``application/json``\ 」を設定している。 .. group-tab:: RestTemplate インポート宣言 .. code-block:: java import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") private URI uri; メソッド内部 .. code-block:: java var user = new User(); // omitted RequestEntity requestEntity = RequestEntity .post(uri) // (1) .contentType(MediaType.APPLICATION_JSON) // (2) .body(user); // (3) ResponseEntity responseEntity = restTemplate.exchange(requestEntity, User.class); //(4) User user = responseEntity.getBody(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestEntity``\ の\ ``post``\ メソッドを使用し、POSTリクエスト用のリクエストビルダを生成する。 | パラメータにURIを設定する。 | 必要に応じて、リクエストビルダのメソッドを使用し、リクエストヘッダやリクエストボディを設定する。 * - | (2) - | \ ``RequestEntity.BodyBuilder``\ の\ ``contentType``\ メソッドを使用し、Content-Typeヘッダの値を指定する。 | 上記の実装例では、送信時のデータ形式がJSONであることを示す「\ ``application/json``\ 」を設定している。 * - | (3) - | \ ``RequestEntity.BodyBuilder``\ の\ ``body``\ メソッドにて、リクエストボディを設定し、\ ``RequestEntity``\ オブジェクトを作成する。 | ボディの設定が不要の場合は、\ ``build``\ メソッドを使用して\ ``RequestEntity``\ オブジェクトを作成する。 * - | (4) - | \ ``exchange``\ メソッドを使用し、リクエストを送信する。 | 引数には、(3)で生成した\ ``RequestEntity``\ と、レスポンスデータの型を指定する。 | レスポンスは\ ``ResponseEntity``\ で返却され、型パラメータは引数で指定したレスポンスデータの型となる。 .. note:: \ **RequestEntityとは**\ \ ``RequestEntity``\ はHTTPリクエストを表すクラスで、接続URI、HTTPメソッド、リクエストヘッダ、リクエストボディを設定することができる。 詳細は\ :url_spring_javadoc:`RequestEntityのJavadoc `\ を参照されたい。 | .. _RestClientHowToUseRequestHeaderAccept: Acceptヘッダの設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | サーバから取得するデータの形式を指定する場合は、Acceptヘッダの指定が必要となる。 | なお、サーバが複数のデータ形式のレスポンスをサポートしていない場合は、Acceptヘッダを明示的に指定しなくてもよいケースもある。 | Acceptヘッダをリクエスト送信時に設定するには、以下の通り実装を行う。 \ **Acceptヘッダの設定例**\ .. tabs:: .. group-tab:: RestClient メソッド内部 .. code-block:: java ResponseEntity responseEntity = restClient.get().uri(uri) .accept(MediaType.APPLICATION_JSON) // (1) .retrieve() .toEntity(User.class); User user = responseEntity.getBody(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestBodyUriSpec.accept``\ メソッドを使用して、Acceptヘッダの値を設定する。 | 上記の実装例では、取得可能なデータ形式がJSONであることを示す「\ ``application/json``\ 」を設定している。 .. group-tab:: RestTemplate メソッド内部 .. code-block:: java RequestEntity requestEntity = RequestEntity .get(uri) .accept(MediaType.APPLICATION_JSON) // (1) .build(); // (2) ResponseEntity responseEntity = restTemplate.exchange(requestEntity, User.class); User user = responseEntity.getBody(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestEntity.HeadersBuilder``\ の\ ``accept``\ メソッドを使用して、Acceptヘッダの値を設定する。 | 上記の実装例では、取得可能なデータ形式がJSONであることを示す「\ ``application/json``\ 」を設定している。 * - | (2) - | \ ``RequestEntity.HeadersBuilder``\ の\ ``build``\ メソッドを使用し、\ ``RequestEntity``\ オブジェクトを作成する。 | .. _RestClientHowToUseRequestHeaderAnyHeader: 任意のリクエストヘッダの設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | サーバへアクセスするために、リクエストヘッダの設定が必要になるケースや、カスタムヘッダを付与するケースでは以下の通り設定を行う。 | 以下は、カスタムヘッダの設定を行う例である。 \ **カスタムヘッダの実装例**\ .. tabs:: .. group-tab:: RestClient メソッド内部 .. code-block:: java ResponseEntity responseEntity = restClient.get().uri(uri) .header("Custom-Header", "CustomValue") // (1) .retrieve() .toEntity(User.class); User user = responseEntity.getBody(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestBodyUriSpec.header``\ メソッドを使用してリクエストヘッダの名前と値を設定する。 .. group-tab:: RestTemplate メソッド内部 .. code-block:: java RequestEntity requestEntity = RequestEntity .get(uri) .header("Custom-Header", "CustomValue") // (1) .build(); ResponseEntity responseEntity = restTemplate.exchange(requestEntity, User.class); User user = responseEntity.getBody(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestEntity.HeadersBuilder``\ の\ ``header``\ メソッドを使用してリクエストヘッダの名前と値を設定する。 | .. _RestClientHowToUseAuthorizationHeader: 認証要求の設定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | RESTクライアント側が実装すべき認証要求の方法は、RESTサーバの採用する認証方式に依存するが、本項では認証ヘッダを利用した認証要求の実装方法を説明する。 | 認証ヘッダを利用した認証方式としては、Bearerトークン認証(APIキー認証)やBasic認証などがある。 | APIキー認証に関しては、\ ``X-API-Key``\ 等の独自ヘッダを利用することもあるため、サーバが利用する認証の仕様に応じてヘッダ名を変更する必要がある。 | 以下では、Basic認証を例とした認証ヘッダの設定方法を説明する。 | | 認証ヘッダに関する仕様は、\ :url_rfc:`RFC 9110 `\ に記載されているのでこちらを参照されたい。 .. tabs:: .. group-tab:: RestClient \ **Bean定義ファイルの定義例**\ - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Value("${api.auth.username}") // (1) String username; @Value("${api.auth.password}") // (2) String password; @Bean("restClient") public RestClient restClient() { String base64Credentials = Base64.getEncoder() .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); // (3) return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .defaultHeader("Authorization", "Basic " + base64Credentials) // (4) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | Basic認証で利用するユーザ名を設定する。 * - | (2) - | Basic認証で利用するパスワードを設定する。 * - | (3) - | ユーザ名とパスワードを「”:“ 」でつなげ、バイト配列に変換する。 | バイト配列をJava標準の\ ``java.util.Base64``\ を使用してBase64エンコードを行う。 * - | (4) - | \ ``RestClient.Builder``\ の\ ``defaultHeader``\ メソッドを使用して、AuthorizationヘッダにBasic認証の資格情報を設定する。 | 上記ではBasic認証の例としているため、認証スキームとして\ ``Basic``\ を指定して認証情報を設定している。 .. group-tab:: RestTemplate \ **ClientHttpRequestInitializerの実装例**\ .. code-block:: java // (1) public class CustomHeaderInitializer implements ClientHttpRequestInitializer { private String username; private String password; // (2) public CustomHeaderInitializer(String basicAuthUsername, String basicAuthPassword) { this.username = basicAuthUsername; this.password = basicAuthPassword; } // (3) @Override public void initialize(ClientHttpRequest request) { String plainCredentials = username + ":" + password; // (4) String base64Credentials = Base64.getEncoder().encodeToString(plainCredentials.getBytes(StandardCharsets.UTF_8)); // (5) request.getHeaders().add("Authorization", "Basic " + base64Credentials); // (6) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInitializer``\ の実装クラスを作成する。 * - | (2) - | Basic認証で利用するユーザ名とパスワードを設定するコンストラクタを定義する。 * - | (3) - | \ ``initialize``\ メソッドに\ ``ClientHttpRequest``\ の初期化処理を実装する。 | 上記の例では、リクエストヘッダに\ ``Authorization``\ ヘッダを付与している。 * - | (4) - | ユーザ名とパスワードを「”:“ 」でつなげる。 * - | (5) - | (4)をバイト配列に変換し、Java標準の\ ``java.util.Base64``\ を使用してBase64エンコードを行う。 * - | (6) - | AuthorizationヘッダにBasic認証の資格情報を設定する。 | 上記ではBasic認証の例としているため、認証スキームとして\ ``Basic``\ を指定して認証情報を設定している。 \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Value("${api.auth.username}") private String basicAuthUsername; // (2) @Value("${api.auth.password}") private String basicAuthPassword; // (3) @Bean("customHeaderInitializer") public CustomHeaderInitializer customHeaderInitializer() { return new CustomHeaderInitializer(basicAuthUsername, basicAuthPassword); } @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setClientHttpRequestInitializers(List.of(customHeaderInitializer())); // (4) return restTemplate; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | Basic認証で利用するユーザ名を設定する。 * - | (2) - | Basic認証で利用するパスワードを設定する。 * - | (3) - | \ ``ClientHttpRequestInitializer``\ のBean定義を行う。 | \ ``ClientHttpRequestInitializer``\ の実装クラスのコンストラクタに、Basic認証で利用するユーザ名とパスワードを渡す。 * - | (4) - | \ ``RestTemplate``\ の\ ``setClientHttpRequestInitializers``\ メソッドに\ ``ClientHttpRequestInitializer``\ のBeanを設定する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | Basic認証で利用するユーザ名を設定する。 * - | (2) - | Basic認証で利用するパスワードを設定する。 * - | (3) - | \ ``ClientHttpRequestInitializer``\ のBean定義を行う。 | \ ``ClientHttpRequestInitializer``\ の実装クラスのコンストラクタに、Basic認証で利用するユーザ名とパスワードを渡す。 * - | (4) - | \ ``RestTemplate``\ の\ ``clientHttpRequestInitializers``\ プロパティに\ ``ClientHttpRequestInitializer``\ のBeanを設定する。 .. note:: Spring Security 5より、\ ``org.springframework.security.crypto.codec.Base64``\ は非推奨になったため、Java標準の\ ``java.util.Base64``\ に置き換えることを推奨する。 | .. _RestClientHowToUseTimeoutSettings: 通信タイムアウトの設定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | サーバとの通信に対してタイムアウト時間を指定したい場合は、\ ``ClientHttpRequestFactory``\ の実装クラスを定義し、実装クラスのプロパティ値を設定することで実現することができる。 | 本項では、\ ``ClientHttpRequestFactory``\ の実装の定義と、タイムアウトの設定方法について説明を行う。 | .. _RestClientHowToUseSimpleClientHttpRequestFactoryTimeout: \ ``HttpComponentsClientHttpRequestFactory``\ を使用した通信タイムアウトの設定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | サーバとの通信に対してタイムアウト時間を指定したい場合は、\ ``ClientHttpRequestFactory``\ の実装クラスのプロパティ値を設定することで実現することができる。 | 以下は、\ ``HttpComponentsClientHttpRequestFactory``\ を使用した定義例である。 .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Value("${api.connectTimeout:2000}") private int connectTimeout; // (2) @Value("${api.readTimeout:2000}") private int readTimeout; // omitted @Bean("clientHttpRequestFactory") public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() { // (3) var connectionConfig = ConnectionConfig.custom() .setConnectTimeout(Timeout.ofMilliseconds(connectTimeout)) .build(); // (4) var poolManager = PoolingHttpClientConnectionManagerBuilder.create() .setDefaultConnectionConfig(connectionConfig) .build(); // (5) var httpClient = HttpClientBuilder.create() .setConnectionManager(poolManager) .build(); // (6) var bean = new HttpComponentsClientHttpRequestFactory(httpClient); bean.setReadTimeout(readTimeout); // (7) return bean; } @Bean("timeoutRestClient") public RestClient timeoutRestClient() { return RestClient.builder() .requestFactory(clientHttpRequestFactory()) // (8) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | サーバとの接続タイムアウトの時間(ミリ秒)を設定する。 * - | (2) - | レスポンスデータの読み込みタイムアウトの時間(ミリ秒)を設定する。 * - | (3) - | \ ``org.apache.hc.client5.http.config.ConnectionConfig.custom``\ メソッドにて\ ``Builder``\ を生成する。 | 生成した\ ``Builder``\ の\ :url_http_client_javadoc:`setConnectTimeout `\ メソッドに、(1)の接続タイムアウト時間を設定する。 | \ ``build``\ メソッドにて\ ``ConnectionConfig``\ のインスタンスを生成する。 * - | (4) - | \ ``org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder.create``\ メソッドにて\ ``PoolingHttpClientConnectionManagerBuilder``\ を生成する。 | 生成した\ ``PoolingHttpClientConnectionManagerBuilder``\ の\ ``setDefaultConnectionConfig``\ メソッドに、(3)で生成した\ ``ConnectionConfig``\ のインスタンスを設定する。 | \ ``build``\ メソッドにて\ ``PoolingHttpClientConnectionManager``\ のインスタンスを生成する。 * - | (5) - | \ ``org.apache.hc.client5.http.impl.classic.HttpClientBuilder.create``\ メソッドにて\ ``HttpClientBuilder``\ を生成する。 | 生成した\ ``HttpClientBuilder``\ の\ ``setConnectionManager``\ メソッドに、(4)で生成した\ ``PoolingHttpClientConnectionManager``\ のインスタンスを設定する。 | \ ``build``\ メソッドにて\ ``HttpClient``\ のインスタンスを生成する。 * - | (6) - | \ ``HttpComponentsClientHttpRequestFactory``\ のコンストラクタに、(5)で生成した\ ``HttpClient``\ のインスタンスを設定して、\ ``HttpComponentsClientHttpRequestFactory``\ のインスタンスを生成する。 * - | (7) - | \ ``HttpComponentsClientHttpRequestFactory``\ の\ ``setReadTimeout``\ メソッドに、(2)の読み込みタイムアウト時間を設定する。 | \ ``setReadTimeout``\ メソッドで設定した読み込みタイムアウト時間は、\ ``org.apache.hc.client5.http.config.RequestConfig.Builder``\ の\ :url_http_client_javadoc:`setResponseTimeout `\ に設定される。 * - | (8) - | \ ``RestClient.Builder``\ の\ ``requestFactory``\ メソッドを使用して生成した\ ``HttpComponentsClientHttpRequestFactory``\ を設定する。 | \ ``HttpComponentsClientHttpRequestFactory``\ で設定したタイムアウト値を超えた場合は\ ``org.springframework.web.client.ResourceAccessException``\ が発生する。 .. group-tab:: RestTemplate \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Value("${api.connectTimeout:2000}") private int connectTimeout; // (2) @Value("${api.readTimeout:2000}") private int readTimeout; // omitted @Bean("clientHttpRequestFactory") public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() { // (3) var connectionConfig = ConnectionConfig.custom() .setConnectTimeout(Timeout.ofMilliseconds(connectTimeout)) .build(); // (4) var poolManager = PoolingHttpClientConnectionManagerBuilder.create() .setDefaultConnectionConfig(connectionConfig) .build(); // (5) var httpClient = HttpClientBuilder.create() .setConnectionManager(poolManager) .build(); // (6) var bean = new HttpComponentsClientHttpRequestFactory(httpClient); bean.setReadTimeout(readTimeout); // (7) return bean; } @Bean("timeoutRestTemplate") public RestTemplate timeoutRestTemplate() { return new RestTemplate(clientHttpRequestFactory()); // (8) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | サーバとの接続タイムアウトの時間(ミリ秒)を設定する。 * - | (2) - | レスポンスデータの読み込みタイムアウトの時間(ミリ秒)を設定する。 * - | (3) - | \ ``org.apache.hc.client5.http.config.ConnectionConfig.custom``\ メソッドにて\ ``Builder``\ を生成する。 | 生成した\ ``Builder``\ の\ :url_http_client_javadoc:`setConnectTimeout `\ メソッドに、(1)の接続タイムアウト時間を設定する。 | \ ``build``\ メソッドにて\ ``ConnectionConfig``\ のインスタンスを生成する。 * - | (4) - | \ ``org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder.create``\ メソッドにて\ ``PoolingHttpClientConnectionManagerBuilder``\ を生成する。 | 生成した\ ``PoolingHttpClientConnectionManagerBuilder``\ の\ ``setDefaultConnectionConfig``\ メソッドに、(3)で生成した\ ``ConnectionConfig``\ のインスタンスを設定する。 | \ ``build``\ メソッドにて\ ``PoolingHttpClientConnectionManager``\ のインスタンスを生成する。 * - | (5) - | \ ``org.apache.hc.client5.http.impl.classic.HttpClientBuilder.create``\ メソッドにて\ ``HttpClientBuilder``\ を生成する。 | 生成した\ ``HttpClientBuilder``\ の\ ``setConnectionManager``\ メソッドに、(4)で生成した\ ``PoolingHttpClientConnectionManager``\ のインスタンスを設定する。 | \ ``build``\ メソッドにて\ ``HttpClient``\ のインスタンスを生成する。 * - | (6) - | \ ``HttpComponentsClientHttpRequestFactory``\ のコンストラクタに、(5)で生成した\ ``HttpClient``\ のインスタンスを設定して、\ ``HttpComponentsClientHttpRequestFactory``\ のインスタンスを生成する。 * - | (7) - | \ ``HttpComponentsClientHttpRequestFactory``\ の\ ``setReadTimeout``\ メソッドに、(2)の読み込みタイムアウト時間を設定する。 | \ ``setReadTimeout``\ メソッドで設定した読み込みタイムアウト時間は、\ ``org.apache.hc.client5.http.config.RequestConfig.Builder``\ の\ :url_http_client_javadoc:`setResponseTimeout `\ に設定される。 * - | (8) - | \ ``RestTemplate``\ のコンストラクタに、生成した\ ``HttpComponentsClientHttpRequestFactory``\ を設定する。 | \ ``HttpComponentsClientHttpRequestFactory``\ で設定したタイムアウト値を超えた場合は\ ``org.springframework.web.client.ResourceAccessException``\ が発生する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml // (8) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.apache.hc.client5.http.config.ConnectionConfig.custom``\ メソッドにて\ ``Builder``\ を生成する。 | 生成した\ ``Builder``\ の\ :url_http_client_javadoc:`connectTimeout `\ プロパティに、サーバとの接続タイムアウトの時間(ミリ秒)を設定する。 * - | (2) - | \ ``Builder``\ の\ ``build``\ メソッドにて\ ``ConnectionConfig``\ のインスタンスを生成する。 * - | (3) - | \ ``org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder.create``\ メソッドにて\ ``PoolingHttpClientConnectionManagerBuilder``\ を生成する。 | 生成した\ ``PoolingHttpClientConnectionManagerBuilder``\ の\ ``defaultConnectionConfig``\ プロパティに、(2)で生成した\ ``ConnectionConfig``\ のインスタンスを設定する。 * - | (4) - | \ ``PoolingHttpClientConnectionManagerBuilder``\ の\ ``build``\ メソッドにて\ ``PoolingHttpClientConnectionManager``\ のインスタンスを生成する。 * - | (5) - | \ ``org.apache.hc.client5.http.impl.classic.HttpClientBuilder.create``\ メソッドにて\ ``HttpClientBuilder``\ を生成する。 | 生成した\ ``HttpClientBuilder``\ の\ ``connectionManager``\ プロパティに、(4)で生成した\ ``PoolingHttpClientConnectionManager``\ のインスタンスを設定する。 * - | (6) - | \ ``HttpClientBuilder``\ の\ ``build``\ メソッドにて\ ``HttpClient``\ のインスタンスを生成する。 * - | (7) - | \ ``HttpComponentsClientHttpRequestFactory``\ のコンストラクタに、(6)で生成した\ ``HttpClient``\ のインスタンスを設定して、\ ``HttpComponentsClientHttpRequestFactory``\ のインスタンスを生成する。 | \ ``HttpComponentsClientHttpRequestFactory``\ の\ ``readTimeout``\ プロパティに、レスポンスデータの読み込みタイムアウトの時間(ミリ秒)を設定する。 | \ ``readTimeout``\ プロパティで設定した読み込みタイムアウト時間は、\ ``org.apache.hc.client5.http.config.RequestConfig.Builder``\ の\ :url_http_client_javadoc:`setResponseTimeout `\ に設定される。 * - | (8) - | \ ``RestTemplate``\ のコンストラクタに、生成した\ ``HttpComponentsClientHttpRequestFactory``\ を設定する。 | \ ``HttpComponentsClientHttpRequestFactory``\ で設定したタイムアウト値を超えた場合は\ ``org.springframework.web.client.ResourceAccessException``\ が発生する。 .. note:: \ **タイムアウト発生時の起因例外**\ | \ ``HttpComponentsClientHttpRequestFactory``\ で設定したタイムアウト値を超えた場合は、タイムアウトの種類に関わらず\ ``org.springframework.web.client.ResourceAccessException``\ が発生する。 | したがって、タイムアウトの種類に応じて例外ハンドリングを行いたい場合は、\ ``ResourceAccessException``\ の起因例外の型を確認する必要がある。 | \ ``ResourceAccessException``\ の起因例外として、接続タイムアウト発生時は\ ``org.apache.hc.client5.http.ConnectTimeoutException``\ 、読み込みタイムアウト発生時は\ ``java.net.SocketTimeoutException``\ が設定されているため、これらの型を確認することでタイムアウトの種類を判別することができる。 | なお、上記挙動に関しては使用する\ ``ClientHttpRequestFactory``\ の実装によって異なるため、\ ``ClientHttpRequestFactory``\ が利用するHttpClientの仕様を確認してから実装を行うこと。 | .. _RestClientHowToUseFileUpload: ファイルアップロード(マルチパートリクエスト) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ファイルアップロード(マルチパートリクエスト)を行う場合は、以下のように実装する。 \ **ファイルアップロードの実装例**\ .. tabs:: .. group-tab:: RestClient .. code-block:: java MultiValueMap multiPartBody = new LinkedMultiValueMap<>(); //(1) multiPartBody.add("file", new ClassPathResource("/uploadFiles/User.txt")); //(2) restClient.post().uri(uri) .contentType(MediaType.MULTIPART_FORM_DATA) //(3) .body(multiPartBody) //(4) .retrieve() .body(Void.class); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | マルチパートリクエストとして送信するデータを格納するために\ ``MultiValueMap``\ を生成する。 * - | (2) - | パラメータ名をキーに指定して、アップロードするファイルを\ ``MultiValueMap``\ に追加する。 | 上記例では、\ ``file``\ というパラメータ名を指定して、クラスパス上に配置されているファイルをアップロードファイルとして追加している。 * - | (3) - | Content-Typeヘッダのメディアタイプを\ ``multipart/form-data``\ に設定する。 | \ ``MultiValueMap``\ にString値以外が設定されている場合、Content-Typeヘッダは\ ``FormHttpMessageConverter``\ によって\ ``multipart/form-data``\ が設定される。 | したがって、上記の例では省略しても問題はない。 * - | (4) - | アップロードするファイルが格納されている\ ``MultiValueMap``\ をリクエストボディに設定する。 .. group-tab:: RestTemplate .. code-block:: java MultiValueMap multiPartBody = new LinkedMultiValueMap<>(); //(1) multiPartBody.add("file", new ClassPathResource("/uploadFiles/User.txt")); //(2) RequestEntity> requestEntity = RequestEntity .post(uri) .contentType(MediaType.MULTIPART_FORM_DATA) //(3) .body(multiPartBody); //(4) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | マルチパートリクエストとして送信するデータを格納するために\ ``MultiValueMap``\ を生成する。 * - | (2) - | パラメータ名をキーに指定して、アップロードするファイルを\ ``MultiValueMap``\ に追加する。 | 上記例では、\ ``file``\ というパラメータ名を指定して、クラスパス上に配置されているファイルをアップロードファイルとして追加している。 * - | (3) - | Content-Typeヘッダのメディアタイプを\ ``multipart/form-data``\ に設定する。 | \ ``MultiValueMap``\ にString値以外が設定されている場合、Content-Typeヘッダは\ ``FormHttpMessageConverter``\ によって\ ``multipart/form-data``\ が設定される。 | したがって、上記の例では省略しても問題はない。 * - | (4) - | アップロードするファイルが格納されている\ ``MultiValueMap``\ をリクエストボディに設定する。 .. note:: \ **Spring Frameworkが提供するResourceクラスについて**\ Spring Frameworkはリソースを表現するインタフェースとして\ ``org.springframework.core.io.Resource``\ を提供しており、ファイルをアップロードする際に使用することができる。 \ ``Resource``\ インタフェースの主な実装クラスは以下の通りである。 * \ ``org.springframework.core.io.PathResource``\ * \ ``org.springframework.core.io.FileSystemResource``\ * \ ``org.springframework.core.io.ClassPathResource``\ * \ ``org.springframework.core.io.UrlResource``\ * \ ``org.springframework.core.io.InputStreamResource``\ (ファイル名をサーバに連携できない) * \ ``org.springframework.core.io.ByteArrayResource``\ (ファイル名をサーバに連携できない) | .. _RestClientHowToUseFileDownload: ファイルダウンロード ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ファイルダウンロードの実装方法は、レスポンス取得後処理で\ ``InputStream``\ を使用してレスポンスボディを少しずつファイルに書き出す方法と、\ ``byte``\ 配列でレスポンスボディを一括取得する方法がある。 | \ ``byte``\ 配列で一括取得する方法は、ダウンロードするファイルのサイズによっては、\ ``java.lang.OutOfMemoryError``\ が発生する可能性があるため、基本的には\ ``InputStream``\ を使用した実装を推奨する。 | ダウンロードするファイルのサイズが小さく、メモリに展開しても問題ない場合に限り、\ ``byte``\ 配列で一括取得する実装を検討するとよい。 | \ **InputStreamを使用してファイルに書き出す方法**\ | レスポンス取得後処理にてレスポンスボディを\ ``InputStream``\ で読み込み、読み込んだ内容をファイルに書き出す実装例を示す。 | 以下は、「\ :ref:`RestClientOverviewResponseExtractor`\ 」に記載している\ ``ExchangeFunction``\ や\ ``ResponseExtractor``\ を使用したダウンロードの実装例である。 .. tabs:: .. group-tab:: RestClient インポート宣言 .. code-block:: java import org.springframework.util.FileCopyUtils; メソッド内部 .. code-block:: java File rcvFile = restClient.get().uri(uri).exchange((request, response) -> { // (1) // (2) if (response.getStatusCode().isError()) { throw response.createException(); } File rcvFile = File.createTempFile("rcvFile", "zip"); // (3) FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile)); // (4) return rcvFile; // (5) }); // (6) // omitted .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestBodyUriSpec.exchange``\ メソッドを利用し、レスポンス取得後処理を実装する。 | 引数には\ ``ExchangeFunction``\ が定義されているため、関数にレスポンス取得後処理を実装する。 * - | (2) - | エラー判定及びエラー処理を実装する。 | \ ``ExchangeFunction``\ の引数として定義されている\ ``ConvertibleClientHttpResponse``\ からステータスコードを取得し、クライアントエラー及びサーバエラーの場合、\ ``ConvertibleClientHttpResponse``\ の\ ``createException``\ メソッドを使用して例外をスローする。 | \ ``ConvertibleClientHttpResponse``\ の\ ``createException``\ メソッドを使用することにより、\ ``StatusHandler``\ で生成される例外をスローすることができる。 | | 「\ :ref:`RestClientOverviewResponseExtractor`\ 」にて説明している通り、\ ``ExchangeFunction``\ はエラー判定、エラー処理、レスポンスボディの変換処理を行う必要があるため、\ ``RestTemplate``\ とは異なり、エラー判定及びエラー処理も実装する必要がある。 * - | (3) - | 出力するファイルを生成する。 * - | (4) - | レスポンスボディを\ ``InputStream``\ で読み込み、\ ``FileCopyUtils``\ を使用して、(3)で生成したファイルにレスポンスボディを少しずつ書き出す。 * - | (5) - | ファイル作成後、作成したファイルを返却する。 .. note:: \ **HTTPヘッダや、ステータスコードも返却する場合**\ HTTPヘッダや、ステータスコードも含めて返却したい場合は、 \ ``ResponseEntity``\ に格納して返却すればよい。 .. code-block:: java ResponseEntity responseEntity = restClient.get().uri(uri).exchange((request, response) -> { if (response.getStatusCode().isError()) { throw response.createException(); } File rcvFile = File.createTempFile("rcvFile", "zip"); FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile)); return ResponseEntity.status(response.getStatusCode()) .headers(response.getHeaders()).body(rcvFile); }); * - | (6) - | \ ``RestClient``\ を実行してファイルのダウンロードを行い、出力したファイルを取得する。 .. group-tab:: RestTemplate インポート宣言 .. code-block:: java import org.springframework.util.FileCopyUtils; メソッド内部 .. code-block:: java // (1) final ResponseExtractor responseExtractor = new ResponseExtractor() { // (2) @Override public File extractData(ClientHttpResponse response) throws IOException { File rcvFile = File.createTempFile("rcvFile", "zip"); // (3) FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile)); // (4) return rcvFile; // (5) } }; // (6) File rcvFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor); // omitted .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | レスポンス取得後処理をカスタマイズするため、\ ``ResponseExtractor``\ の実装を作成する。 * - | (2) - | \ ``extractData``\ メソッドを実装し、レスポンス取得後処理を定義する。 * - | (3) - | 出力するファイルを生成する。 * - | (4) - | レスポンスボディを\ ``InputStream``\ で読み込み、\ ``FileCopyUtils``\ を使用して、(3)で生成したファイルにレスポンスボディを少しずつ書き出す。 * - | (5) - | ファイル作成後、作成したファイルを返却する。 .. note:: \ **HTTPヘッダや、ステータスコードも返却する場合**\ HTTPヘッダや、ステータスコードも含めて返却したい場合は、 \ ``ResponseEntity``\ に格納して返却すればよい。 .. code-block:: java final ResponseExtractor> responseExtractor = new ResponseExtractor>() { @Override public ResponseEntity extractData(ClientHttpResponse response) throws IOException { File rcvFile = File.createTempFile("rcvFile", "zip"); FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile)); return ResponseEntity.status(response.getStatusCode()) .headers(response.getHeaders()).body(rcvFile); } }; * - | (6) - | \ ``RestTemplate.execute``\ メソッドを使用してファイルのダウンロードを行い、出力したファイルを取得する。 | \ **byte配列で一括取得する方法**\ | 以下は、レスポンスボディを\ ``byte``\ 配列で一括取得する実装例である。 | ダウンロードするファイルのサイズが小さい場合のみ、以下の方法で実装を行うこと。 .. tabs:: .. group-tab:: RestClient .. code-block:: java byte[] downloadContent = restClient.get().uri(uri) .retrieve() .body(byte[].class); //(1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ダウンロードファイルを指定したデータ型で扱う。 | 上記の例では、ダウンロードしたファイルをバイト配列で取得する。 .. group-tab:: RestTemplate .. code-block:: java byte[] downloadContent = restTemplate.getForObject(uri, byte[].class); //(1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ダウンロードファイルを指定したデータ型で扱う。 | 上記の例では、ダウンロードしたファイルをバイト配列で取得する。 .. warning:: \ **byte配列でファイルをダウンロードする際の注意点**\ | サイズの大きなファイルをデフォルトで登録されている\ ``HttpMessageConverter``\ を使用して \ ``byte``\ 配列で取得すると、 \ ``java.lang.OutOfMemoryError``\ が発生する可能性がある。 | また、\ ``org.springframework.core.io.Resource``\ で取得した場合も、メッセージ変換で利用される\ ``ResourceHttpMessageConverter``\ が\ ``ByteArrayResource``\ を生成する際にレスポンスボディを\ ``byte``\ 配列で取得するため、同様の事象が発生する。 | .. _RestClientHowToExtend: How to extend(同期通信) -------------------------------------------------------------------------------- | 本節では、同期通信実装の拡張方法について説明する。 | 本節の内容は、「:ref:`RestClientHowToUse`」だけでは実現できない動作や設定をカスタマイズしたい場合に必要な内容である。 | | 「:ref:`RestClientComponentsOverview`」にて説明を行った構成要素のカスタマイズ方法は、以下の通りである。 * \ :ref:`RestClientHowToExtendUriBuilderFactory`\ * \ :ref:`RestClientHowToExtendClientHttpRequestFactory`\ * \ :ref:`RestClientHowToExtendHttpMessageConverter`\ * \ :ref:`RestClientHowToExtendClientHttpRequestInterceptor`\ | .. _RestClientHowToExtendUriBuilderFactory: URL生成処理をカスタマイズする方法(\ ``UriBuilderFactory``\ ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | RESTサーバが公開するAPIには、一定のパターンに従うプレースホルダを含んでいる物が存在することが想定される。(例えば、日付を含むURLなど)このような場合、RESTクライアント側では、リクエスト送信先のURLを生成する処理をカスタマイズして、サーバが期待する形式のURLを生成できると便利である。 | 以下では、RESTサーバ側が期待するURLに特定形式でフォーマットされた日付が含まれる場合を例にとり、RESTクライアントでリクエスト先URLの生成処理をカスタマイズして、RESTサーバが期待するURLをRESTクライアントが生成できるようにする例を示す。 | RESTクライアントでのリクエスト先URLの生成処理のカスタマイズは \ ``UriBuilderFactory``\ および\ ``UriBuilder``\ の実装を拡張するすることで実現する。 | URL生成処理のカスタマイズが、ある特定のリクエスト送信処理のみだけに限定される場合は、 \ ``UriBuilderFactory``\ や\ ``UriBuilder``\ を利用せず、個別のリクエスト送信処理の中でURLをカスタマイズすることも可能だが、そうでない場合は、\ ``UriBuilderFactory``\ や\ ``UriBuilder``\ を利用することで実装の重複を避け、コードのメンテンス性を落とさずにカスタマイズが可能である。 | なお、 \ ``UriBuilderFactory``\ および\ ``UriBuilder``\ は日付の利用に関係なく、リクエスト先URLの生成処理一般のためのカスタマイズポイントとして機能する。 | .. _RestClientHowToExtendUriBuilderFactoryFormatVariable: URIテンプレート変数値のフォーマット変換 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 以下は、URIテンプレート変数の値(\ ``LocalDate``\ )を、特定のフォーマット文字列に変換する\ ``UriBuilderFactory``\ の実装例である。 * URIテンプレート:\ ``http://localhost:8080/api/order/{date}``\ * URIテンプレート変数の値:\ ``LocalDate.of(2025, 1, 1);``\ * 送信時のURL:\ ``http://localhost:8080/api/order/20250101``\ \ **UriBuilderFactoryの拡張**\ .. code-block:: java // (1) public class CustomUriBuilderFactory extends DefaultUriBuilderFactory { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); // Default Constructor public CustomUriBuilderFactory() { super(); } // Constructor with baseUri public CustomUriBuilderFactory(String baseUriTemplate) { super(baseUriTemplate); } // Constructor with UriComponentsBuilder public CustomUriBuilderFactory(UriComponentsBuilder baseUri) { super(baseUri); } // (2) @Override public UriBuilder uriString(String uriTemplate) { return new CustomUriBuilder(super.uriString(uriTemplate)); } // (2) @Override public UriBuilder builder() { return new CustomUriBuilder(super.builder()); } // (3) private class CustomUriBuilder implements UriBuilder { private final UriBuilder delegate; public CustomUriBuilder(UriBuilder delegate) { this.delegate = delegate; } // (4) @Override public URI build(Map uriVariables) { Map convertedUriVariables = uriVariables.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, this::convert)); return delegate.build(convertedUriVariables); } @Override public URI build(Object... uriVariables) { Object[] convertedUriVariables = Arrays.stream(uriVariables) .map(this::convert) .toArray(); return delegate.build(convertedUriVariables); } // (5) private Object convert(Object value) { if(value instanceof LocalDate date) { return FORMATTER.format(date); } return value; } // Delegate all other methods to the original UriBuilder // (6) @Override public UriBuilder scheme(String scheme) { return delegate.scheme(scheme); } @Override public UriBuilder userInfo(String userInfo) { return delegate.userInfo(userInfo); } // ... other methods from UriBuilder } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``UriBuilderFactory``\ の実装クラスとして、\ ``CustomUriBuilderFactory``\ を生成する。 | 上記の例では、\ ``DefaultUriBuilderFactory``\ を拡張した実装クラスを作成して、必要なメソッドのみをオーバーライドしている。 | コンストラクタに関しては、\ ``DefaultUriBuilderFactory``\ と同様のコンストラクタを用意している。 * - | (2) - | \ ``UriBuilder``\ を生成するメソッドをオーバーライドして、独自の\ ``UriBuilder``\ を生成するよう実装を行う。 | 変更箇所以外は\ ``DefaultUriBuilder``\ の実装をそのまま使用するため、\ ``CustomUriBuilder``\ のコンストラクタ引数にて\ ``DefaultUriBuilder``\ を引き渡すようにしている。 * - | (3) - | \ ``UriBuilder``\ の実装クラスとして\ ``CustomUriBuilder``\ を生成する。 * - | (4) - | \ ``build``\ メソッドにパラメータの変換処理を作成する。 | 上記の例では、URIテンプレート変数の値が設定されているコレクションを受け取り、全ての値に対して変換処理を行い返却を行っている。 | なお、カスタマイズした変換処理以外は\ ``DefaultUriBuilder``\ の実装をそのまま使用するため、\ ``DefaultUriBuilder``\ の\ ``build``\ メソッドを最後に実行している。 * - | (5) - | (4)の各要素で呼び出す変換処理を実装する。 | 上記の例では、\ ``LocalDate``\ 型の場合のみ、指定したフォーマット文字列に変換して返却するようにしている。 * - | (6) - | 変換処理以外のメソッドは、\ ``DefaultUriBuilder``\ の実装をそのまま使用するため、\ ``DefaultUriBuilder``\ のメソッドをそのまま呼び出すように実装を行う。 | \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("customUriBuilderFactory") public CustomUriBuilderFactory customUriBuilderFactory() { return new CustomUriBuilderFactory(); } @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .uriBuilderFactory(customUriBuilderFactory()) // (2) .build(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``CustomUriBuilderFactory``\ のBeanを定義する。 * - | (2) - | \ ``RestClient.Builder``\ の\ ``uriBuilderFactory``\ メソッドに、\ ``CustomUriBuilderFactory``\ のBeanを設定する。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("customUriBuilderFactory") public CustomUriBuilderFactory customUriBuilderFactory() { return new CustomUriBuilderFactory(); } @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setUriTemplateHandler(customUriBuilderFactory()); // (2) return restTemplate; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``CustomUriBuilderFactory``\ のBeanを定義する。 * - | (2) - | \ ``RestTemplate``\ の\ ``setUriTemplateHandler``\ メソッドに、\ ``CustomUriBuilderFactory``\ のBeanを設定する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``CustomUriBuilderFactory``\ のBeanを定義する。 * - | (2) - | \ ``RestTemplate``\ の\ ``uriTemplateHandler``\ プロパティに、\ ``CustomUriBuilderFactory``\ のBeanを設定する。 | .. _RestClientHowToExtendClientHttpRequestFactory: リクエスト生成処理のカスタマイズ方法(\ ``ClientHttpRequestFactory``\ ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``ClientHttpRequestFactory``\ を使用したリクエスト生成処理のカスタマイズ方法について説明する。 | タイムアウトの設定については、「\ :ref:`RestClientHowToUseTimeoutSettings`\ 」にて説明を行っているのでこちらを参照されたい。 | .. _RestClientHowToUseHttps: カスタマイズしたキーストアファイルの利用(SSL/TLS) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | JDKのデフォルトのキーストアファイルでは信頼できると見なせないサーバ証明書を返すサーバと通信する場合等、使用するキーストアファイルを変更したいケースがある。 | ここでは、その一例として、SSL自己署名証明書を使用するケースを想定して説明する。 | テスト環境などでSSL自己署名証明書を使用する場合は、以下のように実装する。 \ **FactoryBeanの実装例**\ | \ ``RestClient``\ / \ ``RestTemplate``\ に設定する\ ``ClientHttpRequestFactory``\ を\ ``org.springframework.beans.factory.FactoryBean``\ にて実装する。 | サンプルコードであるため、\ ``HttpClientConnectionManager``\ や\ ``HttpClient``\ の設定値は業務要件に応じ適切に設定されたい。 .. code-block:: java import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.cookie.StandardCookieSpec; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.ssl.TLS; import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.pool.PoolReusePolicy; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; public class RequestFactoryBean implements FactoryBean, DisposableBean { // (18) private String keyStoreFileName; private char[] keyStorePassword; private HttpComponentsClientHttpRequestFactory factory; // (18) @Override public ClientHttpRequestFactory getObject() throws Exception { // (1) SSLContext sslContext = SSLContext.getInstance("TLS"); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(this.getClass().getClassLoader().getResourceAsStream( this.keyStoreFileName), this.keyStorePassword); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(ks, this.keyStorePassword); TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ks); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); // @formatter:off PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() // (2) .setTlsSocketStrategy(new DefaultClientTlsStrategy(sslContext)) // (3) .setDefaultTlsConfig( // (4) TlsConfig.custom() .setSupportedProtocols(TLS.V_1_3, TLS.V_1_2) .setHandshakeTimeout(Timeout.ofMilliseconds(1L)) .build()) .setDefaultSocketConfig( // (5) SocketConfig.custom() .setSoTimeout(Timeout.ofMinutes(1L)) .build()) .setMaxConnTotal(1) // (6) .setMaxConnPerRoute(1) // (7) .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT) // (8) .setConnPoolPolicy(PoolReusePolicy.LIFO) // (9) .setDefaultConnectionConfig( // (10) ConnectionConfig.custom() .setTimeToLive(TimeValue.ofMinutes(1L)) // (11) .setConnectTimeout(Timeout.ofSeconds(5L)) // (12) .build()) .build(); // @formatter:on // @formatter:off CloseableHttpClient httpClient = HttpClients.custom() // (13) .setConnectionManager(connectionManager) // (14) .setDefaultRequestConfig( RequestConfig.custom() .setResponseTimeout(Timeout.ofSeconds(10L)) // (15) .setCookieSpec(StandardCookieSpec.STRICT) // (16) .build()) .build(); // @formatter:on // (17) this.factory = new HttpComponentsClientHttpRequestFactory(httpClient); return this.factory; } @Override public Class getObjectType() { return ClientHttpRequestFactory.class; } @Override public boolean isSingleton() { return true; } public void setKeyStoreFileName(String keyStoreFileName) { this.keyStoreFileName = keyStoreFileName; } public void setKeyStorePassword(char[] keyStorePassword) { this.keyStorePassword = keyStorePassword; } // (18) @Override public void destroy() throws Exception { if (this.factory != null) { this.factory.destroy(); } } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 後述のBean定義で指定されたキーストアファイルのファイル名とパスワードを元に、SSLコンテキストを作成する。 | 使用するSSL自己署名証明書のキーストアファイルは、クラスパス上に配置する。 * - | (2) - | (1)で作成したSSLコンテキストを設定するための\ :url_http_client_javadoc:`HttpClientConnectionManager `\ を作成する。 | ここでは、実装クラスとして\ ``org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager``\ を指定している。 * - | (3) - | (1)で作成したSSLコンテキストを設定する。 * - | (4) - | Tls Configのデフォルト値を設定する。 | \ ``Socket``\ から提供される\ ``InputStream``\ の\ ``read()``\ のブロック時間が\ :url_http_client_javadoc:`HandshakeTimeout `\ を越えた場合、通常、\ ``java.net.SocketTimeoutException``\ が発生するが、TCPコネクション確立後、SSLハンドシェイクを行っている間にこのタイムアウトが起きた場合には、\ ``org.apache.hc.client5.http.ConnectTimeoutException``\ が発生する。 .. note:: \ **HandshakeTimeoutはSSLハンドシェイクが完了する十分な長さを確保すること。**\ SSLハンドシェイク完了前に\ ``HandshakeTimeout``\ によってタイムアウトした際に、SSLハンドシェイクが中断される(SSLハンドシェイクに関する続きの通信を行わなくなる)がTCPコネクションのcloseも行われない事象を確認している。この状態になると、サーバ側で処理を破棄しない限りサーバ側はSSLハンドシェイクの続きを待つこととなってしまう。 そのため、SSLハンドシェイク中に通信障害や通信相手のハードウェア障害等に遭遇し通信相手からのパケットが届かない時間が長く続く場合(クライアント側で通信の継続を諦めなければならない場合)以外においては、\ ``HandshakeTimeout``\ を利用したタイムアウトは避けたほうが良い。 SSLハンドシェイク完了後のHTTPSリクエストに対するレスポンスのタイムアウトには、後述の\ ``ResponseTimeout``\ が利用できるため、 * HTTPSリクエストに対する応答に関するタイムアウトには\ ``ResponseTimeout``\ * \ ``ResponseTimeout``\ の有効範囲外(HTTPSリクエスト送信前)となるSSLハンドシェイク中の応答に関するタイムアウトには\ ``HandshakeTimeout``\ (正常稼働時にはタイムアウトしないよう長めに設定) という具合に使い分けること。なお、\ ``HandshakeTimeout``\ が未設定の場合は\ :url_http_client_javadoc:`ConnectTimeout `\ が設定されている。 * - | (5) - | Socket Configのデフォルト値を設定する。 .. note:: \ **SoTimeoutの設定について**\ \ ``SocketConfig.Builder``\ の\ :url_http_core:`SoTimeout `\ は、Apache HttpComponents HttpClient 5.5まではSSLハンドシェイクのタイムアウト値にも適用されていた。 しかしながら、バージョン5.5.1からはSSLハンドシェイクのタイムアウト値は、\ ``TlsConfig.Builder``\ の\ ``HandshakeTimeout``\ の設定を利用するように変更された。 そのため、SSLハンドシェイクのタイムアウト設定を行う場合は、(4)の\ ``HandshakeTimeout``\ に設定を行う必要がある。 * - | (6) - | 最大合計接続数を設定する。 | 最大合計接続数を超える場合、後続処理はコネクションの取得を待機する。 * - | (7) - | 宛先(ホスト名 + ポート番号 及び スキーマ定義)ごとの最大接続数を設定する。 | 最大接続数を超える場合、後続処理はコネクションの取得を待機する。 * - | (8) - | \ :url_http_core:`プール同時実行ポリシー `\ を設定する。 * - | (9) - | \ :url_http_core:`プールされたコネクションの再利用ポリシー `\ を設定する。 * - | (10) - | コネクションに関連するデフォルト値を設定する。 * - | (11) - | コネクションが接続されてから切断されるまでの最大生存時間を設定する。 | 使用状況にかかわらず、\ :url_http_client_javadoc:`ConnectionTimeToLive `\ を越えた場合にコネクションを破棄する。 * - | (12) - | 新しい接続が確立されるまでのタイムアウトのデフォルト値を設定する。 | 接続の確立に\ :url_http_client_javadoc:`ConnectTimeout `\ 以上かかった場合、\ ``org.apache.hc.client5.http.HttpHostConnectException``\ が発生する。 * - | (13) - | 作成したSSLコンテキストを利用する \ ``org.apache.hc.client5.http.impl.classic.CloseableHttpClient``\ を作成する。 * - | (14) - | (2)で作成した\ ``HttpClientConnectionManager``\ を設定する。 * - | (15) - | レスポンスタイムアウトのデフォルト値を設定する。 | レスポンス返却に\ :url_http_client_javadoc:`ResponseTimeout `\ 以上かかった場合、\ ``java.net.SocketTimeoutException``\ が発生する。 * - | (16) - | \ :url_http_client_javadoc:`HttpClientがサポートするCookieの仕様 `\ を設定する。 * - | (17) - | 作成した\ ``HttpClient``\ を利用する\ ``ClientHttpRequestFactory``\ を作成する。 * - | (18) - | \ ``FactoryBean``\ で生成したオブジェクトのライフサイクルはDIコンテナで管理されないため、破棄時に特定の処理を実行するには\ ``FactoryBean``\ に\ ``DisposableBean``\ インタフェースの\ ``destroy``\ メソッドを実装する必要がある。 | ここでは、\ ``getObject``\ メソッドで生成した \ ``HttpComponentsClientHttpRequestFactory``\ の\ ``destroy``\ メソッドを呼び出し、\ ``HttpClient``\ をクローズしている。このため、生成したオブジェクトを変数に保持している。 .. note:: 通信先のアプリケーションがTLS 1.2以前のバージョンにしか対応していない等の理由により、使用するTLSのバージョンをJVMレベルで変更するには\ :ref:`support-tls1.3-by-default-from-java11`\ を参照されたい。 ただし、JVMレベルで設定してしまうと一つのクライアントアプリからTLS 1.2とTLS 1.3を利用した別々のアプリケーションに接続するような要件を実現することができない。このような場合は、\ ``HttpClient``\ ごとに利用するTLSのバージョンを指定するような実装を検討されたい。 | \ **Bean定義ファイルの定義例**\ SSL自己署名証明書を使用したSSL通信を行う\ ``ClientHttpRequestFactory``\ を、\ ``RestClient``\ / \ ``RestTemplate``\ に設定する。 .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Value("${rscl.keystore.filename}") private String keystoreFilename; @Value("${rscl.keystore.password}") private String keystorePassword; // omitted @Bean("httpsRestClient") public RestClient httpsRestClient() throws Exception { return RestClient.builder() .requestFactory(httpsRequestFactoryBean().getObject()) // (1) .build(); } // (1) @Bean("httpsRequestFactoryBean") public RequestFactoryBean httpsRequestFactoryBean() { var factory = new RequestFactoryBean(); factory.setKeyStoreFileName(keystoreFilename); factory.setKeyStorePassword(keystorePassword.toCharArray()); return factory; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 作成した\ ``RequestFactoryBean``\ を\ ``RestClient.Builder``\ の\ ``requestFactory``\ メソッドに指定する。 | \ ``RequestFactoryBean``\ には、キーストアファイルのファイル名とパスワードを渡す。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Value("${rscl.keystore.filename}") private String keystoreFilename; @Value("${rscl.keystore.password}") private String keystorePassword; // omitted @Bean("httpsRestTemplate") public RestTemplate httpsRestTemplate() throws Exception { return new RestTemplate(httpsRequestFactoryBean().getObject()); // (1) } // (1) @Bean("httpsRequestFactoryBean") public RequestFactoryBean httpsRequestFactoryBean() { var factory = new RequestFactoryBean(); factory.setKeyStoreFileName(keystoreFilename); factory.setKeyStorePassword(keystorePassword.toCharArray()); return factory; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 作成した\ ``RequestFactoryBean``\ を\ ``RestTemplate``\ のコンストラクタに指定する。 | \ ``RequestFactoryBean``\ には、キーストアファイルのファイル名とパスワードを渡す。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 作成した\ ``RequestFactoryBean``\ を\ ``RestTemplate``\ のコンストラクタに指定する。 | \ ``RequestFactoryBean``\ には、キーストアファイルのファイル名とパスワードを渡す。 \ **RESTクライアントの使用方法**\ \ ``RestClient``\ / \ ``RestTemplate``\ の使い方については、SSL自己署名証明書を使用しない場合と同様であるため、以下を参照されたい。 * \ :ref:`RestClientHowToUseGet`\ * \ :ref:`RestClientHowToUsePost`\ | .. _RestClientProxySettings: HTTP Proxyサーバの設定方法 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | サーバへアクセスする際にHTTP Proxyサーバを経由する必要がある場合は、システムプロパティやJVM起動引数、または\ ``RestClient``\ / \ ``RestTemplate``\ のBean定義にてHTTP Proxyサーバの設定が必要である。 | システムプロパティやJVM起動引数に設定した場合、アプリケーション全体に影響を与えてしまうため、\ ``RestClient``\ / \ ``RestTemplate``\ 毎にHTTP Proxyサーバの設定を行う例を紹介する。 | HTTP Proxyサーバの設定は、\ ``ClientHttpRequestFactory``\ インタフェースの実装クラスである\ ``HttpComponentsClientHttpRequestFactory``\ を利用して設定を行う。 | 本項では、Proxy認証を行わない場合と、Proxy認証を行う場合の2通りの設定方法について説明する。 | Proxy認証を行わない場合の設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 資格情報が不要なHTTP Proxyサーバの接続先の指定については、以下の通り実装を行う。 \ **Bean定義ファイル**\ .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (2) @Value("${rscl.http.proxyHost}") private String httpProxyHost; // (3) @Value("${rscl.http.proxyPort}") private int httpProxyPort; // (1) @Bean public HttpHost httpHost() { return new HttpHost(httpProxyHost, httpProxyPort); // (2) (3) } // (4) @Bean("proxyHttpClientBuilder") public HttpClientBuilder proxyHttpClientBuilder() { HttpClientBuilder bean = HttpClientBuilder.create(); bean.setProxy(httpHost()); // (5) return bean; } // (6) @Bean("httpComponentsClientHttpRequestFactory") public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() { return new HttpComponentsClientHttpRequestFactory(proxyHttpClientBuilder().build()); // (7) } // (8) @Bean("proxyRestClient") public RestClient proxyRestClient() { return RestClient.builder() .requestFactory(httpComponentsClientHttpRequestFactory()) // (9) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.apache.hc.core5.http.HttpHost``\ にHTTP Proxyサーバの設定を行う。 * - | (2) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``hostname``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyHost``\ の値をHTTP Proxyサーバのホスト名として設定する。 * - | (3) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``port``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyPort``\ の値をHTTP Proxyサーバのポート番号として設定する。 * - | (4) - | \ ``org.apache.hc.client5.http.impl.classic.HttpClientBuilder``\ を使用し、\ ``org.apache.hc.client5.http.classic.HttpClient``\ の設定を行う。 * - | (5) - | \ ``HttpClientBuilder``\ の\ ``setProxy``\ メソッドに、\ HTTP Proxyサーバの設定を行った\ ``org.apache.hc.core5.http.HttpHost``\ を設定する。 * - | (6) - | \ ``HttpComponentsClientHttpRequestFactory``\ にプロキシ設定を行った\ ``HttpClient``\ を設定する。 * - | (7) - | \ ``HttpComponentsClientHttpRequestFactory``\ のコンストラクタの引数に、\ ``HttpClientBuilder``\ から生成した\ ``HttpClient``\ を設定する。 * - | (8) - | \ ``RestClient``\ のBean定義を行う。 * - | (9) - | \ ``RestClient.Builder``\ の\ ``requestFactory``\ メソッドを使用して、(6)で生成した\ ``HttpComponentsClientHttpRequestFactory``\ を設定する。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (2) @Value("${rscl.http.proxyHost}") private String httpProxyHost; // (3) @Value("${rscl.http.proxyPort}") private int httpProxyPort; // (1) @Bean public HttpHost httpHost() { return new HttpHost(httpProxyHost, httpProxyPort); // (2) (3) } // (4) @Bean("proxyHttpClientBuilder") public HttpClientBuilder proxyHttpClientBuilder() { HttpClientBuilder bean = HttpClientBuilder.create(); bean.setProxy(httpHost()); // (5) return bean; } // (6) @Bean("httpComponentsClientHttpRequestFactory") public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() { return new HttpComponentsClientHttpRequestFactory(proxyHttpClientBuilder().build()); // (7) } // (8) @Bean("proxyRestTemplate") public RestTemplate proxyRestTemplate() { return new RestTemplate(httpComponentsClientHttpRequestFactory()); // (9) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.apache.hc.core5.http.HttpHost``\ にHTTP Proxyサーバの設定を行う。 * - | (2) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``hostname``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyHost``\ の値をHTTP Proxyサーバのホスト名として設定する。 * - | (3) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``port``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyPort``\ の値をHTTP Proxyサーバのポート番号として設定する。 * - | (4) - | \ ``org.apache.hc.client5.http.impl.classic.HttpClientBuilder``\ を使用し、\ ``org.apache.hc.client5.http.classic.HttpClient``\ の設定を行う。 * - | (5) - | \ ``HttpClientBuilder``\ の\ ``setProxy``\ メソッドに、\ HTTP Proxyサーバの設定を行った\ ``org.apache.hc.core5.http.HttpHost``\ を設定する。 * - | (6) - | \ ``HttpComponentsClientHttpRequestFactory``\ にプロキシ設定を行った\ ``HttpClient``\ を設定する。 * - | (7) - | \ ``HttpComponentsClientHttpRequestFactory``\ のコンストラクタの引数に、\ ``HttpClientBuilder``\ から生成した\ ``HttpClient``\ を設定する。 * - | (8) - | \ ``RestTemplate``\ のBean定義を行う。 * - | (9) - | \ ``RestTemplate``\ のコンストラクタの引数に、(6)で生成した\ ``HttpComponentsClientHttpRequestFactory``\ を設定する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.apache.hc.core5.http.HttpHost``\ にHTTP Proxyサーバの設定を行う。 * - | (2) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``hostname``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyHost``\ の値をHTTP Proxyサーバのホスト名として設定する。 * - | (3) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``port``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyPort``\ の値をHTTP Proxyサーバのポート番号として設定する。 * - | (4) - | \ ``org.apache.hc.client5.http.impl.classic.HttpClientBuilder``\ を使用し、\ ``org.apache.hc.client5.http.classic.HttpClient``\ の設定を行う。 * - | (5) - | \ ``HttpClientBuilder``\ の\ ``proxy``\ プロパティに、\ HTTP Proxyサーバの設定を行った\ ``org.apache.hc.core5.http.HttpHost``\ を設定する。 .. warning:: \ **オーバーロードされている場合に誤ったコンストラクタが使用されることがある**\ .. TODO : 当問題に関しては https://github.com/spring-projects/spring-framework/issues/31871 で問題提起しており、Springの回答次第では記載内容を見直す必要がある 当実装例は、本来であれば以下の様に記述することが可能である。 .. code-block:: xml ただし、\ :url_spring_framework_issues:`Spring#31871 `\ で報告されているように、コンストラクタがオーバーロードされている場合に想定とは異なるコンストラクタが使用されてしまう可能性がある。 そのためtype属性を設定し、明示的に使用するコンストラクタを決定している。 * - | (6) - | \ ``HttpComponentsClientHttpRequestFactory``\ にプロキシ設定を行った\ ``HttpClient``\ を設定する。 * - | (7) - | \ ``HttpComponentsClientHttpRequestFactory``\ のコンストラクタの引数に、\ ``HttpClientBuilder``\ から生成した\ ``HttpClient``\ を設定する。 * - | (8) - | \ ``RestTemplate``\ のBean定義を行う。 * - | (9) - | \ ``RestTemplate``\ のコンストラクタの引数に、(6)で生成した\ ``HttpComponentsClientHttpRequestFactory``\ を設定する。 | Proxy認証を行う場合の設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | HTTP Proxyサーバにアクセスする際に資格情報(ユーザ名とパスワード)が必要な場合は、\ ``org.apache.http.impl.client.BasicCredentialsProvider``\ を使用し資格情報を設定する。 | \ ``BasicCredentialsProvider``\ の\ ``setCredentials``\ メソッドが引数を2つ取るため、セッターインジェクションを利用してBeanを生成することができない。 | このため、\ ``org.springframework.beans.factory.FactoryBean``\ を利用してBeanを生成する。 \ **FactoryBeanクラス**\ .. code-block:: java import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Value; // (1) public class BasicCredentialsProviderFactoryBean implements FactoryBean { // (2) @Value("${rscl.http.proxyHost}") String host; // (3) @Value("${rscl.http.proxyPort}") int port; // (4) @Value("${rscl.http.proxyUserName}") String userName; // (5) @Value("${rscl.http.proxyPassword}") String password; @Override public BasicCredentialsProvider getObject() throws Exception { // (6) var authScope = new AuthScope(this.host, this.port); // (7) char[] passwordCharArray = this.password == null ? null : this.password.toCharArray(); var usernamePasswordCredentials = new UsernamePasswordCredentials(this.userName, passwordCharArray); // (8) var credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(authScope, usernamePasswordCredentials); return credentialsProvider; } @Override public Class getObjectType() { return BasicCredentialsProvider.class; } @Override public boolean isSingleton() { return true; } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.springframework.beans.factory.FactoryBean``\ を実装した\ ``BasicCredentialsProviderFactoryBean``\ クラスを定義する。 | Beanの型に\ ``BasicCredentialsProvider``\ を設定する。 * - | (2) - | プロパティファイルに設定されたキー\ ``rscl.http.proxyHost``\ の値をHTTP Proxyサーバのホスト名として、インスタンス変数に設定する。 * - | (3) - | プロパティファイルに設定されたキー\ ``rscl.http.proxyPort``\ の値をHTTP Proxyサーバのポート番号として、インスタンス変数に設定する。 * - | (4) - | プロパティファイルに設定されたキー\ ``rscl.http.proxyUserName``\ の値をHTTP Proxyサーバのユーザ名として、インスタンス変数に設定する。 * - | (5) - | プロパティファイルに設定されたキー\ ``rscl.http.proxyPassword``\ の値をHTTP Proxyサーバのパスワードとして、インスタンス変数に設定する。 * - | (6) - | \ ``org.apache.http.auth.AuthScope`` \ を作成し資格情報のスコープを設定する。この例は、HTTP Proxyサーバのホスト名とポート番号を指定したものである。その他の設定方法については、\ :url_http_client:`AuthScope (Apache HttpClient API) `\ を参照されたい。 * - | (7) - | \ ``org.apache.http.auth.UsernamePasswordCredentials``\ を作成し資格情報を設定する。 * - | (8) - | \ ``org.apache.http.impl.client.BasicCredentialsProvider``\ を作成し、\ ``setCredentials``\ メソッドを使用し、資格情報のスコープと資格情報を設定する。 \ **Bean定義ファイル**\ .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean public BasicCredentialsProviderFactoryBean basicCredentialsProviderFactoryBean() { return new BasicCredentialsProviderFactoryBean(); } @Bean public HttpHost httpHost() { return new HttpHost(httpProxyHost, httpProxyPort); } @Bean("proxyHttpClientBuilder") public HttpClientBuilder proxyHttpClientBuilder() throws Exception { HttpClientBuilder bean = HttpClientBuilder.create(); bean.setDefaultCredentialsProvider(basicCredentialsProviderFactoryBean().getObject()); // (1) bean.setProxy(httpHost()); return bean; } @Bean("httpComponentsClientHttpRequestFactory") public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() { return new HttpComponentsClientHttpRequestFactory(proxyHttpClientBuilder() .build()); } @Bean("proxyRestClient") public RestClient proxyRestClient() { return RestClient.builder() .requestFactory(httpComponentsClientHttpRequestFactory()) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HttpClientBuilder``\ の\ ``setDefaultCredentialsProvider``\ メソッドに、\ ``BasicCredentialsProvider``\ を設定する。 | \ ``BasicCredentialsProvider``\ は、\ ``FactoryBean``\ を実装した\ ``BasicCredentialsProviderFactoryBean``\ を使用しBeanを作成する。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean public BasicCredentialsProviderFactoryBean basicCredentialsProviderFactoryBean() { return new BasicCredentialsProviderFactoryBean(); } @Bean public HttpHost httpHost() { return new HttpHost(httpProxyHost, httpProxyPort); } @Bean("proxyHttpClientBuilder") public HttpClientBuilder proxyHttpClientBuilder() throws Exception { HttpClientBuilder bean = HttpClientBuilder.create(); bean.setDefaultCredentialsProvider(basicCredentialsProviderFactoryBean().getObject()); // (1) bean.setProxy(httpHost()); return bean; } @Bean("httpComponentsClientHttpRequestFactory") public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() { return new HttpComponentsClientHttpRequestFactory(proxyHttpClientBuilder() .build()); } @Bean("proxyRestTemplate") public RestTemplate proxyRestTemplate() { return new RestTemplate(httpComponentsClientHttpRequestFactory()); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HttpClientBuilder``\ の\ ``setDefaultCredentialsProvider``\ メソッドに、\ ``BasicCredentialsProvider``\ を設定する。 | \ ``BasicCredentialsProvider``\ は、\ ``FactoryBean``\ を実装した\ ``BasicCredentialsProviderFactoryBean``\ を使用しBeanを作成する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HttpClientBuilder``\ の\ ``defaultCredentialsProvider``\ プロパティに、\ ``BasicCredentialsProvider``\ を設定する。 | \ ``BasicCredentialsProvider``\ は、\ ``FactoryBean``\ を実装した\ ``BasicCredentialsProviderFactoryBean``\ を使用しBeanを作成する。 | .. _RestClientHowToExtendHttpMessageConverter: メッセージ変換処理の設定(\ ``HttpMessageConverter``\ ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ カスタムメッセージコンバータの登録 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | デフォルトで登録されている \ ``HttpMessageConverter``\ で電文変換の要件を満たせない場合は、カスタマイズした \ ``HttpMessageConverter``\ を作成、登録することで要件に対応する事ができる。 | ただし、カスタマイズしたメッセージコンバータを登録すると、デフォルトで利用されていた \ ``HttpMessageConverter``\ は利用されなくなる。このため、カスタムメッセージコンバータ以外にも、要件を満たすためのメッセージコンバータが必要な場合は、必要な\ ``HttpMessageConverter``\ 実装をすべて登録する必要がある。 | カスタムメッセージコンバータを利用するには以下のように実装する。 \ **カスタムメッセージコンバータの作成**\ .. code-block:: java import java.io.IOException; import java.lang.reflect.Type; import org.jspecify.annotations.Nullable; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.converter.AbstractGenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; // (1) public class CustomHttpMessageConverter extends AbstractGenericHttpMessageConverter { public CustomHttpMessageConverter() { // (2) super(new MediaType("text", "custom-format"), new MediaType("application", "custom-format")); } // (3) @Override protected void writeInternal(Object t, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // omitted } // (3) @Override protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { // omitted } // (3) @Override public Object read(Type type, @Nullable Class contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { // omitted } // omitted } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.springframework.http.converter.AbstractGenericHttpMessageConverter``\ を継承した実装クラスを作成する。 | \ ``AbstractGenericHttpMessageConverter``\ は\ ``GenericHttpMessageConverter``\ インターフェースを実装した抽象クラスであり、「\ :ref:`RestClientHowToUseGetCollection`\ 」で説明した\ ``ParameterizedTypeReference``\ を利用したコレクション型を扱うことができる。 * - | (2) - | 本コンバータが対応する\ ``MediaType``\ を設定する。 | なお、複数の\ ``MediaType``\ に対応する場合は、配列で複数指定する必要がある。 * - | (3) - | 要件にあわせて変換処理を実装する。 | メソッドの説明は、\ :url_spring_javadoc:`AbstractGenericHttpMessageConverterのJavadoc `\ を参照されたい。 .. Spring7.0対応:HttpMessageConverters.ClientBuilderを使ってConverterを登録するように変更された \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("customHttpMessageConverter") public CustomHttpMessageConverter customHttpMessageConverter() { return new CustomHttpMessageConverter(); } @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .configureMessageConverters(f -> (2) f.registerDefaults() (3) .addCustomConverter(customHttpMessageConverter()) (4) ) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | カスタムメッセージコンバータのBeanを定義する。 * - | (2) - | \ ``RestClient.Builder``\ の\ ``configureMessageConverters``\ メソッドにて\ ``HttpMessageConverter``\ を登録する。 * - | (3) - | 関数の引数に\ ``HttpMessageConverters.ClientBuilder``\ が渡されるため、\ ``ClientBuilder``\ の\ ``registerDefaults``\ メソッドを呼び出し、デフォルトの\ ``HttpMessageConverter``\ の登録を行う。 * - | (4) - | \ ``ClientBuilder``\ の\ ``addCustomConverter``\ メソッドにて(1)で定義したBeanを登録する。 | | なお、\ ``ClientBuilder``\ にはカスタムメッセージコンバータの追加以外にも、各種メディアタイプに対応する\ ``HttpMessageConverter``\ の置き換え用のメソッド(with~)が用意されている。 | 詳しくは、\ :url_spring_javadoc:`HttpMessageConverters.BuilderのJavadoc `\ を参照されたい。 | これらの置き換え用のメソッドを使用することで、既存のメディアタイプに対応する\ ``HttpMessageConverter``\ の変更も可能である。 .. group-tab:: RestTemplate .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("customHttpMessageConverter") public CustomHttpMessageConverter customHttpMessageConverter() { return new CustomHttpMessageConverter(); } @Bean("restTemplate") public RestTemplate restTemplate() { var bean = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); List> converters = new ArrayList<>(); // omitted: Default HttpMessageConverters registration converters.add(customHttpMessageConverter()); // (2) bean.setMessageConverters(converters); // (3) return bean; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | カスタムメッセージコンバータのBeanを定義する。 * - | (2) - | \ ``List``\ に(1)で定義したBeanを追加する。 | カスタムメッセージコンバータ以外の\ ``HttpMessageConverter``\ の登録は省略しているが、必要に応じて登録を行うこと。 * - | (3) - | \ ``RestTemplate``\ の\ ``setMessageConverters``\ メソッドを使用して、\ ``HttpMessageConverter``\ のリストを設定する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | カスタムメッセージコンバータのBeanを定義する。 * - | (2) - | \ ``List``\ に(1)で定義したBeanを追加する。 | カスタムメッセージコンバータ以外の\ ``HttpMessageConverter``\ の登録は省略しているが、必要に応じて登録を行うこと。 * - | (3) - | \ ``RestTemplate``\ の\ ``messageConverters``\ プロパティを使用して、\ ``HttpMessageConverter``\ のリストを設定する。 | .. _RestClientHowToExtendClientHttpRequestInterceptor: リクエスト送信前後の共通処理の適用(\ ``ClientHttpRequestInterceptor``\ ) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``ClientHttpRequestInterceptor``\ を使用することで、サーバとの通信処理の前後に任意の処理を実行させることができる。 | ここでは、リクエスト送信前後のログ出力処理を適用する方法を紹介する。 リクエスト送信前後のロギング処理 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" サーバとの通信ログを出力したい場合は、以下のような実装を行う。 \ **通信ログ出力の実装例**\ .. tabs:: .. group-tab:: RestClient \ **Bean定義ファイルの定義例**\ - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .requestInterceptor((request, body, execution) -> { // (1) if (logger.isInfoEnabled()) { var requestBody = new String(body, StandardCharsets.UTF_8); // (2) logger.info("Request Header {}", request.getHeaders()); logger.info("Request Body {}", requestBody); } ClientHttpResponse response = execution.execute(request, body); // (3) if (logger.isInfoEnabled()) { // (4) logger.info("Response Header {}", response.getHeaders()); logger.info("Response Status Code {}", response.getStatusCode()); } return response; // (5) }) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestClient.Builder``\ の\ ``requestInterceptor``\ メソッドにリクエスト送信前後の処理を実装する。 * - | (2) - | リクエスト送信前の共通処理を実装する。 | 上記の実装例では、リクエストヘッダとリクエストボディの内容をログに出力している。 * - | (3) - | 関数の引数として受け取った\ ``org.springframework.http.client.ClientHttpRequestExecution``\ の\ ``execute``\ メソッドを実行し、リクエストの送信を行う。 * - | (4) - | レスポンス受信後の共通処理を実装する。 | 上記の実装例では、レスポンスヘッダとステータスコードの内容をログに出力している。 * - | (5) - | (3)で受信したレスポンスを返却する。 .. group-tab:: RestTemplate \ **ClientHttpRequestInterceptorの実装**\ .. code-block:: java package com.example.restclient; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; public class LoggingInterceptor implements ClientHttpRequestInterceptor { // (1) private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class); @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { if (logger.isInfoEnabled()) { var requestBody = new String(body, StandardCharsets.UTF_8); // (2) logger.info("Request Header {}", request.getHeaders()); logger.info("Request Body {}", requestBody); } ClientHttpResponse response = execution.execute(request, body); // (3) if (logger.isInfoEnabled()) { // (4) logger.info("Response Header {}", response.getHeaders()); logger.info("Response Status Code {}", response.getStatusCode()); } return response; // (5) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.springframework.http.client.ClientHttpRequestInterceptor``\ インタフェースを実装する。 * - | (2) - | リクエスト送信前の共通処理を実装する。 | 上記の実装例では、リクエストヘッダとリクエストボディの内容をログに出力している。 * - | (3) - | \ ``intercept``\ メソッドの引数として受け取った\ ``org.springframework.http.client.ClientHttpRequestExecution``\ の\ ``execute``\ メソッドを実行し、リクエストの送信を行う。 * - | (4) - | レスポンス受信後の共通処理を実装する。 | 上記の実装例では、レスポンスヘッダとステータスコードの内容をログに出力している。 * - | (5) - | (3)で受信したレスポンスを返却する。 \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java // (1) @Bean("loggingInterceptor") public LoggingInterceptor loggingInterceptor() { return new LoggingInterceptor(); } @Bean("restTemplate") public RestTemplate restTemplate() { var bean = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); bean.setInterceptors(List.of(loggingInterceptor())); // (2) return bean; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInterceptor``\ の実装クラスのBean定義を行う。 * - | (2) - | \ ``RestTemplate``\ の\ ``setInterceptors``\ メソッドに\ ``ClientHttpRequestInterceptor``\ の\ ``List``\ を設定する。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInterceptor``\ の実装クラスのBean定義を行う。 * - | (1) - | \ ``interceptors``\ プロパティに\ ``ClientHttpRequestInterceptor``\ の\ ``List``\ をインジェクションする。 | .. _RestClientHowToExtendMultiClientHttpRequestInterceptor: 複数の\ ``ClientHttpRequestInterceptor``\ を適用する方法 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 複数の\ ``ClientHttpRequestInterceptor``\ を適用するには、以下のような実装を行う。 .. tabs:: .. group-tab:: RestClient \ **Bean定義ファイルの定義例**\ - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("restClient") public RestClient restClient() { return RestClient.builder() .requestFactory(new HttpComponentsClientHttpRequestFactory()) .requestInterceptors(interceptors -> { // (1) interceptors.add((request, body, execution) -> { // (2) if (logger.isInfoEnabled()) { var requestBody = new String(body, StandardCharsets.UTF_8); logger.info("Request Header {}", request.getHeaders()); logger.info("Request Body {}", requestBody); } ClientHttpResponse response = execution.execute(request, body); if (logger.isInfoEnabled()) { logger.info("Response Header {}", response.getHeaders()); logger.info("Response Status Code {}", response.getStatusCode()); } return response; }); interceptors.add((request, body, execution) -> { // (3) // omitted return execution.execute(request, body); }); }) .build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestClient.Builder``\ の\ ``requestInterceptors``\ メソッドの引数には\ ``Consumer>``\ が設定されているため、関数を呼び出し\ ``ClientHttpRequestInterceptor``\ を追加する。 | \ ``List``\ に追加した順番で\ ``ClientHttpRequestInterceptor``\ のチェーンが実行される。 | 上記の例では、リクエスト送信前は(2)、(3)の順に実行され、レスポンス受信後は(3)、(2)の順に実行される。 * - | (2) - | (1)で取得した\ ``List``\ に\ ``ClientHttpRequestInterceptor``\ を追加する。 * - | (3) - | (2)と同様に、登録したい\ ``ClientHttpRequestInterceptor``\ を追加する。 .. group-tab:: RestTemplate \ **Bean定義ファイルの定義例**\ .. tabs:: .. group-tab:: Java Config - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("loggingInterceptor") public LoggingInterceptor loggingInterceptor() { return new LoggingInterceptor(); } @Bean("customInterceptor") public CustomInterceptor customInterceptor() { return new CustomInterceptor(); } @Bean("restTemplate") public RestTemplate restTemplate() { var bean = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); bean.setInterceptors(List.of(loggingInterceptor(), customInterceptor())); // (1) return bean; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestTemplate``\ の\ ``setInterceptors``\ メソッドに\ ``ClientHttpRequestInterceptor``\ の\ ``List``\ を設定する。 | \ ``List``\ に追加した順番で\ ``ClientHttpRequestInterceptor``\ のチェーンが実行される。 | 上記の例では、リクエスト送信前はloggingInterceptor、customInterceptorの順に実行され、レスポンス受信後はcustomInterceptor、loggingInterceptorの順に実行される。 .. group-tab:: XML Config - \ ``applicationContext.xml``\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestTemplate``\ の\ ``interceptors``\ プロパティに\ ``ClientHttpRequestInterceptor``\ の\ ``List``\ を設定する。 | \ ``List``\ に追加した順番で\ ``ClientHttpRequestInterceptor``\ のチェーンが実行される。 | 上記の例では、リクエスト送信前はloggingInterceptor、customInterceptorの順に実行され、レスポンス受信後はcustomInterceptor、loggingInterceptorの順に実行される。 | How to use(HTTP Service Clients) -------------------------------------------------------------------------------- | 本節では、\ ``HTTP Service Clients``\ を使用したクライアント処理の実装方法について説明する。 | | \ ``HTTP Service Clients``\ の設定方法は、\ ``@HttpExchange``\ アノテーションを付与したインターフェースを、インターフェース単位で設定・登録する方法と、インターフェースをグループ単位で設定し一括登録する方法の2通りがある。 | インターフェース単位で設定・登録する場合は、\ ``HttpServiceProxyFactory``\ にクライアント設定を行ったうえで、\ ``@HttpExchange``\ を付与したインターフェースのクライアント実装(動的プロキシ)を作成し、DIコンテナに登録を行う。 | インターフェースをグループ単位で設定し一括登録する場合は、\ ``HttpServiceGroupConfigurer``\ にてグループ単位でクライアント設定を行う。 | そのうえで、\ ``@ImportHttpServices``\ にグループに含める\ ``@HttpExchange``\ を付与したインターフェースを指定し、指定したインターフェースのクライアント実装(動的プロキシ)を一括作成して、DIコンテナに登録を行う。 | | \ ``@HttpExchange``\ アノテーションを付与したインターフェースの作成粒度に制約はないため、一つのインターフェースにてサーバへのアクセスを全て集約して定義することも可能ではあるが、可読性やメンテナンス性を考慮すると意味のある単位で分割して定義を行う方が望ましい。 | 適切な分割単位としては、RESTful Web Serviceの章で紹介している「\ :ref:`リソース指向アーキテクチャ `\ 」のリソース単位で作成するのが可読性やメンテナンス性の観点からも適している。 | |framework_name| では、\ ``@HttpExchange``\ アノテーションを付与したインターフェースは、リソース単位で作成することを推奨し、以降の説明においてもリソース単位で作成することを前提として説明を行う。 | | それぞれの特徴は、以下の通りである。 * \ **インターフェース単位で設定・登録する場合(HttpServiceProxyFactoryを利用した設定)**\ | アクセスするリソース単位で繰り返しBean定義を行う必要があるため、小規模なシステムで\ ``HTTP Service Clients``\ を数個程度利用する場合に適している。 | また、クライアントの設定をカスタマイズ(\ ``ClientHttpRequestFactory``\ 、\ ``ClientHttpRequestInterceptor``\ の設定等)する場合、\ ``HTTP Service Clients``\ に直接設定するのではなく、元となる各RESTクライアント実装に設定を行う必要があるため、既に\ ``RestClient``\ や\ ``RestTemplate``\ を利用していて\ ``HTTP Service Clients``\ に移行したい場合に適している。 * \ **グループ単位で設定を集約して一括登録する場合(@ImportHttpServicesアノテーションとHttpServiceGroupConfigurerを利用した設定)**\ | 一括でまとめてBean定義を行うことができるため、中規模以上のシステムで\ ``HTTP Service Clients``\ を多数利用する場合に適している。 | クライアントの設定をカスタマイズする際には、\ ``RestClient``\ や\ ``RestTemplate``\ で利用する拡張ポイントが \ ``RestClientHttpServiceGroupConfigurer``\ 経由で同じように利用できるため、\ ``HTTP Service Clients``\ だけで設定を完結できる。 | これにより設定内容を \ ``HTTP Service Clients``\ に集約できるため、新規でRESTクライアントを実装する場合に適している。 | .. _HttpServiceClientsHowToUseResourceSetup: HTTP Service Clients(インターフェース単位で設定・登録する場合)のセットアップ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``HttpServiceProxyFactory``\ を利用しインターフェース単位で設定・登録する場合は、以下の手順でセットアップを行う。 #. | \ ``RestClient``\ 、\ ``RestTemplate``\ のいずれかのBeanをDIコンテナに登録する。 #. | \ ``@HttpExchange``\ アノテーションを付与したインターフェースを作成する。 #. | DIコンテナに登録したRESTクライアントのBeanを、\ ``Adapter``\ を介して\ ``HttpServiceProxyFactory``\ に設定する。 | \ ``HttpServiceProxyFactory``\ を使用して\ ``@HttpExchange``\ アノテーションを付与したインターフェースの動的プロキシを生成し、DIコンテナに登録する。 #. | RESTクライアントを利用するコンポーネントにて、\ ``@HttpExchange``\ アノテーションを付与したインターフェースの動的プロキシをインジェクションする。 | RESTクライアントのBean定義(インターフェース単位で設定・登録する場合) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestClient``\ 、\ ``RestTemplate``\ のいずれかのBeanをDIコンテナに登録する。 | 本手順は\ ``RestClient``\ 、\ ``RestTemplate``\ の設定と同様のため、「\ :ref:`RestClientHowToUseSetup`\ 」を参照されたい。 | | なお、\ ``HttpServiceProxyFactory``\ を利用する場合は、各RESTクライアント実装の設定を引き継ぐため\ ``HTTP Service Clients``\ で設定できる内容は限られている。 | 具体的には、REST APIへのアクセス方法や「\ :ref:`HttpServiceClientsHowToExtendConversionService`\ 」等の一部のみが設定可能であり、\ ``ClientHttpRequestFactory``\ や\ ``ClientHttpRequestInterceptor``\ 等の拡張ポイントは設定できない。 | したがって、共通的な設定に関しては各RESTクライアント実装にて設定を行う必要がある。 | 設定については\ ``RestClient``\ 、\ ``RestTemplate``\ にて説明を行っているので、以下を参照されたい。 * \ :ref:`RestClientHowToUseBaseSettings`\ * \ :ref:`RestClientHowToUseErrorHandling`\ * \ :ref:`RestClientHowToUseRequestHeaderCommon`\ * \ :ref:`RestClientHowToUseAuthorizationHeader`\ * \ :ref:`RestClientHowToUseTimeoutSettings`\ * \ :ref:`RestClientHowToExtendClientHttpRequestFactory`\ * \ :ref:`RestClientHowToExtendHttpMessageConverter`\ * \ :ref:`RestClientHowToExtendClientHttpRequestInterceptor`\ | .. _HttpServiceClientsHowToUseSetupAPISetting: HTTP Service Clientsの定義(インターフェース単位で設定・登録する場合) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``@HttpExchange``\ アノテーションを付与したインターフェースを作成する。 | インターフェースの定義は、\ ``RestClient``\ 、\ ``RestTemplate``\ のいずれの場合でも同様である。 | .. code-block:: java import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; @HttpExchange(url = "/api/v1") // (1) public interface UserApi { // (2) @GetExchange(value = "users/{id}") User getUser(@PathVariable("id") String id); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | REST APIのURLとマッピングするアノテーションとして\ ``@HttpExchange``\ アノテーションを定義する。 | url属性には、本インターフェースの基準となるURLを指定する。 | 上記の例では、「\ :ref:`RestClientHowToUseBaseSettings`\ 」を設定したうえで、\ ``/api/v1``\ をこのインターフェースで利用する共通のURLとして設定している。 | なお、\ ``@HttpExchange``\ アノテーションはクラスで指定をせず、メソッドに付与することも可能である。 * - | (2) - | インターフェースとして定義を行う。 | 実装は動的プロキシとして作成されるため、実装クラスは不要である。 | HTTP Service ClientsのBean定義(インターフェース単位で設定・登録する場合) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestClient``\ 、\ ``RestTemplate``\ のBeanを、\ ``Adapter``\ を介して\ ``HTTP Service Clients``\ に設定する。 | .. tabs:: .. group-tab:: RestClient - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Value("${api.baseUrl:http://localhost:8080}") private String url; @Bean("restClient") public RestClient restClient() { return RestClient.builder() .baseUrl(url) .requestFactory(new HttpComponentsClientHttpRequestFactory()) .build(); } // (1) @Bean("userApi") public UserApi userApi() { RestClientAdapter adapter = RestClientAdapter.create(restClient()); // (2) HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); // (3) return factory.createClient(UserApi.class); // (4) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HTTP Service Clients``\ のBean定義を行う。 | 返却値として「\ :ref:`HttpServiceClientsHowToUseSetupAPISetting`\ 」で定義したインターフェースを設定する。 * - | (2) - | \ ``org.springframework.web.client.support.RestClientAdapter``\ の\ ``create``\ メソッドを使用して\ ``RestClientAdapter``\ のインスタンスを生成する。 | 引数には、\ ``RestClient``\ のBeanを指定する。 * - | (3) - | \ ``org.springframework.web.service.invoker.HttpServiceProxyFactory``\ の\ ``builderFor``\ メソッドを使用して、(2)で生成した\ ``Adapter``\ を設定する。 | その後、\ ``build``\ メソッドを実行して\ ``HttpServiceProxyFactory``\ のインスタンスを生成する。 * - | (4) - | \ ``HttpServiceProxyFactory``\ の \ ``createClient``\ メソッドを使用して、(1)で定義したインターフェースの型(クラス)を設定する。 | 返却値として\ ``HTTP Service Clients``\ のBean(動的プロキシ)を返却し、DIコンテナに登録する。 .. group-tab:: RestTemplate - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Value("${api.baseUrl:http://localhost:8080}") private String url; @Bean("restTemplate") public RestTemplate restTemplate() { var restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory()); restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(url)); return restTemplate; } // (1) @Bean("userApi") public UserApi userApi() { RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate()); // (2) HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); // (3) return factory.createClient(UserApi.class); // (4) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HTTP Service Clients``\ のBean定義を行う。 | 返却値として「\ :ref:`HttpServiceClientsHowToUseSetupAPISetting`\ 」で定義したインターフェースを設定する。 * - | (2) - | \ ``org.springframework.web.client.support.RestTemplateAdapter``\ の\ ``create``\ メソッドを使用して\ ``RestTemplateAdapter``\ のインスタンスを生成する。 | 引数には、\ ``RestTemplate``\ のBeanを指定する。 * - | (3) - | \ ``org.springframework.web.service.invoker.HttpServiceProxyFactory``\ の\ ``builderFor``\ メソッドを使用して、(2)で生成した\ ``Adapter``\ を設定する。 | その後、\ ``build``\ メソッドを実行して\ ``HttpServiceProxyFactory``\ のインスタンスを生成する。 * - | (4) - | \ ``HttpServiceProxyFactory``\ の \ ``createClient``\ メソッドを使用して、(1)で定義したインターフェースの型(クラス)を設定する。 | 返却値として\ ``HTTP Service Clients``\ のBean(動的プロキシ)を返却し、DIコンテナに登録する。 | .. _HttpServiceClientsHowToUseSetupAPIUse: HTTP Service Clientsの利用(インターフェース単位で設定・登録する場合) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | DIコンテナに登録した\ ``HTTP Service Clients``\ のBeanをインジェクションする。 | .. code-block:: java @Service public class AccountServiceImpl implements AccountService { @Inject private UserApi userApi; // (1) @Override public User getAccount(String id) { return userApi.getUser(id); // (2) } // omitted } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``@Inject``\ アノテーションを付与し、\ ``HTTP Service Clients``\ のBeanをインジェクションする。 * - | (2) - | 「\ :ref:`HttpServiceClientsHowToUseSetupAPISetting`\ 」で定義したメソッドを呼び出し、処理を実行する。 | .. Spring7.0対応:ImportHttpServicesとHttpServiceGroupConfigurerを使った設定が可能になった .. _HttpServiceClientsHowToUseBulkSetup: HTTP Service Clients(グループ単位で設定を集約して一括登録する場合)のセットアップ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``@ImportHttpServices``\ アノテーションと\ ``HttpServiceGroupConfigurer``\ を利用してグループ単位で一括で設定を行う場合は、以下の手順でセットアップを行う。 #. | \ ``@HttpExchange``\ アノテーションを付与したインターフェースを作成する。 #. | \ ``@HttpExchange``\ アノテーションを付与したインターフェースをHTTPサービスグループに登録し、HTTPサービスグループ単位で設定を行う。 .. note:: \ **HTTPサービスグループとは**\ 同じクライアント設定と\ ``HttpServiceProxyFactory``\ を共有する\ ``@HttpExchange``\ を付与したインターフェースの集合である。 グループ定義は\ ``@ImportHttpServices``\ で行い、定義したグループ単位のクライアント設定は\ ``HttpServiceGroupConfigurer``\ にて適用する。 #. | RESTクライアントを利用するコンポーネントにて、\ ``@HttpExchange``\ アノテーションを付与したインターフェースの動的プロキシをインジェクションする。 | HTTP Service Clientsの定義(グループ単位で設定を集約して一括登録する場合) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``@HttpExchange``\ アノテーションを付与したインターフェースを作成する。 | インターフェースの定義については、インターフェース単位で設定・登録する場合と同様のため「\ :ref:`HttpServiceClientsHowToUseSetupAPISetting`\ 」を参照されたい。 | HTTP Service ClientsのBean定義(グループ単位で設定を集約して一括登録する場合) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 作成した\ ``HTTP Service Clients``\ をHTTPサービスグループに登録し、HTTPサービスグループ単位で設定を行う。 | .. code-block:: java import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer; import org.springframework.web.service.registry.ImportHttpServices; // (1) @Configuration @ImportHttpServices(group = "first", types = { UserApi.class }) // (2) @ImportHttpServices(group = "second", basePackages = "com.example.restclient.api") // (3) public class HttpServiceClientsConfig { private static final Logger logger = LoggerFactory.getLogger(HttpServiceClientsConfig.class); @Value("${api.baseUrl:http://localhost:8080}") private String url; @Bean @Order(0) // (4) RestClientHttpServiceGroupConfigurer defaults() { // (5) return groups -> groups.forEachGroup((group, clientBuilder, factoryBuilder) -> { // (6) clientBuilder.baseUrl(url) .requestFactory(new HttpComponentsClientHttpRequestFactory()) .defaultHeader("Default-Header", "TestHeader") .requestInterceptor((request, body, execution) -> { if (logger.isInfoEnabled()) { var requestBody = new String(body, StandardCharsets.UTF_8); logger.info("Request Header {}", request.getHeaders()); logger.info("Request Body {}", requestBody); } ClientHttpResponse response = execution.execute(request, body); if (logger.isInfoEnabled()) { logger.info("Response Header {}", response.getHeaders()); logger.info("Response Status Code {}", response.getStatusCode()); } return response; }); }); } @Bean @Order(1) // (4) RestClientHttpServiceGroupConfigurer firstGroup() { // (7) return groups -> groups.filterByName("first").forEachGroup((group, clientBuilder, factoryBuilder) -> { clientBuilder.defaultHeader("First-Header", "TestHeader"); }); } @Bean @Order(2) // (4) RestClientHttpServiceGroupConfigurer secondGroup() { // (7) return groups -> groups.filterByName("second").forEachGroup((group, clientBuilder, factoryBuilder) -> { clientBuilder.defaultHeader("Second-Header", "TestHeader"); }); } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HTTP Service Clients``\ のBean定義を行うクラスを作成する。 | \ ``ApplicationContextConfig``\ に記載しても問題はないが、他の定義と混在し煩雑になりやすいため、別クラスに分けることを推奨する。 | \ ``ApplicationContextConfig``\ には、\ ``@Import({ HttpServiceClientsConfig.class })``\ を追加し、\ ``HttpServiceClientsConfig``\ の定義を取り込む。 * - | (2) - | \ ``@ImportHttpServices``\ アノテーションを付与し、\ ``HTTP Service Clients``\ をHTTPサービスグループに登録する。 | クラス単位で登録する場合は、\ ``group``\ 属性にグループ名を指定し、\ ``types``\ 属性に\ ``HTTP Service Clients``\ のインターフェースの型(クラス)を指定する。 | 同一グループに複数の\ ``HTTP Service Clients``\ を登録する場合は、\ ``types``\ 属性に配列で指定する。 | | (2)、(3)は設定方法の違いを示すために両方記載しているが、どちらか一方の方法で登録すれば問題ない。 * - | (3) - | \ ``@ImportHttpServices``\ アノテーションを付与し、\ ``HTTP Service Clients``\ をHTTPサービスグループに登録する。 | パッケージを指定して一括で登録する場合は、\ ``group``\ 属性にグループ名を指定し、\ ``basePackages``\ 属性に登録するパッケージ名を指定する。 | \ ``basePackages``\ を指定することにより、パッケージ配下に存在する\ ``@HttpExchange``\ アノテーションを付与した全てのインターフェースが登録される。 | | (2)、(3)は設定方法の違いを示すために両方記載しているが、どちらか一方の方法で登録すれば問題ない。 | また、HTTPサービスグループは、他にも手動で登録する方法などもあるので、詳しくは「\ :url_spring_reference:`HTTP Service Clients - HTTP Service groups `\ 」を参照されたい。 * - | (4) - | \ ``@Order``\ アノテーションを付与し、HTTPサービスグループの設定順序を指定する。 | 上記の例では、\ ``defaults``\ メソッドで全てのグループに対して共通の設定を行った後に、\ ``firstGroup``\ メソッドと\ ``secondGroup``\ メソッドでそれぞれのグループに対して個別の設定を行っている。 .. warning:: \ **Orderアノテーションの設定順序について**\ | \ ``@Order``\ アノテーションの引数の値が小さい\ ``HttpServiceGroupConfigurer``\ ほど優先度が高く、先に適用される。 | そのため、同じ設定項目に対して複数の\ ``HttpServiceGroupConfigurer``\ で設定を行う場合、設定の種類に応じて次の挙動となる。 - \ ``ClientHttpRequestFactory``\ のように単一の設定しか保持できない項目 | 後から適用された設定で上書きされる。 | そのため、複数の\ ``HttpServiceGroupConfigurer``\ で設定した場合は、\ ``@Order``\ アノテーションの引数の値が大きい設定が有効となる。 - \ ``ClientHttpRequestInterceptor``\ のように複数設定を保持できる項目 \ ``@Order``\ アノテーションの引数の値が小さい順に設定が追加される。 | そのため、共通設定を先に適用し、個別設定で上書きまたは追加を行う場合は、共通設定の\ ``@Order``\ の値を小さくし、個別設定の\ ``@Order``\ の値を大きくする必要がある。 * - | (5) - | \ ``org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer``\ を利用して、HTTPサービスグループの設定を行う。 | \ ``RestClientHttpServiceGroupConfigurer``\ は、\ ``@FunctionalInterface``\ で提供されているため、関数にて設定を行う。 | 上記の例では、グループをフィルタリングせずに\ ``forEachGroup``\ メソッドを使用して全てのグループに対して設定を行っている。 * - | (6) - | \ ``forEachGroup``\ の引数として\ ``DefaultRestClientBuilder``\ が引き渡されるため、必要に応じて共通のカスタマイズ設定を行う。 | 設定内容は\ ``RestClient``\ と同様のため、以下を参照されたい。 * \ :ref:`RestClientHowToUseBaseSettings`\ * \ :ref:`RestClientHowToUseErrorHandling`\ * \ :ref:`RestClientHowToUseRequestHeaderCommon`\ * \ :ref:`RestClientHowToUseAuthorizationHeader`\ * \ :ref:`RestClientHowToUseTimeoutSettings`\ * \ :ref:`RestClientHowToExtendClientHttpRequestFactory`\ * \ :ref:`RestClientHowToExtendHttpMessageConverter`\ * \ :ref:`RestClientHowToExtendClientHttpRequestInterceptor`\ * - | (7) - | グループ単位で設定する場合は、\ ``filterByName``\ メソッドを使用してグループをフィルタリングして設定を行う。 .. note:: **HttpServiceGroupConfigurerについて** | \ ``HttpServiceGroupConfigurer``\ のサブインターフェースとして、\ ``RestClientHttpServiceGroupConfigurer``\ と \ ``WebClientHttpServiceGroupConfigurer``\ が定義されている。 | これらのインターフェースが、\ ``DefaultRestClientBuilder``\ や\ ``DefaultWebClientBuilder``\ を仲介し、\ ``RestClient``\ や\ ``WebClient``\ の設定を行っている。 | したがって、同期通信を行う場合は\ ``RestClientHttpServiceGroupConfigurer``\ を、非同期通信を行う場合は\ ``WebClientHttpServiceGroupConfigurer``\ を使用する必要がある。 | HTTP Service Clientsの利用(グループ単位で設定を集約して一括登録する場合) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | DIコンテナに登録した\ ``HTTP Service Clients``\ のBeanをインジェクションする。 | \ ``HTTP Service Clients``\ の利用に関しては、インターフェース単位で設定・登録する場合と同様のため「\ :ref:`HttpServiceClientsHowToUseSetupAPIUse`\ 」を参照されたい。 | .. _HttpServiceClientsHowToUseGet: データの取得(GETリクエスト送信) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 本項では、\ ``HTTP Service Clients``\ を使用したデータ取得について説明を行う。 | GETメソッドを指定したリクエスト送信の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | GETメソッドでリクエスト送信を行い、結果を取得する場合の実装の説明を行う。 | 以下は、\ ``HTTP Service Clients``\ のインターフェースの定義である。 | \ ``HTTP Service Clients``\ の定義 .. code-block:: java // (1) @GetExchange(value = "users/{id}") // (2) User getUser(@PathVariable("id") String id); // (3) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 返却値としてレスポンスボディの型を定義する。 | 返却値についての説明は、次項の「\ :ref:`HttpServiceClientsHowToUseGetReturn`\ 」にて説明を行う。 * - | (2) - | HTTPのメソッドにGETを指定する場合は、\ ``@GetExchange``\ アノテーションを付与する。 | \ ``value``\ 属性にはURLを指定する。 | \ ``@HttpExchange``\ の\ ``method``\ 属性にGETを指定した場合と同等である。 * - | (3) - | URIテンプレート変数の値を設定する場合は、メソッドに引数を定義し\ ``@PathVariable``\ アノテーションを付与する。 | アノテーションの引数にはURIテンプレート変数の名前を指定する。 | .. _HttpServiceClientsHowToUseGetReturn: \ ``HTTP Service Clients``\ の返却値について """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``HTTP Service Clients``\ で指定できる返却値は、他のRESTクライアントと同様でリクエストボディの型と\ ``ResponseEntity``\ が指定可能である。 | 下記以外の実装については、「\ :url_spring_reference:`HTTP Service Clients - Return Values `\ 」を参照されたい。 | \ ``HTTP Service Clients``\ の定義 .. code-block:: java @GetExchange(value = "users/{id}") User getUser(@PathVariable("id") String id); // (1) @GetExchange(value = "users/{id}") ResponseEntity getUserResponseEntity(@PathVariable("id") String id); // (2) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 返却値としてレスポンスボディの型を定義する。 | レスポンスボディのみ取得したい場合に使用する。 * - | (2) - | 返却値として\ ``ResponseEntity``\ を定義する。 | 総称型にはレスポンスボディの型を指定する。 | HTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合に使用する。 | .. _HttpServiceClientsHowToUsePost: データの登録(POSTリクエスト送信) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 本項では、\ ``HTTP Service Clients``\ を使用したデータ登録について説明を行う。 | 他のHTTPメソッド(PUT, PATCH, DELETE, HEAD, OPTIONSなど)もサポートしており、同じような要領で使用することができる。 | 他の実装については、「\ :url_spring_javadoc:`HttpExchangeのJavadoc `\ 」を参照されたい。 | POSTメソッドを指定したリクエスト送信の実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | POSTメソッドでリクエスト送信を行い、結果を取得する場合の実装の説明を行う。 | 以下は、\ ``HTTP Service Clients``\ のインターフェースの定義である。 | \ ``HTTP Service Clients``\ の定義 .. code-block:: java @PostExchange(value = "users") // (1) User createUser(@RequestBody User user); // (2) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | HTTPのメソッドにPOSTを指定する場合は、\ ``@PostExchange``\ アノテーションを付与する。 | \ ``value``\ 属性にはURLを指定する。 * - | (2) - | リクエストボディに値を設定する場合は、メソッドに引数を定義し\ ``@RequestBody``\ アノテーションを付与する。 | .. _HttpServiceClientsHowToUseRequestHeader: リクエストヘッダの設定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | リクエストヘッダを設定する方法は、アプリケーション全体で共通のヘッダを設定する方法と、リクエスト送信単位でヘッダを設定する方法がある。 | \ ``HTTP Service Clients``\ のインターフェースで定義できるのは、後者のリクエスト送信単位でヘッダを設定する方法である。 | 共通のリクエストヘッダについては「\ :ref:`RestClientHowToUseRequestHeaderCommon`\ 」と同様の設定を行う必要がある。 | | 以下は、\ ``HTTP Service Clients``\ を利用したリクエスト送信単位でヘッダを設定する実装例である。 | \ ``HTTP Service Clients``\ の定義 .. code-block:: java @GetExchange(value = "users/{id}") User getUserSetHeader(@RequestHeader("Custom-Header") String customValue, // (1) @PathVariable("id") String id); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | リクエストヘッダを設定する場合は、\ ``@RequestHeader``\ アノテーションを付与する。 | アノテーションの引数にはヘッダ名を指定する。 | エラーハンドリング ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``HTTP Service Clients``\ を利用した際のエラーのハンドリングに関しては、同期通信のエラーハンドリングと同様で\ ``try-catch``\ 文を使用して例外ハンドリングを行う。 | ハンドリング方法に関しては、同期通信の「\ :ref:`RestClientHowToUseErrorHandling`\ 」を参照されたい。 | ファイルアップロード(マルチパートリクエスト) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``HTTP Service Clients``\ におけるファイルアップロードについて説明を行う。 | \ ``HTTP Service Clients``\ でファイルアップロード(マルチパートリクエスト)を行うには、\ ``@RequestPart``\ アノテーションを使用してリクエストボディにファイルを設定する必要がある。 | \ ``HTTP Service Clients``\ の定義 .. code-block:: java @PostExchange(value = "users/upload") void upload(@RequestPart("file") Resource resource); // (1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | マルチパートデータを設定する場合は、\ ``@RequestPart``\ アノテーションを付与する。 | アノテーションの引数にはパート名を指定し、ファイルを渡す際の型は\ ``Resource``\ 型を指定する。 | なお、ファイルを渡す際の型は\ ``MultipartFile``\ 型を指定することも可能である。 | \ ``HTTP Service Clients``\ の呼び出し .. code-block:: java @Override public void upload(String path) { var resource = new ClassPathResource(path); // (1) userApi.upload(resource); // (2) } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ファイルを\ :url_spring_javadoc:`Resource `\ の実装クラスを使用して取得する。 * - | (2) - | \ ``HTTP Service Clients``\ のメソッドに(1)の\ ``Resource``\ を引数として設定し、アップロードを実行する。 | ファイルダウンロード ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``HTTP Service Clients``\ では返却値として\ ``InputStream``\ や\ ``Resource``\ などの型を指定することで、レスポンスボディをファイルとして受け取ることが出来る。 | 「\ :ref:`RestClientHowToUseFileDownload`\ 」でも説明しているとおり、\ ``HTTP Service Clients``\ においても\ ``Resource``\ を返却値に指定した場合はダウンロードするファイルのサイズによっては、\ ``java.lang.OutOfMemoryError``\ が発生する可能性があるため、基本的には\ ``InputStream``\ を返却値に指定する実装を推奨する。 | ダウンロードするファイルのサイズが小さく、メモリに展開しても問題ない場合に限り、\ ``Resource``\ を返却値に指定した実装を検討するとよい。 | .. Spring7.0対応:レスポンスボディをInputStreamで受け取ることが可能になった \ **レスポンスボディをInputStream型で取得する実装例**\ | \ ``HTTP Service Clients``\ を利用して、レスポンスボディを\ ``InputStream``\ 型で受け取りダウンロードする場合の実装例を示す。 | \ ``HTTP Service Clients``\ の定義 .. code-block:: java @GetExchange(value = "users/download") InputStream download(); // (1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 返却値に\ ``InputStream``\ 型を指定する。 | \ ``HTTP Service Clients``\ の呼び出し インポート宣言 .. code-block:: java import org.springframework.util.FileCopyUtils; メソッド .. code-block:: java @Override public void download() { try(InputStream in = userApi.download()) { // (1) File rcvFile = File.createTempFile("rcvFile", "zip"); // (2) FileCopyUtils.copy(in, new FileOutputStream(rcvFile)); // (3) // omitted }; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HTTP Service Clients``\ のメソッドを呼び出し、ダウンロードを実行する。 * - | (2) - | 出力するファイルを生成する。 * - | (3) - | \ ``FileCopyUtils``\ を使用して、(2)で生成したファイルにレスポンスボディを少しずつ書き出す。 | \ **レスポンスボディをResource型で取得する実装例**\ | \ ``HTTP Service Clients``\ を利用して、レスポンスボディを\ ``Resource``\ 型で受け取りダウンロードする場合の実装例を示す。 | 本実装では、\ ``ResourceHttpMessageConverter``\ がレスポンスボディを一度\ ``byte``\ 配列として読み込むため、レスポンスボディ全体がメモリ上に展開される。 | そのため、ダウンロードするファイルのサイズが小さい場合のみ、以下の方法で実装を行うこと。 | \ ``HTTP Service Clients``\ の定義 .. code-block:: java @GetExchange(value = "users/download") Resource download(); // (1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 返却値に\ ``Resource``\ 型を指定する。 | \ ``HTTP Service Clients``\ の呼び出し .. code-block:: java @Override public void download() { Resource resource = userApi.download(); // (1) // omitted } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HTTP Service Clients``\ のメソッドを呼び出し、ダウンロードを実行する。 | ダウンロード後、必要に応じて\ ``Resource``\ を使用した処理を実装する。 | .. _HttpServiceClientsHowToExtend: How to extend(HTTP Service Clients) -------------------------------------------------------------------------------- 本節では、\ ``HTTP Service Clients``\ 実装の拡張方法について説明する。 | .. _HttpServiceClientsHowToExtendConversionService: HTTP Service ClientsでのURIテンプレート変数値のフォーマット変換 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``HTTP Service Clients``\ を利用し、URIテンプレート変数の値をフォーマット変換する場合は、\ ``ConversionService``\ を利用して実装する必要がある。 | | 「\ :ref:`RestClientHowToExtendUriBuilderFactoryFormatVariable`\ 」と同様のことを\ ``HTTP Service Clients``\ で実現する場合に、\ ``UriBuilder``\ ではなく\ ``ConversionService``\ で変換処理を実装する理由を説明する。 | \ ``HTTP Service Clients``\ を利用した場合も\ ``UriBuilder``\ の適用は行われるため、カスタマイズした\ ``UriBuilder``\ も動作するが、\ ``HTTP Service Clients``\ で定義しているデータ型の変換にSpring Frameworkの型変換の仕組みである\ ``ConversionService``\ が利用されており、その\ ``ConversionService``\ が\ ``UriBuilder``\ より先に動作するため、意図した変換が行われない。 | 具体的には、\ ``UriBuilder``\ より先に\ ``ConversionService``\ がURIテンプレート変数を文字列型に変換するため、\ ``ConversionService``\ の後に動作する\ ``UriBuilder``\ は期待するデータ型(\ ``LocalDate``\ 等)ではなく文字列型で変換処理が呼び出され、意図したフォーマットでの変換が行われない。 | したがって、\ ``HTTP Service Clients``\ を利用する場合は、\ ``ConversionService``\ を利用して変換処理を実装する必要がある。 | | 以下は、\ ``HTTP Service Clients``\ でURIテンプレート変数の値(\ ``LocalDate``\ )を、特定のフォーマット文字列に変換する\ ``ConversionService``\ の実装例である。 * URIテンプレート:\ ``http://localhost:8080/api/order/{date}``\ * URIテンプレート変数の値:\ ``LocalDate.of(2025, 1, 1);``\ * 送信時のURL:\ ``http://localhost:8080/api/order/20250101``\ | \ **Bean定義ファイルの定義例(インターフェース単位で設定・登録する場合)**\ \ ``HttpServiceProxyFactory``\ を利用したインターフェース単位での設定方法は、以下の通りである。 - \ ``ApplicationContextConfig.java``\ .. code-block:: java private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); // (1) @Bean("dateConversionService") public ConversionService dateConversionService() { var conversionService = new DefaultConversionService(); // (2) conversionService.addConverter(LocalDate.class, String.class, d -> formatter.format(d)); // (3) return conversionService; } @Bean("userApi") public UserApi userApi() { RestClientAdapter adapter = RestClientAdapter.create(restClient()); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter) .conversionService(dateConversionService()).build(); // (4) return factory.createClient(UserApi.class); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.springframework.core.convert.ConversionService``\ のBean定義を行う。 * - | (2) - | \ ``org.springframework.core.convert.support.DefaultConversionService``\ のインスタンスを生成する。 * - | (3) - | \ ``DefaultConversionService``\ の\ ``addConverter``\ メソッドを使用して、\ ``Converter``\ を登録する。 | 引数には、変換前の型、変換後の型、変換処理を設定する。 * - | (4) - | \ ``HttpServiceProxyFactory.Builder``\ の\ ``conversionService``\ メソッドを使用して、(1)の\ ``ConversionService``\ を登録する。 | \ **Bean定義ファイルの定義例(グループ単位で設定を集約して一括登録する場合)**\ \ ``@ImportHttpServices``\ アノテーションと\ ``HttpServiceGroupConfigurer``\ を利用してグループ単位で一括で設定を行う場合は、以下の通りである。 - \ ``HttpServiceClientsConfig.java``\ .. code-block:: java private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); // (1) @Bean("dateConversionService") public ConversionService dateConversionService() { var conversionService = new DefaultConversionService(); conversionService.addConverter(LocalDate.class, String.class, d -> formatter.format(d)); return conversionService; } @Bean @Order(0) RestClientHttpServiceGroupConfigurer defaults() { return groups -> groups.forEachGroup((group, clientBuilder, factoryBuilder) -> { clientBuilder.baseUrl(url) .requestFactory(new HttpComponentsClientHttpRequestFactory()) .defaultHeader("Default-Header", "TestHeader") .requestInterceptor((request, body, execution) -> { if (logger.isInfoEnabled()) { var requestBody = new String(body, StandardCharsets.UTF_8); logger.info("Request Header {}", request.getHeaders()); logger.info("Request Body {}", requestBody); } ClientHttpResponse response = execution.execute(request, body); if (logger.isInfoEnabled()) { logger.info("Response Header {}", response.getHeaders()); logger.info("Response Status Code {}", response.getStatusCode()); } return response; }); factoryBuilder.conversionService(dateConversionService()); // (2) }); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.springframework.core.convert.ConversionService``\ のBean定義を行う。 | 実装に関しては、インターフェース単位で設定・登録する場合と同様である。 * - | (2) - | 引数で渡される\ ``HttpServiceProxyFactory.Builder``\ の\ ``conversionService``\ メソッドを使用して、(1)の\ ``ConversionService``\ を登録する。 | .. Spring7.0対応:exchangeAdapterDecoratorを使った実装 .. _HttpServiceClientsHowToExtendExchangeAdapterDecorator: HTTP Service Clientsの共通処理 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``HTTP Service Clients``\ では\ ``exchangeAdapterDecorator``\ を利用することで、\ ``HTTP Service Clients``\ での共通処理を実装することができる。 | \ ``RestClient``\ や\ ``RestTemplate``\ で利用する\ ``ClientHttpRequestInterceptor``\ と異なり、リクエストの送信前後ではなく\ ``HTTP Service Clients``\ のアダプタの実行前後に処理が実行される。 | そのため、\ ``HTTP Service Clients``\ の例外処理や、返却値の加工などの共通処理を実装することができる。 | Spring Frameworkでは上記の仕組みを利用して、404エラー(NOT_FOUND)の場合にもエラーとせずレスポンスボディを\ ``null``\ で返却する機能を提供している。 | | 以下にて、\ ``exchangeAdapterDecorator``\ の実装について説明を行う。 | 実装例は、Spring Frameworkが提供する\ ``NotFoundRestClientAdapterDecorator``\ を利用した例となり、404エラー(NOT_FOUND)の場合にもエラーとせずレスポンスボディを\ ``null``\ で返却する設定である。 | \ **Bean定義ファイルの定義例(インターフェース単位で設定・登録する場合)**\ \ ``HttpServiceProxyFactory``\ を利用したインターフェース単位での設定方法は、以下の通りである。 - \ ``ApplicationContextConfig.java``\ .. code-block:: java @Bean("userApi") public UserApi userApi() { RestClientAdapter adapter = RestClientAdapter.create(restClient()); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter) .exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new).build(); // (1) return factory.createClient(UserApi.class); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HttpServiceProxyFactory.Builder``\ の\ ``exchangeAdapterDecorator``\ メソッドを使用して、\ ``org.springframework.web.client.support.NotFoundRestClientAdapterDecorator``\ のインスタンスを登録する。 .. note:: \ **独自のexchangeAdapterDecoratorの定義**\ Spring Frameworkで提供している\ ``org.springframework.web.service.invoker.HttpExchangeAdapterDecorator``\ を継承した実装クラスを作成することで、独自の\ ``exchangeAdapterDecorator``\ を定義し設定することも可能である。 | \ **Bean定義ファイルの定義例(グループ単位で設定を集約して一括登録する場合)**\ \ ``@ImportHttpServices``\ アノテーションと\ ``HttpServiceGroupConfigurer``\ を利用してグループ単位で一括で設定を行う場合は、以下の通りである。 - \ ``HttpServiceClientsConfig.java``\ .. code-block:: java @Bean @Order(0) RestClientHttpServiceGroupConfigurer defaults() { return groups -> groups.forEachGroup((group, clientBuilder, factoryBuilder) -> { clientBuilder.baseUrl(url) .requestFactory(new HttpComponentsClientHttpRequestFactory()) .defaultHeader("Default-Header", "TestHeader") .requestInterceptor((request, body, execution) -> { if (logger.isInfoEnabled()) { var requestBody = new String(body, StandardCharsets.UTF_8); logger.info("Request Header {}", request.getHeaders()); logger.info("Request Body {}", requestBody); } ClientHttpResponse response = execution.execute(request, body); if (logger.isInfoEnabled()) { logger.info("Response Header {}", response.getHeaders()); logger.info("Response Status Code {}", response.getStatusCode()); } return response; }); factoryBuilder.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new); // (1) }); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 引数で渡される\ ``HttpServiceProxyFactory.Builder``\ の\ ``exchangeAdapterDecorator``\ メソッドを使用して、\ ``org.springframework.web.client.support.NotFoundRestClientAdapterDecorator``\ のインスタンスを登録する。 .. note:: \ **独自のexchangeAdapterDecoratorの定義**\ Spring Frameworkで提供している\ ``org.springframework.web.service.invoker.HttpExchangeAdapterDecorator``\ を継承した実装クラスを作成することで、独自の\ ``exchangeAdapterDecorator``\ を定義し設定することも可能である。 | .. _RestClientAppendix: Appendix -------------------------------------------------------------------------------- .. _RestClientHowToUseRestFull: \ ``RestTemplate``\ にてURIテンプレートを扱う方法と実装例 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``RestTemplate``\ にてURIテンプレートを扱うには、\ ``RestTemplate``\ の呼び出すメソッドにより対応方法が異なるため、メソッド毎に説明を行う。 | \ ``getForObject``\ メソッドと\ ``exchange``\ メソッドの違いについては、「\ :ref:`RestClientHowToUseGet`\」を参照されたい。 \ **getForObjectメソッドでの使用例**\ | \ ``getForObject``\ メソッドでURLを直接渡す場合は、\ ``RestTemplate``\ の内部で\ ``UriBuilderFactory``\ が使用されるため、URIテンプレートとURIテンプレート変数の値を使ったURL文字列の生成が自動で行われる。 | したがって、本メソッドを使用した場合は、特に意識せずURIテンプレートとURIテンプレート変数の値を引数で指定することでRESTfulなURLを実現できる。 フィールド宣言部 .. code-block:: java @Value("${api.serverUrl}/api/users/{userId}") // (1) String uriStr; メソッド内部 .. code-block:: java User user = restTemplate.getForObject(uriStr, User.class, "0001"); // (2) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | URIテンプレートの変数{userId}は、\ ``RestTemplate``\ の使用時に指定の値に変換される。 * - | (2) - | URIテンプレートの変数1つ目が\ ``getForObject``\ メソッドの第3引数に指定した値で置換され、『\ ``http://localhost:8080/api/users/0001``\ 』として処理される。 | \ **exchangeメソッドでの使用例**\ | \ ``exchange``\ メソッドで\ ``RequestEntity``\ を渡す場合は、\ ``RestTemplate``\ にて\ ``UriBuilderFactory``\ は使用されないため、URIテンプレートとURIテンプレート変数の値を使ったURL文字列の生成が自動で行われない。 | したがって、本メソッドを使用した場合は、\ ``UriComponentsBuilder``\ を使用して明示的にURLを構築する必要がある。 フィールド宣言部 .. code-block:: java @Value("${api.serverUrl}/api/users/{action}") // (1) String uriStr; メソッド内部 .. code-block:: java URI targetUri = UriComponentsBuilder.fromUriString(uriStr). buildAndExpand("create").toUri(); //(2) var user = new User(); // omitted RequestEntity requestEntity = RequestEntity .post(targetUri) .body(user); ResponseEntity responseEntity = restTemplate.exchange(requestEntity, User.class); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | URIテンプレートの変数{action}は、\ ``RestTemplate``\ の使用時に指定の値に変換される。 * - | (2) - | \ ``UriComponentsBuilder``\ を使用することで、URIテンプレートの変数1つ目が\ ``buildAndExpand``\ の引数で指定した値に置換され、『\ ``http://localhost:8080/api/users/create``\ 』のURIが作成される。 | 詳細は\ :url_spring_javadoc:`UriComponentsBuilderのJavadoc `\ を参照されたい。 | .. _AsyncDescription: 非同期通信の利用について ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 非同期通信に関しては、Spring Framework 6.0から\ ``AsyncRestTemplate``\ が削除されており、Spring Web Reactive APIの\ :url_spring_reference:`WebClient `\ を使用するように案内されているため、非同期通信を行う場合は\ ``WebClient``\ を使用する必要がある。 | \ ``WebClient``\ は、\ ``Spring WebFlux``\ に含まれるRESTクライアント実装であり、非同期かつリアクティブプログラミングをサポートしたRESTクライアント実装である。 | \ |framework_name|\ では\ ``AsyncRestTemplate``\ の代替機能として\ ``WebClient``\ を案内しているだけであり、Spring Web Reactiveを完全にサポートしているわけではない点に注意されたい。 | | \ ``WebClient``\ や、\ ``WebClient``\ で使用される\ ``Reactor Netty``\ に関する詳細は以下を参照されたい。 | \ ``WebClient``\ に関する参考情報 * \ :url_spring_reference:`WebClientのリファレンス `\ * \ :url_spring_javadoc:`WebClientのJavadoc `\ | \ ``Reactor Netty``\ に関する参考情報 * \ :url_reactor_reference:`Reactor Netty `\ | .. _AsyncSetup: 非同期通信のセットアップ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 非同期通信の利用に関しては、以下の手順でセットアップを行う。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 手順 * - | (1) - | \ ``pom.xml``\ に\ ``Spring WebFlux``\ の依存関係を追加する。 * - | (2) - | \ ``WebClient``\ のBean定義を行い、DIコンテナに登録する。 * - | (3) - | \ ``WebClient``\ を利用するコンポーネントにて、(2)のBeanをインジェクションする。 | 依存ライブラリ設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | \ ``WebClient``\ を使用するために\ ``pom.xml``\ に、Spring Frameworkのspring-webfluxライブラリを追加する。 | マルチプロジェクト構成の場合は、domainプロジェクトの\ ``pom.xml``\ に追加する。 .. code-block:: xml org.springframework spring-webflux io.projectreactor.netty reactor-netty .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``spring-webflux``\ ライブラリをdependenciesに追加する。 * - | (1) - | \ ``reactor-netty``\ ライブラリをdependenciesに追加する。 | .. _AsyncBeanDefinition: \ ``WebClient``\ のBean定義 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | \ ``WebClient``\ のBean定義を行い、DIコンテナに登録する。 | \ **Bean定義の実装例(WebClientConfig.java)**\ .. code-block:: java @Configuration public class WebClientConfig { @Bean public WebClient defaultWebClient() { // (1) return WebClient.builder().build(); } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``WebClient.Builder``\ を\ ``build``\ することで\ ``WebClient``\ を生成しBeanとして登録する。 | \ ``WebClient.Builder``\ はオプションを付けることでカスタマイズすることが可能である。 | 設定できるオプションは以下の通り。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 30 70 * - Configuration - 説明 * - | uriBuilderFactory - | ベースURLとして使用するUriBuilderFactoryをカスタマイズ。 * - | defaultUriVariables - | URIテンプレートを展開する際に使用するデフォルト値。 * - | defaultHeader - | リクエストのHeader。 * - | defaultCookie - | リクエストごとのCookie。 * - | defaultRequest - | リクエストをカスタマイズ。 * - | filter - | リクエスト時に使用されるFilter * - | exchangeStrategies - | HTTP MessageのReader/Writerをカスタマイズ。 * - | clientConnector - | HTTP Client ライブラリを設定。 | 非同期通信のデータ登録 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 非同期通信を利用したケースとして、同期処理の中でデータ登録のみを非同期で通信する実装例を説明する。 | ユースケースとしては、処理を完了させるまでに時間がかかるREST APIへのアクセスを非同期で行うケースを想定したものである。 | | なお、本項では非同期通信の説明として\ ``CompletableFuture``\ を使用した実装にて説明を行っているが、\ ``Mono``\ や\ ``Flux``\ のみを使用した実装も可能である。 | \ ``Mono``\ や\ ``Flux``\ の仕様については、以下を参照されたい。 * \ :url_reactor_javadoc:`reactor.core.publisher.Mono (API Document) `\ * \ :url_reactor_javadoc:`reactor.core.publisher.Flux (API Document) `\ | \ **CompletableFutureを利用したデータの登録**\ | \ ``java.util.concurrent.CompletableFuture``\ は、JDKが提供する非同期処理を行うためのクラスである。 | Spring Frameworkの\ ``WebClient``\ などのリアクティブAPIと統合して利用する場合は、\ ``Mono``\ や\ ``Flux``\ からの結果をバイパスするために利用される。 | 実際には\ ``Mono``\ や\ ``Flux``\ の処理が動作した後に\ ``CompletableFuture``\ にデータを渡されており、\ ``CompletableFuture``\ がリアクティブ処理を隠蔽しているため、従来型の非同期処理として実装を行える。 | \ ``CompletableFuture``\ の詳しい仕様は、「\ :url_javase17:`CompletableFutureのJavadoc `\ 」を参照されたい。 | .. code-block:: java CompletableFuture> future = // (1) webClient.post() // (2) .uri(url) .bodyValue(users) // (3) .retrieve() // (4) .toBodilessEntity() // (5) .toFuture(); // (6) future.handle((r, t) -> { // (7) if (t == null) { // (8) // 成功のレスポンスが返ってきた場合の処理を実装 } else { // (9) // エラーが発生した場合の処理を実装 } return null; }); // omitted .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 返却値に\ ``CompletableFuture``\ を指定し、非同期通信のレスポンスを受け取る。 | 上記の例では、レスポンスボディは不要なため\ ``CompletableFuture``\ の型引数には、\ ``ResponseEntity``\ を指定している。 * - | (2) - | \ ``post``\ メソッドを使用してHTTPメソッドにPOSTを設定する。 * - | (3) - | \ ``RequestBodyUriSpec``\ の\ ``bodyValue``\ メソッドを使用してリクエストボディを設定する。 * - | (4) - | \ ``RequestBodyUriSpec``\ の\ ``retrieve``\ メソッドを使用してレスポンスのストリームを作成する。 * - | (5) - | \ ``ResponseSpec``\ の\ ``toBodilessEntity``\ メソッドを使用して、ストリームで扱うデータ型を指定する。 | 上記の場合は、レスポンスボディを扱わないため\ ``Mono>``\ 型のレスポンスのストリームを取得している。 * - | (6) - | \ ``Mono``\ の\ ``toFuture``\ メソッドを使用して、\ ``CompletableFuture``\ 型に変換を行う。 | このタイミングで、内部的に\ ``Mono``\ の\ ``subscribe``\ が実行され、非同期通信が開始される。 * - | (7) - | \ ``CompletableFuture``\ の\ ``handle``\ メソッドに、処理結果に応じた処理を実装する。 | \ ``handle``\ メソッドは、正常終了した場合と異常終了した場合の両方で呼び出されるため、エラーが発生したかどうかは例外の有無で判断を行う。 | 上記の場合、\ ``handle``\ メソッドの関数の引数には、\ ``ResponseEntity``\ と\ ``Throwable``\ が渡されるため、第二引数の\ ``Throwable``\ を参照してエラーの有無を判断している。 .. tip:: 同期通信に変更したい場合は、\ ``handle``\ メソッドからメソッドチェーンで\ ``get``\ メソッドを呼び出すことで処理結果の待ち合わせを行うことができる。 * - | (8) - | 成功時の処理を実装する。 * - | (9) - | エラーが発生した場合の処理を実装する。 | .. _AsyncHowToUseErrorHandling: エラーハンドリング """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 非同期通信でのエラーハンドリングの方法について説明を行う。 | 非同期通信のエラーハンドリングは、大きく2つ考慮すべき点がある。 | 1つ目は、サーバとの通信結果でエラーが発生している場合、2つ目はレスポンスを受信した後にデータをストリーム化して処理している最中にエラーが発生している場合の2つがある。 | それぞれのケースについて、以下で説明を行う。 サーバとの通信結果をハンドリングする方法 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | \ ``WebClient``\ の\ ``StatusHandler``\ は、HTTPステータスコードをもとにエラーの判定を行うためのインタフェースである。 | したがって、エラー全体をハンドリングする仕組みではないため、その点は留意する必要がある。 | \ ``RestClient``\ と同様で、アプリケーション全体でエラーハンドリングを行う場合と、リクエスト毎でエラーハンドリングを行う場合の2つがあるため、それぞれについて説明を行う。 | \ **アプリケーション全体でエラーハンドリングを行う方法**\ アプリケーション全体でエラーハンドリングを行う場合は、\ ``WebClient``\ を生成するタイミングで\ ``StatusHandler``\ を設定し、エラーの判定とエラー処理の実装を行う。 - \ ``WebClientConfig.java``\ .. code-block:: java @Bean("webClient") public WebClient webClient() { return WebClient.builder() .defaultStatusHandler(HttpStatusCode::isError, response -> { // (1) // エラー時の処理を実装 return Mono.error(new CustomException(response.statusCode().value())); }).build(); } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``WebClient.Builder``\ の\ ``defaultStatusHandler``\ メソッドに\ ``Predicate``\ と、\ ``Function>``\ を設定する。 | 上記の実装例では、\ ``HttpStatusCode``\ がサーバエラー(5xx系)及びクライアントエラー(4xx系)の場合、独自例外にHTTPステータスコードを設定して返却するように実装している。 | なお、ストリーム中にエラーを返却する場合は、\ ``Mono``\ の\ ``error``\ メソッドを使用して返却する必要がある。 | \ **リクエスト毎にエラーハンドリングを行う方法**\ リクエスト毎にエラーハンドリングを行う場合は、非同期通信を行う際に\ ``ResponseSpec``\ の\ ``onStatus``\ メソッドを使用して、エラーの判定とエラー処理の実装を行う。 .. code-block:: java CompletableFuture> future = webClient.post() .uri(url) .bodyValue(users) .retrieve() .onStatus(HttpStatusCode::is5xxServerError, response -> { // (1) // エラー時の処理を実装 return Mono.error(new CustomException(response.statusCode().value())); }) .toBodilessEntity() .toFuture(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | サーバエラー(5xx系)のHTTPステータスコードが返却された場合のエラーハンドリングを行う。 | \ ``defaultStatusHandler``\ の実装と同様に、\ ``onStatus``\ メソッドに\ ``Predicate``\ と\ ``Function>``\ を設定する。 | なお、\ ``onStatus``\ メソッドの記載がある場合は、\ ``defaultStatusHandler``\ の実装より優先されて処理される。 | したがって、上記の実装例ではサーバエラー(5xx系)のHTTPステータスコードが返却された場合にのみ適用され、クライアントエラー(4xx系)のHTTPステータスコードが返却された場合は\ ``defaultStatusHandler``\ の実装が適用される。 | ストリーム中や\ ``StatusHandler``\ で発生した例外をハンドリングする方法 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 最終的なエラーのハンドリングは、``CompletableFuture``\ の\ ``handle``\ メソッドにてハンドリングを行う必要がある。 .. code-block:: java CompletableFuture> future = webClient.post() .uri(url) .bodyValue(users) .retrieve() .toBodilessEntity() .toFuture(); future.handle((r, t) -> { // (1) if (t == null) { // 成功のレスポンスが返ってきた場合の処理を実装 } else { // エラーが発生した場合の処理を実装 } return null; }); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``CompletableFuture``\ の\ ``handle``\ メソッドにエラー時の処理を実装する。 | 非同期で動作している特性上、画面へのエラー通知は行えないため、エラー通知や復帰処理等を行う場合は\ ``handle``\ メソッドにて実装を行う必要がある。 | リクエスト送信前の共通処理の適用 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``org.springframework.web.reactive.function.client.ExchangeFilterFunction``\ を使用した共通処理の実装方法について説明を行う。 リクエスト送信前の共通処理の実装 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | \ ``org.springframework.web.reactive.function.client.ExchangeFilterFunction``\ を実装することで、サーバとの通信処理前に任意の処理を実行させることができる。 | ここでは単純な例として、通信前のロギング処理の実装例を紹介する。 \ **通信ログ出力の実装例**\ .. code-block:: java package com.example.webclient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFunction; import reactor.core.publisher.Mono; public class WebClientExchangeFilterFunction implements ExchangeFilterFunction { // (1) private static final Logger LOGGER = LoggerFactory.getLogger( WebClientExchangeFilterFunction.class); @Override public Mono filter(ClientRequest request, ExchangeFunction next) { LOGGER.info("External Request to {}", request.url()); // (2) return next.exchange(request); // (3) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ExchangeFilterFunction``\ インタフェースを実装する。 * - | (2) - | 非同期リクエストを送信する前に実行する処理を実装する。 | 上記の実装例では、通信先のURLを出力している。 * - | (3) - | 次の\ ``ExchangeFilterFunction``\ を呼び出す。 | \ **Bean定義の実装例(WebClientConfig.java)**\ .. code-block:: java @Configuration public class WebClientConfig { @Bean public WebClient loggingWebClient() { // @formatter:off WebClient webClient = WebClient.builder() .filter(new LoggingExchangeFilterFunction()) // (1) .build(); // @formatter:on return webClient; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ExchangeFilterFunction``\ の実装クラスをfilterに登録する。 .. note:: 複数のFilterを登録したい場合は、\ ``filters``\ を使用してFilterを登録する。 .. code-block:: java WebClient webClient = WebClient.builder().filters(f -> { f.add(0, new FirstExchangeFilterFunction()); f.add(1, new SecondExchangeFilterFunction()); f.add(2, new ThirdExchangeFilterFunction()); }).build(); リストに登録された順番に処理されるようになる。 | .. raw:: latex \newpage