RESTクライアント(HTTPクライアント) ================================================================================ .. only:: html .. contents:: 目次 :depth: 3 :local: | .. _RestClientOverview: Overview -------------------------------------------------------------------------------- 本節では、Spring Frameworkが提供する\ ``org.springframework.web.client.RestTemplate``\ を使用してRESTful Web Service(REST API)を呼び出す実装方法について説明する。 | .. _RestClientOverviewRestTemplate: \ ``RestTemplate``\ とは ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``RestTemplate``\ は、REST API(Web API)を呼び出すためのメソッドを提供するクラスであり、Spring Frameworkが提供するHTTPクライアントである。 具体的な実装方法の説明を行う前に、\ ``RestTemplate``\ がどのようにREST API(Web API)にアクセスしているかを説明する。 .. figure:: ./images_RestClient/RestClientOverview.png :alt: Constitution of RestTemplate :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) - | \ ``RestTemplate``\ - | \ ``HttpMessageConverter``\ を使用して、Javaオブジェクトをリクエストボディに設定する電文(JSON等)に変換する。 * - | (3) - | - | \ ``ClientHttpRequestFactory``\ から\ ``ClientHttpRequest``\ を取得して、電文(JSON等)の送信依頼を行う。 * - | (4) - | \ ``ClientHttpRequest``\ - | 電文(JSON等)をリクエストボディに設定して、REST API(Web API)にHTTP経由でリクエストを行う。 * - | (5) - | \ ``RestTemplate``\ - | \ ``ResponseErrorHandler``\ を使用して、HTTP通信のエラー判定及びエラー処理を行う。 * - | (6) - | \ ``ResponseErrorHandler``\ - | \ ``ClientHttpResponse``\ からレスポンスデータを取得して、エラー判定及びエラー処理を行う。 * - | (7) - | \ ``RestTemplate``\ - | \ ``HttpMessageConverter``\ を使用して、レスポンスボディに設定されている電文(JSON等)をJavaオブジェクトに変換する。 * - | (8) - | - | REST API(Web API)の呼び出し結果(Javaオブジェクト)をアプリケーションへ返却する。 .. note:: \ **非同期処理への対応**\ REST APIからの応答を別スレッドで処理したい場合(非同期で処理したい場合)は、\ ``RestTemplate``\ の代わりに\ ``org.springframework.web.reactive.function.client.WebClient``\ を使用する。 非同期処理の実装例については、\ :ref:`RestClientAsync`\ を参照されたい。 | .. _RestClientOverviewHttpMessageConverter: \ ``HttpMessageConverter``\ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``org.springframework.http.converter.HttpMessageConverter``\ は、アプリケーションで扱うJavaオブジェクトとサーバと通信するための電文(JSON等)を相互に変換するためのインタフェースである。 \ ``RestTemplate``\ を使用した場合、デフォルトで以下の\ ``HttpMessageConverter``\ の実装クラスが登録される。 .. tabularcolumns:: |p{0.05\linewidth}|p{0.25\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}| .. list-table:: \ **デフォルトで登録されるHttpMessageConverter**\ :header-rows: 1 :widths: 5 25 55 15 :class: longtable * - 項番 - クラス名 - 説明 - サポート型 * - | (1) - | \ ``org.springframework.http.converter.``\ | \ ``ByteArrayHttpMessageConverter``\ - | 「HTTPボディ(テキスト or バイナリデータ)⇔バイト配列」変換用のクラス。 | デフォルトですべてのメディアタイプ(\ ``*/*``\ )をサポートする。 - | \ ``byte[]``\ * - | (2) - | \ ``org.springframework.http.converter.``\ | \ ``StringHttpMessageConverter``\ - | 「HTTPボディ(テキスト)⇔文字列」変換用のクラス。 | デフォルトですべてのテキストメディアタイプ(\ ``text/*``\ )をサポートする。 - | \ ``String``\ * - | (3) - | \ ``org.springframework.http.converter.``\ | \ ``ResourceHttpMessageConverter``\ - | 「HTTPボディ(バイナリデータ)⇔Springのリソースオブジェクト」変換用のクラス。 | デフォルトですべてのメディアタイプ(\ ``*/*``\ )をサポートする。 - | \ ``Resource``\ [#p1]_ * - | (4) - | \ ``org.springframework.http.converter.xml.``\ | \ ``SourceHttpMessageConverter``\ - | 「HTTPボディ(XML)⇔XMLソースオブジェクト」変換用のクラス。 | デフォルトでXML用のメディアタイプ(\ ``text/xml``\ ,\ ``application/xml``\ ,\ ``application/*-xml``\ )をサポートする。 - | \ ``Source``\ [#p2]_ * - | (5) - | \ ``org.springframework.http.converter.support.``\ | \ ``AllEncompassingFormHttpMessageConverter``\ - | 「HTTPボディ⇔\ ``MultiValueMap``\ オブジェクト」変換用のクラス。 | \ ``FormHttpMessageConverter``\ の拡張クラスで、multipartのパートデータとしてXMLとJSONへの変換がサポートされている。 | デフォルトでフォームデータ用のメディアタイプ(\ ``application/x-www-form-urlencoded``\ ,\ ``multipart/form-data``\ )をサポートする。 * メディアタイプが\ ``application/x-www-form-urlencoded``\ の場合、\ ``MultiValueMap``\ として読込/書込される。 * メディアタイプが\ ``multipart/form-data``\ の場合、\ ``MultiValueMap``\ として書込され、\ ``Object``\ は\ ``AllEncompassingFormHttpMessageConverter``\ 内に別途設定される\ ``HttpMessageConveter``\ で変換される。(注意: Note 参照) | デフォルトで登録されるパートデータ変換用の\ ``HttpMessageConveter``\ は、\ `AllEncompassingFormHttpMessageConverter `_\と `FormHttpMessageConverter `_\ のソースを参照されたい。なお、任意の\ ``HttpMessageConverter``\ を登録することもできる。 - | \ ``MultiValueMap``\ [#p3]_ .. note:: \ **AllEncompassingFormHttpMessageConverterのメディアタイプがmultipart/form-dataの場合について**\ メディアタイプが\ ``multipart/form-data``\ の場合、「\ ``MultiValueMap``\ オブジェクト から HTTPボディ」への変換は可能だが、「HTTPボディ から \ ``MultiValueMap``\ オブジェクト」への変換は現状サポートされていない。よって、「HTTPボディ から \ ``MultiValueMap``\ オブジェクト」への変換を行いたい場合は、独自に実装する必要がある。 .. tabularcolumns:: |p{0.05\linewidth}|p{0.25\linewidth}|p{0.55\linewidth}|p{0.15\linewidth}| .. list-table:: \ **依存ライブラリがクラスパス上に存在する場合に登録されるHttpMessageConverter**\ :header-rows: 1 :widths: 5 25 55 15 :class: longtable * - 項番 - クラス名 - 説明 - サポート型 * - | (6) - | \ ``org.springframework.http.converter.feed.``\ | \ ``AtomFeedHttpMessageConverter``\ - | 「HTTPボディ(Atom)⇔Atomフィードオブジェクト」変換用のクラス。 | デフォルトでATOM用のメディアタイプ(\ ``application/atom+xml``\ )をサポートする。 | (ROMEがクラスパスに存在する場合に登録される) - | \ ``Feed``\ [#p4]_ * - | (7) - | \ ``org.springframework.http.converter.feed.``\ | \ ``RssChannelHttpMessageConverter``\ - | 「HTTPボディ(RSS)⇔Rssチャネルオブジェクト」変換用のクラス。 | デフォルトでRSS用のメディアタイプ(\ ``application/rss+xml``\ )をサポートする。 | (ROMEがクラスパスに存在する場合に登録される) - | \ ``Channel``\ [#p5]_ * - | (8) - | \ ``org.springframework.http.converter.json.``\ | \ ``MappingJackson2HttpMessageConverter``\ - | 「HTTPボディ(JSON)⇔JavaBean」変換用のクラス。 | デフォルトでJSON用のメディアタイプ(\ ``application/json``\ ,\ ``application/*+json``\ )をサポートする。 | (Jackson2がクラスパスに存在する場合に登録される) - | \ ``Object``\ (JavaBean) | \ ``Map``\ * - | (9) - | \ ``org.springframework.http.converter.xml.``\ | \ ``MappingJackson2XmlHttpMessageConverter``\ - | 「HTTPボディ(XML)⇔JavaBean」変換用のクラス。 | デフォルトでXML用のメディアタイプ(\ ``text/xml``\ ,\ ``application/xml``\ ,\ ``application/*-xml``\ )をサポートする。 | (Jackson-dataformat-xmlがクラスパスに存在する場合に登録される) - | \ ``Object``\ (JavaBean) | \ ``Map``\ * - | (10) - | \ ``org.springframework.http.converter.xml.``\ | \ ``Jaxb2RootElementHttpMessageConverter``\ - | 「HTTPボディ(XML)⇔JavaBean」変換用のクラス。 | デフォルトでXML用のメディアタイプ(\ ``text/xml``\ ,\ ``application/xml``\ ,\ ``application/*-xml``\ )をサポートする。 | (JAXBがクラスパスに存在する場合に登録される) .. note:: Java SE 17環境にてJAXBをクラスパスに登録するには\ :ref:`remove-jaxb-from-java11`\ を参照されたい。 - | \ ``Object``\ (JavaBean) * - | (11) - | \ ``org.springframework.http.converter.json.``\ | \ ``GsonHttpMessageConverter``\ - | 「HTTPボディ(JSON)⇔JavaBean」変換用のクラス。 | デフォルトでJSON用のメディアタイプ(\ ``application/json``\ ,\ ``application/*+json``\ )をサポートする。 | (Gsonがクラスパスに存在する場合に登録される) - | \ ``Object``\ (JavaBean) | \ ``Map``\ .. [#p1] \ ``org.springframework.core.io``\ パッケージのクラス .. [#p2] \ ``jakarta.xml.transform``\ パッケージのクラス .. [#p3] \ ``org.springframework.util``\ パッケージのクラス .. [#p4] \ ``com.rometools.rome.feed.atom``\ パッケージのクラス .. [#p5] \ ``com.rometools.rome.feed.rss``\ パッケージのクラス | .. _RestClientOverviewClientHttpRequestFactory: \ ``ClientHttpRequestFactory``\ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``RestTemplate``\ は、サーバとの通信処理を以下の3つのインタフェースの実装クラスに委譲することで実現している。 * \ ``org.springframework.http.client.ClientHttpRequestFactory``\ * \ ``org.springframework.http.client.ClientHttpRequest``\ * \ ``org.springframework.http.client.ClientHttpResponse``\ この3つのインタフェースのうち、開発者が意識するのは\ ``ClientHttpRequestFactory``\ である。\ ``ClientHttpRequestFactory``\ は、サーバとの通信処理を行うクラス(\ ``ClientHttpRequest``\ と \ ``ClientHttpResponse``\ インタフェースの実装クラス)を解決する役割を担っている。 なお、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.``\ | \ ``SimpleClientHttpRequestFactory``\ - | Java SE標準の\ `HttpURLConnection `_\ のAPIを使用して通信処理(同期、非同期)を行うための実装クラス。(デフォルトで使用される実装クラス) * - | (2) - | \ ``org.springframework.http.client.``\ | \ ``HttpComponentsClientHttpRequestFactory``\ - | \ `Apache HttpComponents HttpClient `_\ のAPIを使用して同期型の通信処理を行うための実装クラス。(HttpClient 5.2以上が必要) * - | (3) - | \ ``org.springframework.http.client.``\ | \ ``OkHttpClientHttpRequestFactory``\ - | \ `Square OkHttp `_\ のAPIを使用して通信処理(同期、非同期)を行うための実装クラス。 .. note:: \ **使用するClientHttpRequestFactoryの実装クラスについて**\ \ ``RestTemplate``\ が使用するデフォルト実装は\ ``SimpleClientHttpRequestFactory``\ であり、本ガイドラインでも\ ``SimpleClientHttpRequestFactory``\ を使用した際の実装例となっている。Java SEの\ ``HttpURLConnection``\ で要件が満たせない場合は、Netty、Apache Http Componentsなどのライブラリの利用を検討されたい。 | .. _RestClientOverviewResponseErrorHandler: \ ``ResponseErrorHandler``\ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``RestTemplate``\ は、サーバとの通信エラーのハンドリングを\ ``org.springframework.web.client.ResponseErrorHandler``\ インタフェースに委譲することで実現している。 \ ``ResponseErrorHandler``\ には、 * エラー判定を行うメソッド(\ ``hasError``\ ) * エラー処理を行うメソッド(\ ``handleError``\ ) が定義されており、Spring Frameworkはデフォルト実装として\ ``org.springframework.web.client.DefaultResponseErrorHandler``\ を提供している。 \ ``DefaultResponseErrorHandler``\ は、サーバから応答されたHTTPステータスコードの値によって以下のようなエラー処理を行う。 * レスポンスコードが正常系(2xx)の場合は、エラー処理は行わない。 * レスポンスコードがクライアントエラー系(4xx)の場合は、\ ``org.springframework.web.client.HttpClientErrorException``\ を発生させる。 * レスポンスコードがサーバエラー系(5xx)の場合は、\ ``org.springframework.web.client.HttpServerErrorException``\ を発生させる。 * レスポンスコードが未定義のコード(ユーザ定義のカスタムコード)の場合は、\ ``org.springframework.web.client.UnknownHttpStatusCodeException``\ を発生させる。 .. note:: \ **エラー時のレスポンスデータの取得方法**\ エラー時のレスポンスデータ(HTTPステータスコード、レスポンスヘッダ、レスポンスボディなど)は、例外クラスのgetterメソッドを呼び出すことで取得することができる。 | .. _RestClientOverviewClientHttpRequestInterceptor: \ ``ClientHttpRequestInterceptor``\ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``org.springframework.http.client.ClientHttpRequestInterceptor``\ は、サーバとの通信の前後に共通的な処理を実装するためのインタフェースである。 \ ``ClientHttpRequestInterceptor``\ を使用すると、 * サーバとの通信ログ * 認証ヘッダの設定 といった共通的な処理を\ ``RestTemplate``\ に適用することができる。 .. note:: \ **ClientHttpRequestInterceptorの動作仕様**\ \ ``ClientHttpRequestInterceptor``\ は複数適用することができ、指定した順番でチェーン実行される。これはサーブレットフィルタの動作によく似ており、最後に実行されるチェーン先として\ ``ClientHttpRequest``\ によるHTTP通信処理が登録されている。例えば、ある条件に一致した際にサーバとの通信処理をキャンセルしたいという要件があった場合は、チェーン先を呼びださなければよい。 この仕組みを活用すると、 * サーバとの通信の閉塞 * 通信処理のリトライ といった処理を適用することもできる。 | .. _RestClientHowToUse: How to use -------------------------------------------------------------------------------- 本節では、\ ``RestTemplate``\ を使用したクライアント処理の実装方法について説明する。 .. note:: \ **RestTemplateがサポートするHTTPメソッドについて**\ 本ガイドラインでは、GETメソッドとPOSTメソッドを使用したクライアント処理の実装例のみを紹介するが、\ ``RestTemplate``\ は他のHTTPメソッド(PUT, PATCH, DELETE, HEAD, OPTIONSなど)もサポートしており、同じような要領で使用することができる。 詳細は\ `RestTemplateのJavadoc `_\ を参照されたい。 | .. _RestClientHowToUseSetup: \ ``RestTemplate``\ のセットアップ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``RestTemplate``\ を使用する場合は、\ ``RestTemplate``\ をDIコンテナに登録し、\ ``RestTemplate``\ を利用するコンポーネントにインジェクションする。 | 依存ライブラリ設定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``RestTemplate``\ を使用するために\ ``pom.xml``\ に、Spring Frameworkのspring-webライブラリを追加する。 | マルチプロジェクト構成の場合は、domainプロジェクトの\ ``pom.xml``\ に追加する。 .. code-block:: xml org.springframework spring-web .. note:: 上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存している\ `Spring Boot `_\ で管理されている。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | Spring Frameworkの\ ``spring-web``\ ライブラリをdependenciesに追加する。 | \ ``RestTemplate``\ のbean定義 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``RestTemplate``\ のbean定義を行い、DIコンテナに登録する。 \ **bean定義ファイル(applicationContext.xml)の定義例**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestTemplate``\ をデフォルト設定のまま利用する場合は、デフォルトコンストラクタを使用してbeanを登録する。 .. note:: \ **RestTemplateのカスタマイズ方法**\ HTTP通信処理をカスタマイズする場合は、以下のようなbean定義となる。 .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestFactory``\ のbean定義を行う。 | 本ガイドラインではタイムアウトの設定をカスタマイズする方法を紹介している。 | 詳細は\ :ref:`RestClientHowToUseTimeoutSettings`\ を参照されたい。 * - | (2) - | \ ``ClientHttpRequestFactory``\ を引数に指定するコンストラクタを使用してbeanを登録する。 なお、\ ``HttpMessageConverter``\ 、\ ``ResponseErrorHandler``\ 、\ ``ClientHttpRequestInterceptor``\ のカスタマイズ方法については、 * \ :ref:`RestClientHowToExtendHttpMessageConverter`\ * \ :ref:`RestClientHowToUseErrorHandlingResponseEntity`\ * \ :ref:`RestClientHowToExtendClientHttpRequestInterceptor`\ を参照されたい。 | \ ``RestTemplate``\ の利用 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``RestTemplate``\ を利用する場合は、DIコンテナに登録されている\ ``RestTemplate``\ をインジェクションする。 \ **RestTemplateのインジェクション例**\ .. code-block:: java @Service public class AccountServiceImpl implements AccountService { @Inject RestTemplate restTemplate; // omitted } | .. _RestClientHowToUseGet: GETリクエストの送信 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``RestTemplate``\ は、GETリクエストを行うためのメソッドを複数提供している。 * 通常は\ ``getForObject``\ メソッド又は\ ``getForEntity``\ メソッドを使用する。 * 任意のヘッダを設定するなどリクエストに細かい設定を行いたい場合は、\ ``org.springframework.http.RequestEntity``\ と\ ``exchange``\ メソッドを使用する。 | \ ``getForObject``\ メソッドを使用した実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" レスポンスボディのみ取得できればよい場合は、\ ``getForObject``\ メソッドを使用する。 \ **getForObjectメソッドの使用例**\ フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") 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クラスへ変換された後、返却される。 | \ ``getForEntity``\ メソッドを使用した実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" HTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合は、\ ``getForEntity``\ メソッドを使用する。 \ **getForEntityメソッドの使用例**\ .. code-block:: java ResponseEntity responseEntity = restTemplate.getForEntity(uri, User.class); // (1) HttpStatus 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ステータスコードは\ ``getStatusCode``\ メソッドを用いて取得する。 * - | (3) - | レスポンスヘッダは\ ``getHeaders``\ メソッドを用いて取得する。 * - | (4) - | レスポンスボディは\ ``getBody``\ メソッドを用いて取得する。 .. note:: **ResponseEntityとは** \ ``ResponseEntity``\ はHTTPレスポンスを表すクラスで、HTTPステータスコード、レスポンスヘッダ、レスポンスボディの情報を取得することができる。 詳細は\ `ResponseEntityのJavadoc `_\ を参照されたい。 | \ ``exchange``\ メソッドを使用した実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" リクエストヘッダを指定する必要がある場合は、\ ``org.springframework.http.RequestEntity``\ を生成し\ ``exchange``\ メソッドを使用する。 \ **exchangeメソッドの使用例**\ import部 .. code-block:: java import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") URI uri; メソッド内部 .. code-block:: java RequestEntity requestEntity = RequestEntity .get(uri)//(1) .build();//(2) ResponseEntity responseEntity = restTemplate.exchange(requestEntity, User.class);//(3) User user = responseEntity.getBody();//(4) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestEntity``\ の\ ``get``\メソッドを使用し、GETリクエスト用のリクエストビルダを生成する。 | パラメータにURIを設定する。 * - | (2) - | \ ``RequestEntity.HeadersBuilder``\ の\ ``build``\メソッドを使用し、\ ``RequestEntity``\ オブジェクトを作成する。 * - | (3) - | \ ``exchange``\ メソッドを使用し、リクエストを送信する。第二引数に、レスポンスデータの型を指定する。 | レスポンスは、\ ``ResponseEntity``\ になる。型パラメータに、レスポンスデータの型を設定する。 * - | (4) - | \ ``getBody``\ メソッドを使用し、レスポンスボディのデータを取得する。 .. note:: \ **RequestEntityとは**\ \ ``RequestEntity``\ はHTTPリクエストを表すクラスで、接続URI、HTTPメソッド、リクエストヘッダ、リクエストボディを設定することができる。 詳細は\ `RequestEntityのJavadoc `_\ を参照されたい。 なお、リクエストヘッダの設定方法については、\ :ref:`RestClientHowToUseRequestHeader`\ を参照されたい。 | .. _RestClientHowToUsePost: POSTリクエストの送信 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``RestTemplate``\ は、POSTリクエストを行うためのメソッドを複数提供している。 * 通常は、\ ``postForObject``\ 、\ ``postForEntity``\ を使用する。 * 任意のヘッダを設定するなどリクエストに細かい設定を行いたい場合は、\ ``RequestEntity``\ と \ ``exchange``\ メソッドを使用する。 | \ ``postForObject``\ メソッドを使用した実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" POSTした結果としてレスポンスボディのみ取得できればよい場合は、\ ``postForObject``\ メソッドを使用する。 \ **postForObjectメソッドの使用例**\ .. code-block:: java User 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``\ メソッドを使用した場合は、戻り値はレスポンスボディの値になる。 | \ ``postForEntity``\ メソッドを使用した実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" POSTした結果としてHTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合は、\ ``postForEntity``\ メソッドを使用する。 \ **postForEntityメソッドの使用例**\ .. code-block:: java User user = new User(); // omitted ResponseEntity responseEntity = restTemplate.postForEntity(uri, user, User.class); // (1) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``postForEntity``\ メソッドも\ ``getForObject``\ メソッドと同様に簡易にPOSTリクエストを実装できる。 | \ ``postForEntity``\ メソッドを使用した場合は、戻り値は\ ``ResponseEntity``\ となる。 | レスポンスボディの値は、\ ``ResponseEntity``\ から取得する。 | \ ``exchange``\ メソッドを使用した実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" リクエストヘッダを指定する必要がある場合は、\ ``RequestEntity``\ を生成し\ ``exchange``\ メソッドを使用する。 \ **exchangeメソッドの使用例**\ import部 .. code-block:: java import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; フィールド宣言部 .. code-block:: java @Value("${api.url:http://localhost:8080/api}") URI uri; メソッド内部 .. code-block:: java User user = new User(); // omitted RequestEntity requestEntity = RequestEntity//(1) .post(uri)//(2) .body(user);//(3) ResponseEntity responseEntity = restTemplate.exchange(requestEntity, User.class);//(4) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestEntity``\ を使用して、リクエストを生成する。型パラメータに、リクエストボディに設定するデータの型を指定する。 * - | (2) - | \ ``post``\ メソッドを使用し、POSTリクエスト用のリクエストビルダを生成する。パラメータにURIを設定する。 * - | (3) - | \ ``RequestEntity.BodyBuilder``\ の\ ``body``\ メソッドを使用し、\ ``RequestEntity``\ オブジェクトを作成する。 | パラメータにリクエストボディに変換するJavaオブジェクトを設定する。 * - | (4) - | \ ``exchange``\ メソッドを使用し、リクエストを送信する。 .. note:: \ **リクエストヘッダの設定方法**\ リクエストヘッダの設定方法については、\ :ref:`RestClientHowToUseRequestHeader`\ を参照されたい。 | .. _RestClientHowToUseGetCollection: コレクション形式のデータ取得 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ サーバから応答されるレスポンスボディの電文(JSON等)がコレクション形式の場合は、以下のような実装となる。 \ **コレクション形式のデータの取得例**\ .. 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``\ メソッドで、レスポンスボディのデータを取得する。 | .. _RestClientHowToUseRequestHeader: リクエストヘッダの設定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``RequestEntity``\ と\ ``exchange``\ メソッドを使用すると、\ ``RequestEntity``\ のメソッドを使用して特定のヘッダ及び任意のヘッダを設定することができる。 | 詳細は\ `RequestEntityのJavadoc `_\ を参照されたい。 | 本ガイドラインでは、 * \ :ref:`RestClientHowToUseRequestHeaderContentType`\ * \ :ref:`RestClientHowToUseRequestHeaderAccept`\ * \ :ref:`RestClientHowToUseRequestHeaderAnyHeader`\ について説明する。 | .. _RestClientHowToUseRequestHeaderContentType: Content-Typeヘッダの設定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" サーバへデータを送信する場合は、通常Content-Typeヘッダの指定が必要となる。 \ **Content-Typeヘッダの設定例**\ .. code-block:: java User user = new User(); // omitted RequestEntity requestEntity = RequestEntity .post(uri) .contentType(MediaType.APPLICATION_JSON) // (1) .body(user); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestEntity.BodyBuilder``\ の\ ``contentType``\ メソッドを使用し、Context-Typeヘッダの値を指定する。 | 上記の実装例では、データ形式がJSONであることを示す「\ ``application/json``\ 」を設定している。 | .. _RestClientHowToUseRequestHeaderAccept: Acceptヘッダの設定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | サーバから取得するデータの形式を指定する場合は、Acceptヘッダの指定が必要となる。 | サーバが複数のデータ形式のレスポンスをサポートしていない場合は、Acceptヘッダを明示的に指定しなくてもよいケースもある。 \ **Acceptヘッダの設定例**\ .. code-block:: java User user = new User(); // omitted RequestEntity requestEntity = RequestEntity .post(uri) .accept(MediaType.APPLICATION_JSON) // (1) .body(user); .. 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``\ 」を設定している。 | .. _RestClientHowToUseRequestHeaderAnyHeader: 任意のリクエストヘッダの設定 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" サーバへアクセスするために、リクエストヘッダの設定が必要になるケースがある。 \ **任意ヘッダの設定例**\ .. code-block:: java User user = new User(); // omitted RequestEntity requestEntity = RequestEntity .post(uri) .header("Authorization", "Basic " + base64Credentials) // (1) .body(user); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RequestEntity.HeadersBuilder``\ の\ ``header``\ メソッドを使用してリクエストヘッダの名前と値を設定する。 | 上記の実装例では、AuthorizationヘッダにBasic認証に必要な資格情報を設定している。 | .. _RestClientHowToUseErrorHandling: エラーハンドリング ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. _RestClientHowToUseErrorHandlingHandleException: 例外ハンドリング(デフォルトの動作) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``RestTemplate``\ のデフォルト実装(\ ``DefaultResponseErrorHandler``\ )では、 * レスポンスコードがクライアントエラー系(4xx)の場合は、\ ``HttpClientErrorException``\ * レスポンスコードがサーバエラー系(5xx)の場合は、\ ``HttpServerErrorException``\ * レスポンスコードが未定義のコード(ユーザ定義のカスタムコード)の場合は、\ ``UnknownHttpStatusCodeException``\ が発生するため、必要に応じてこれらの例外をハンドリングする必要がある。 \ **例外ハンドリングの実装例**\ .. note:: 以下の実装例は、サーバエラーが発生した際の例外ハンドリングの一例である。 アプリケーションの要件に応じて\ **適切な例外ハンドリングを行うこと。**\ フィールド宣言部 .. code-block:: java @Value("${api.retry.maxCount}") int retryMaxCount; @Value("${api.retry.retryWaitTimeCoefficient}") int retryWaitTimeCoefficient; メソッド内部 .. code-block:: java int retryCount = 0; while (true) { try { responseEntity = restTemplate.exchange(requestEntity, String.class); if (logger.isInfoEnabled()) { logger.info("Success({}) ", responseEntity.getStatusCode()); } 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); } 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系)の場合、\ ``HttpServerErrorException``\ をキャッチする。 | .. _RestClientHowToUseErrorHandlingResponseEntity: \ ``ResponseEntity``\ の返却(エラーハンドラの拡張) """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``org.springframework.web.client.ResponseErrorHandler``\ インタフェースの実装クラスを\ ``RestTemplate``\ に設定することで、独自のエラー処理を行うことができる。 以下の例では、サーバエラー及びクライアントエラーが発生した場合でも\ ``ResponseEntity``\ を返すようにエラーハンドラを拡張している。 \ **エラーハンドラの実装クラスの作成例**\ .. code-block:: java import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.DefaultResponseErrorHandler; public class CustomErrorHandler extends DefaultResponseErrorHandler { // (1) @Override public void handleError(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``\ を拡張し、 | サーバエラー及びクライアントエラーが発生した際に例外を発生させずに\ ``ResponseEntity``\ が返るようにしている。 \ **bean定義ファイル(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``\ プロパティに\ ``ResponseErrorHandler``\ の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ステータスコードを取得して、その値に応じて処理を制御することができる。 | .. _RestClientHowToUseTimeoutSettings: 通信タイムアウトの設定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ サーバとの通信に対してタイムアウト時間を指定したい場合は、以下のようなbean定義を行う。 \ **bean定義ファイル(applicationContext.xml)の定義例**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``connectTimeout``\ プロパティにサーバとの接続タイムアウト時間(ミリ秒)を設定する。 | タイムアウト発生時は\ ``org.springframework.web.client.ResourceAccessException``\ が発生する。 * - | (2) - | \ ``readTimeout``\ プロパティにレスポンスデータの読み込みタイムアウト時間(ミリ秒)を設定する。 | タイムアウト発生時は\ ``ResourceAccessException``\ が発生する。 .. note:: \ **タイムアウト発生時の起因例外**\ \ ``ResourceAccessException``\ は起因例外をラップしており、接続タイムアウト及び読み込みタイムアウト発生時の起因例外は共に\ ``java.net.SocketTimeoutException``\ である。デフォルト実装(\ ``SimpleClientHttpRequestFactory``\ )を使用した場合は、どちらのタイムアウトが発生したかを例外クラスの種類で区別できないという点を補足しておく。 なお、他の\ ``HttpRequestFactory``\ を使用した場合の動作は未検証であるため、起因例外が上記と異なる可能性がある。他の\ ``HttpRequestFactory``\ を使用する場合は、タイムアウト時に発生する例外を把握した上で適切な例外ハンドリングを行うこと。 | .. _RestClientHowToUseHttps: SSL自己署名証明書の使用 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ テスト環境などでSSL自己署名証明書を使用する場合は、以下のように実装する。 \ **FactoryBeanの実装例**\ | \ ``RestTemplate``\ のBean定義で、コンストラクタの引数に渡す \ ``org.springframework.http.client.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.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.SSLConnectionSocketFactoryBuilder; 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 { // (16) private String keyStoreFileName; private char[] keyStorePassword; private HttpComponentsClientHttpRequestFactory factory; // (16) @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) .setSSLSocketFactory( // (3) SSLConnectionSocketFactoryBuilder.create() .setSslContext(sslContext) .setTlsVersions(TLS.V_1_3, TLS.V_1_2) .build()) .setDefaultSocketConfig( // (4) SocketConfig.custom() .setSoTimeout(Timeout.ofMinutes(1L)) .build()) .setMaxConnTotal(1) // (5) .setMaxConnPerRoute(1) // (6) .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT) // (7) .setConnPoolPolicy(PoolReusePolicy.LIFO) // (8) .setConnectionTimeToLive(TimeValue.ofMinutes(1L)) // (9) .build(); // @formatter:on // @formatter:off CloseableHttpClient httpClient = HttpClients.custom() // (10) .setConnectionManager(connectionManager) // (11) .setDefaultRequestConfig( RequestConfig.custom() .setConnectTimeout(Timeout.ofSeconds(5L)) // (12) .setResponseTimeout(Timeout.ofSeconds(10L)) // (13) .setCookieSpec(StandardCookieSpec.STRICT) // (14) .build()) .build(); // @formatter:on // (15) 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; } // (16) @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コンテキストを設定するための\ `HttpClientConnectionManager `_\ を作成する。 | ここでは、実装クラスとして\ ``org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager``\ を指定している。 * - | (3) - | (1)で作成したSSLコンテキストを設定する。 * - | (4) - | Socket Configのデフォルト値を設定する。 | \ ``Socket``\ から提供される\ ``InputStream``\ の\ ``read()``\ のブロック時間が\ `SoTimeout `_\ を越えた場合、通常、\ ``java.net.SocketTimeoutException``\ が発生するが、TCPコネクション確立後、SSLハンドシェイクを行っている間にこのタイムアウトが起きた場合には、\ ``org.apache.hc.client5.http.ConnectTimeoutException``\ が発生する。 .. note:: \ **SoTimeoutはSSLハンドシェイクが完了する十分な長さを確保すること。**\ SSLハンドシェイク完了前に\ ``SoTimeout``\ によってタイムアウトした際に、SSLハンドシェイクが中断される(SSLハンドシェイクに関する続きの通信を行わなくなる)がTCPコネクションのcloseも行われない事象を確認している。この状態になると、サーバ側で処理を破棄しない限りサーバ側はSSLハンドシェイクの続きを待つこととなってしまう。 そのため、SSLハンドシェイク中に通信障害や通信相手のハードウェア障害等に遭遇し通信相手からのパケットが届かない時間が長く続く場合(クライアント側で通信の継続を諦めなければならない場合)以外においては、\ ``SoTimeout``\ を利用したタイムアウトは避けたほうが良い。 SSLハンドシェイク完了後のHTTPSリクエストに対するレスポンスのタイムアウトには、後述の\ ``ResponseTimeout``\ が利用できるため、 * HTTPSリクエストに対する応答に関するタイムアウトには\ ``ResponseTimeout``\ * \ ``ResponseTimeout``\ の有効範囲外(HTTPSリクエスト送信前)となるSSLハンドシェイク中の応答に関するタイムアウトには\ ``SoTimeout``\ (正常稼働時にはタイムアウトしないよう長めに設定) という具合に使い分けること。なお、\ ``SoTimeout``\ のデフォルト値は3分となっている。 * - | (5) - | 最大合計接続数を設定する。 | 最大合計接続数を超える場合、後続処理はコネクションの取得を待機する。 * - | (6) - | 宛先(ホスト名 + ポート番号 及び スキーマ定義)ごとの最大接続数を設定する。 | 最大接続数を超える場合、後続処理はコネクションの取得を待機する。 * - | (7) - | \ `プール同時実行ポリシー `_\ を設定する。 * - | (8) - | \ `プールされたコネクションの再利用ポリシー `_\ を設定する。 * - | (9) - | コネクションが接続されてから切断されるまでの最大生存時間を設定する。 | 使用状況にかかわらず、\ `ConnectionTimeToLive `_\ を越えた場合にコネクションを破棄する。 * - | (10) - | 作成したSSLコンテキストを利用する \ ``org.apache.hc.client5.http.impl.classic.CloseableHttpClient``\ を作成する。 * - | (11) - | (2)で作成した\ ``HttpClientConnectionManager``\ を設定する。 * - | (12) - | 新しい接続が確立されるまでのタイムアウトのデフォルト値を設定する。 | 接続の確立に\ `ConnectTimeout `_\ 以上かかった場合、\ ``org.apache.hc.client5.http.HttpHostConnectException``\ が発生する。 * - | (13) - | レスポンスタイムアウトのデフォルト値を設定する。 | レスポンス返却に\ `ResponseTimeout `_\ 以上かかった場合、\ ``java.net.SocketTimeoutException``\ が発生する。 * - | (14) - | \ `HttpClientがサポートするCookieの仕様 `_\ を設定する。 * - | (15) - | 作成した\ ``HttpClient``\ を利用する\ ``ClientHttpRequestFactory``\ を作成する。 * - | (16) - | \ ``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のバージョンを指定するような実装を検討されたい。 | \ ``HttpClient``\ および \ ``HttpClientBuilder``\ を使用するためには、Apache HttpComponents HttpClient のライブラリが必要となる。 | 以下を \ :file:`pom.xml`\ に追加し、Apache HttpComponents HttpClient を依存ライブラリに追加する。 * \ :file:`pom.xml`\ .. code-block:: xml org.apache.httpcomponents.client5 httpclient5 .. note:: 上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存している\ `Spring Boot `_\ で管理されている。 \ **bean定義ファイル(applicationContext.xml)の定義例**\ SSL自己署名証明書を使用したSSL通信を行う \ ``RestTemplate``\ を定義する。 .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 作成した \ ``RequestFactoryBean``\ を \ ``RestTemplate``\ のコンストラクタに指定する。 | \ ``RequestFactoryBean``\ には、キーストアファイルのファイル名とパスワードを渡す。 \ **RestTemplateの使用方法**\ \ ``RestTemplate``\ の使い方については、SSL自己署名証明書を使用しない場合と同じである。 | .. _RestClientHowToUseAuthentication: Basic認証 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ サーバがBasic認証を要求する場合は、以下のように実装する。このとき、Java標準の\ ``java.util.Base64``\ を使用する。 \ **Basic認証の実装例**\ フィールド宣言部 .. code-block:: java @Value("${api.auth.username}") String username; @Value("${api.auth.password}") String password; メソッド内部 .. code-block:: java String plainCredentials = username + ":" + password; // (1) String base64Credentials = Base64.getEncoder() .encodeToString(plainCredentials.getBytes(StandardCharsets.UTF_8)); // (2) RequestEntity requestEntity = RequestEntity .get(uri) .header("Authorization", "Basic " + base64Credentials) // (3) .build(); .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ユーザ名とパスワードを「"\ ``:``\ " 」でつなげる。 * - | (2) - | (1)をバイト配列に変換して、Base64エンコードする。 * - | (3) - | AuthorizationヘッダをBasic認証の資格情報を設定する。 .. note:: Spring Security 5より、\ ``org.springframework.security.crypto.codec.Base64``\ は非推奨になったため、Java標準の\ ``java.util.Base64``\ に置き換えることを推奨する。 | .. _RestClientHowToUseFileUpload: ファイルアップロード(マルチパートリクエスト) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``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``\ に設定する。 * - | (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: ファイルダウンロード ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ ``RestTeamplate``\を使用してファイルダウンロードを行う場合は、以下のように実装する。 \ **ファイルダウンロードの実装例(ファイルサイズが小さい場合)**\ .. code-block:: java RequestEntity requestEntity = RequestEntity .get(uri) .build(); ResponseEntity responseEntity = restTemplate.exchange(requestEntity, byte[].class);//(1) byte[] downloadContent = responseEntity.getBody();//(2) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | ダウンロードファイルを指定したデータ型で扱う。ここでは、バイト配列を指定。 * - | (2) - | レスポンスボディからダウンロードしたファイルのデータを取得する。 .. warning:: \ **サイズの大きいファイルをダウンロードする際の注意点**\ サイズの大きなファイルをデフォルトで登録されている\ ``HttpMessageConverter``\ を使用して \ ``byte``\ 配列で取得すると、 \ ``java.lang.OutOfMemoryError``\ が発生する可能性がある。そのため、サイズの大きなファイルをダウンロードしたい場合は、レスポンスから \ ``InputStream``\ を取得して、ダウンロードデータを少しずつファイルに書き出す必要がある。 .. _RestClientHowToUseBigFileDownload: \ **ファイルダウンロードの実装例(ファイルサイズが大きい場合)**\ .. code-block:: java // (1) final ResponseExtractor> responseExtractor = new ResponseExtractor>() { // (2) @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); } }; // (3) ResponseEntity responseEntity = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor); if (HttpStatus.OK.equals(responseEntity.getStatusCode())) { File getFile = responseEntity.getBody(); // omitted } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``RestTemplate#execute``\ で取得されたレスポンスから、\ ``RestTemplate#execute``\ の戻り値を作成するための処理を作成する。 * - | (2) - | レスポンスボディ(\ ``InputStream``\ )からデータを読込み、ファイルを作成する。 | 作成したファイルとHTTPヘッダ、ステータスコードを \ ``ResponseEntity``\ に格納して返却する。 * - | (3) - | \ ``RestTemplate#execute``\ を使用して、ファイルのダウンロードを行う。 \ **ファイルダウンロードの実装例(ファイルサイズが大きい場合(ResponseEntityを使わない例))**\ ステータスコードの判定やHTTPヘッダの参照等が不要な場合は、 以下のように\ ``ResponseEntity``\ ではなく \ ``File``\ を返却すればよい。 .. code-block:: java final ResponseExtractor responseExtractor = new ResponseExtractor() { @Override public File extractData(ClientHttpResponse response) throws IOException { File rcvFile = File.createTempFile("rcvFile", "zip"); FileCopyUtils.copy(response.getBody(), new FileOutputStream( rcvFile)); return rcvFile; } }; File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor); // omitted | .. _RestClientHowToUseRestFull: RESTfulなURL(URIテンプレート)を扱う方法と実装例 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RESTfulなURLを扱うには、URIテンプレートを使用して実装を行えばよい。 \ **getForObjectメソッドでの使用例**\ フィールド宣言部 .. 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}は、\ ``RestTeamplate``\ の使用時に指定の値に変換される。 * - | (2) - | URIテンプレートの変数1つ目が\ ``getForObject``\ メソッドの第3引数に指定した値で置換され、『\ ``http://localhost:8080/api/users/0001``\ 』として処理される。 \ **exchangeメソッドでの使用例**\ .. code-block:: java @Value("${api.serverUrl}/api/users/{action}") // (1) String uriStr; メソッド内部 .. code-block:: java URI targetUri = UriComponentsBuilder.fromUriString(uriStr). buildAndExpand("create").toUri(); //(2) User 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}は、\ ``RestTeamplate``\の使用時に指定の値に変換される。 * - | (2) - | \ ``UriComponentsBuilder``\ を使用することで、URIテンプレートの変数1つ目が\ ``buildAndExpand``\ の引数で指定した値に置換され、『\ ``http://localhost:8080/api/users/create``\ 』のURIが作成される。 | 詳細は\ `UriComponentsBuilderのJavadoc `_\ を参照されたい。 | .. _RestClientHowToExtend: How to extend -------------------------------------------------------------------------------- 本節では、\ ``RestTemplate``\ の拡張方法について説明する。 | .. _RestClientHowToExtendHttpMessageConverter: 任意の\ ``HttpMessageConverter``\ を登録する方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ デフォルトで登録されている \ ``HttpMessageConverter``\ で電文変換の要件を満たせない場合は、任意の\ ``HttpMessageConverter``\ を登録することができる。ただし、デフォルトで登録されていた\ ``HttpMessageConverter``\ は削除されるので、必要な\ ``HttpMessageConverter``\ をすべて個別に登録する必要がある。 \ **bean定義ファイル(applicationContext.xml)の定義例**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 登録する\ ``HttpMessageConverter``\ の実装クラスをbean定義する。 * - | (2) - | \ ``messageConverters``\ プロパティに登録する\ ``HttpMessageConverter``\ のbeanをインジェクションする。 | .. _RestClientHowToExtendClientHttpRequestInterceptor: 共通処理の適用(\ ``ClientHttpRequestInterceptor``\) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | \ ``ClientHttpRequestInterceptor``\ を使用することで、サーバとの通信処理の前後に任意の処理を実行させることができる。 | ここでは、\ :ref:`RestClientHowToExtendClientHttpRequestInterceptorLogging`\ と、\ :ref:`RestClientBasicAuthorizationInterceptorBeanDefinition`\ を適用する方法を紹介する。 | .. _RestClientHowToExtendClientHttpRequestInterceptorLogging: ロギング処理 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" サーバとの通信ログを出力したい場合は、以下のような実装を行う。 \ **通信ログ出力の実装例**\ .. 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()) { String requestBody = new String(body, StandardCharsets.UTF_8); logger.info("Request Header {}", request.getHeaders()); //(2) logger.info("Request Body {}", requestBody); } ClientHttpResponse response = execution.execute(request, body); //(3) if (logger.isInfoEnabled()) { logger.info("Response Header {}", response.getHeaders()); // (4) logger.info("Response Status Code {}", response.getStatusCode()); // (5) } return response; // (6) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInterceptor``\ インタフェースを実装する。 * - | (2) - | リクエストする前に行いたい共通処理を実装する。 | 上記の実装例では、リクエストヘッダーとリクエストボディの内容をログに出力している。 * - | (3) - | \ ``intercept``\ メソッドの引数として受け取った\ ``ClientHttpRequestExecution``\ の\ ``execute``\ メソッドを実行し、リクエストの送信を実行する。 * - | (4) - | レスポンスを受け取った後に行いたい共通処理を実装する。 | 上記の実装例では、レスポンスヘッダの内容をログに出力している。 * - | (5) - | (4)と同様に、ステータスコードの内容をログに出力している。 * - | (6) - | (3)で受信したレスポンスをリターンする。 \ **bean定義ファイル(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定義を行う。 | .. _RestClientBasicAuthorizationInterceptorBeanDefinition: Basic認証用のリクエストヘッダ設定処理 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" サーバにアクセスするためにBasic認証用のリクエストヘッダを設定する必要がある場合は、以下のようなbean定義を行う。 \ **bean定義ファイル(applicationContext.xml)の定義例**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``ClientHttpRequestInterceptor``\ インタフェースを実装した\ ``BasicAuthorizationInterceptor``\ のbean定義を行う。 * - | (2) - | コンストラクタ引数の\ ``username``\ にユーザ名を設定する。 * - | (3) - | コンストラクタ引数の\ ``password``\ にパスワードを設定する。 | \ ``ClientHttpRequestInterceptor``\ の適用 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``RestTemplate``\ に作成した\ ``ClientHttpRequestInterceptor``\ を適用する場合は、以下のようなbean定義を行う。 \ **bean定義ファイル(applicationContext.xml)の定義例**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``interceptors``\ プロパティに\ ``ClientHttpRequestInterceptor``\ のbeanをインジェクションする。 | 複数のbeanをインジェクションした場合は、リストの先頭から順にチェーン実行される。 | 上記の例だと、\ ``BasicAuthorizationInterceptor``\ -> \ ``LoggingInterceptor``\ -> \ ``ClientHttpRequest``\ の順番でリクエスト前の処理が実行される。(レスポンス後の処理は順番が逆転する) | .. _RestClientAsync: 非同期リクエスト ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 非同期リクエストを行う場合は、\ ``org.springframework.web.reactive.function.client.WebClient``\ を使用する。 .. note:: Spring Framework 6.0から\ ``AsyncRestTemplate``\ は削除されており、非同期リクエストはSpring Web Reactive APIの\ `WebClient `_\ を使用するように案内されている。 Macchinetta Server Framework (1.x)では\ ``AsyncRestTemplate``\ の代替機能として\ ``WebClient``\ を案内しているだけであり、Spring Web Reactiveをサポートしているわけではない点に注意されたい。 | \ ``WebClient``\ のセットアップ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``WebClient``\ を使用する場合は、\ ``WebClient``\ をDIコンテナに登録し、\ ``WebClient``\ を利用するコンポーネントにインジェクションする。 | 依存ライブラリ設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | \ ``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に追加する。 .. note:: 上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存している\ `Spring Boot `_\ で管理されている。 | .. _RestClientAsyncBeanDefinition: \ ``WebClient``\ のbean定義 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ ``WebClient``\ のbean定義を行い、DIコンテナに登録する。 .. note:: 本ガイドラインのBean定義は基本的にXML-based configurationを用いているが、\ ``WebClient``\ のBean定義に関してはJava-based configurationを用いている。 \ ``WebClient``\ がBuilderパターンを使用していることに加え、Spring Frameworkが有用なXML DLSを提供していないためJava-based configurationで実装している。Java-based configurationで定義するクラスは、コンポーネントスキャンが有効となるパッケージ配下に配置されたい。詳しくは\ `Java-based configuration `_\ を参照されたい。 \ **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 ライブラリを設定。 | .. _RestClientAsyncImplementation: 非同期リクエストの実装 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" \ **非同期リクエストの実装例**\ フィールド宣言部 .. code-block:: java @Inject WecClient webClient; メソッド内部 .. code-block:: java CompletableFuture> responseEntity = this.webClient .get() // (1) .uri(uri) // (1) .retrieve() // (2) .toEntity(User.class) // (2) .doOnSuccess(r -> { // (3) // omitted }) .doOnError(t -> { // (3) // omitted }) .toFuture(); // (4) .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``WebClient``\ の各メソッドを使用して、非同期リクエストを送信する。 | 上記の実装例では、\ ``uri``\ に対しGETメソッドでHTTPリクエストを送信している。 | 使用できるメソッドについては\ `WebClientのJavadoc `_\ を参照されたい。 * - | (2) - | 処理結果の取得方法を定義する。 | この例では、\ ``User.class``\ でレスポンスを受け取れるよう定義している。 * - | (3) - | 成功のレスポンスが返ってきた場合の処理を\ ``doOnSuccess``\ に実装する。 | エラーが発生した場合の処理を\ ``doOnError``\ に実装する。 * - | (4) - | 非同期処理の結果を\ `CompletableFuture `_\ でラップした\ ``ResponseEntity``\ として受け取る。 .. Note:: \ **WebClientの返却値について**\ Reactive APIが使用する戻り値の型は、単数の場合は\ `reactor.core.publisher.Mono `_\ 、複数の場合は\ `reactor.core.publisher.Flux `_\ が一般的に用いられるが、この例では\ ``CompletableFuture``\ を返却するようにしている。 .. Tip:: \ ``block()``\ を使用し、同期的に処理させることも可能である。その場合の戻り値の型は\ ``ResponseEntity``\ となる。 | .. _RestClientAsyncCustomize: カスタマイズ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | \ ``WebClient.Builder``\ をカスタマイズし、WebClientにデフォルトで設定する方法を説明する。 | ここでの説明はあくまで一例であるため、業務要件に合わせ適宜カスタマイズされたい。 | コネクションプールの設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | コネクションプールの設定を行う方法を説明する。 \ **Bean定義の実装例(WebClientConfig.java)**\ .. code-block:: java @Configuration public class WebClientConfig { // omitted @Bean public WebClient connectionWebClient() { // @formatter:off ConnectionProvider connectionProvider = ConnectionProvider.builder(this.connectionName) // (1) .maxConnections(this.maxConnections) // (2) .pendingAcquireMaxCount(this.pendingAcquireMaxCount) // (3) .pendingAcquireTimeout(Duration.ofSeconds(this.pendingAcquireTimeout)) // (4) .build(); // @formatter:on HttpClient httpClient = HttpClient.create(connectionProvider); // (5) ReactorClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpClient); // (6) // @formatter:off WebClient webClient = WebClient.builder() .clientConnector(httpConnector) // (7) .build(); // @formatter:on return webClient; } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``reactor.netty.resources.ConnectionProvider``\ のbuilderを作成する。 * - | (2) - | builderにMaxコネクション数を設定する。 | 設定しない場合は、デフォルト値\ ``2 * プロセッサ数``\ が使用される。 * - | (3) - | builderにMaxプール数を設定する。 | "\ ``-1``\ "を設定した場合は無制限となる。 * - | (4) - | TimeoutException がスローされるまでの最大時間(ms)を設定する。 | 設定しない場合は、デフォルト45秒が使用される。 | "\ ``-1``\ "を設定した場合は無制限となる。 * - | (5) - | (1)~(4)で生成した\ ``reactor.netty.resources.ConnectionProvider``\ を設定した\ ``reactor.netty.http.client.HttpClient``\ を生成する。 * - | (6) - | (5)で定義した\ ``HttpClient``\ を設定した\ ``org.springframework.http.client.reactive.ReactorClientHttpConnector``\ のbeanを登録する。 * - | (7) - | (6)で定義した\ ``ReactorClientHttpConnector``\ を設定した\ ``WebClient``\ のbeanを登録する。 | フィルターの設定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' | \ ``org.springframework.web.reactive.function.client.ExchangeFilterFunction``\ を実装することで、サーバとの通信処理前に任意の処理を実行させることができる。 | ここでは単純な例として、通信前のロギング処理の実装例を紹介する。なお、サーバとの通信処理後に任意の処理を実施したい場合は、\ ``doOnSuccess``\ に実装すればよい。 \ **通信ログ出力の実装例**\ .. 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(); リストに登録された順番に処理されるようになる。 | .. _RestClientAppendix: Appendix -------------------------------------------------------------------------------- .. _RestClientProxySettings: HTTP Proxyサーバの設定方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | サーバへアクセスする際にHTTP Proxyサーバを経由する必要がある場合は、システムプロパティやJVM起動引数、または\ ``RestTemplate``\ のBean定義にてHTTP Proxyサーバの設定が必要となる。 | システムプロパティやJVM起動引数に設定した場合、アプリケーション全体に影響を与えてしまうため、\ ``RestTemplate``\ 毎にHTTP Proxyサーバの設定を行う例を紹介する。 | \ ``RestTemplate``\ 毎のHTTP Proxyサーバの設定は、\ ``ClientHttpRequestFactory``\ インタフェースのデフォルト実装である\ ``SimpleClientHttpRequestFactory``\ に付与することが可能である。ただし、\ ``SimpleClientHttpRequestFactory``\ では資格情報を設定することはできないため、Proxy認証を行う場合は\ ``HttpComponentsClientHttpRequestFactory``\ を使用する。 | \ ``HttpComponentsClientHttpRequestFactory``\ は、\ ``Apache HttpComponents HttpClient``\ を用いてリクエストを生成する\ ``ClientHttpRequestFactory``\ インタフェースの実装クラスである。 | SimpleClientHttpRequestFactoryを使用したHTTP Proxyサーバの設定方法 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 資格情報が不要なHTTP Proxyサーバの接続先の指定については、\ ``RestTemplate``\ がデフォルトで使用する\ ``SimpleClientHttpRequestFactory``\ で指定することが可能である。 \ **Bean定義ファイル**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``java.net.InetSocketAddress``\ にHTTP Proxyサーバの設定を行う。 * - | (2) - | \ ``InetSocketAddress``\ のコンストラクタ引数の\ ``hostname``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyHost``\ の値をHTTP Proxyサーバのホスト名として設定する。 * - | (3) - | \ ``InetSocketAddress``\ のコンストラクタ引数の\ ``port``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyPort``\ の値をHTTP Proxyサーバのポート番号として設定する。 * - | (4) - | \ ``RestTemplate``\ のBean定義を行う。 * - | (5) - | \ ``RestTemplate``\ のコンストラクタの引数に、\ ``SimpleClientHttpRequestFactory``\ を設定する。 * - | (6) - | \ ``SimpleClientHttpRequestFactory``\ の\ ``proxy``\ プロパティに\ ``java.net.Proxy``\ を設定する。 * - | (7) - | \ ``Proxy``\ のコンストラクタの引数に、\ ``java.net.Proxy.Type.HTTP``\ と生成した\ ``InetSocketAddress``\ を設定する。 | HttpComponentsClientHttpRequestFactoryを使用したHTTP Proxyサーバの設定方法 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" HTTP Proxyサーバの指定方法 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 資格情報が必要なHTTP Proxyサーバの接続先の指定は、\ ``RestTemplate``\ に対して、\ ``HttpComponentsClientHttpRequestFactory``\ を使用し指定する。 \ **pom.xml**\ .. code-block:: xml org.apache.httpcomponents.client5 httpclient5 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``HttpComponentsClientHttpRequestFactory``\ 内で使用する\ ``Apache HTTP Client``\ を使用するために、\ ``Apache HttpComponents Client``\ を\ :file:`pom.xml`\ の依存ライブラリに追加する。 .. note:: 上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。 上記の依存ライブラリはterasoluna-gfw-parentが依存している\ `Spring Boot `_\ で管理されている。 \ **Bean定義ファイル**\ .. code-block:: xml .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``org.apache.hc.client5.http.impl.classic.HttpClientBuilder``\ を使用し、\ ``org.apache.http.client5.HttpClient``\ の設定を行う。 * - | (2) - | \ ``HttpClientBuilder``\ の\ ``proxy``\ プロパティに、\ HTTP Proxyサーバの設定を行った\ ``org.apache.http.HttpHost``\ を設定する。 * - | (3) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``hostname``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyHost``\ の値をHTTP Proxyサーバのホスト名として設定する。 * - | (4) - | \ ``HttpHost``\ のコンストラクタ引数の\ ``port``\ に、プロパティファイルに設定されたキー\ ``rscl.http.proxyPort``\ の値をHTTP Proxyサーバのポート番号として設定する。 * - | (5) - | \ ``RestTemplate``\ のBean定義を行う。 * - | (6) - | \ ``RestTemplate``\ のコンストラクタの引数に、\ ``HttpComponentsClientHttpRequestFactory``\ を設定する。 * - | (7) - | \ ``HttpComponentsClientHttpRequestFactory``\ のコンストラクタの引数に、\ ``HttpClientBuilder``\ から生成した\ ``HttpClient``\ を設定する。 | HTTP 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) AuthScope authScope = new AuthScope(this.host, this.port); // (7) char[] passwordCharArray = this.password == null ? null : this.password.toCharArray(); UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials(this.userName, passwordCharArray); // (8) BasicCredentialsProvider 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サーバのホスト名とポート番号を指定したものである。その他の設定方法については、\ `AuthScope (Apache HttpClient API) `_\ を参照されたい。 * - | (7) - | \ ``org.apache.http.auth.UsernamePasswordCredentials``\ を作成し資格情報を設定する。 * - | (8) - | \ ``org.apache.http.impl.client.BasicCredentialsProvider``\ を作成し、\ ``setCredentials``\ メソッドを使用し、資格情報のスコープと資格情報を設定する。 \ **Bean定義ファイル**\ .. 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を作成する。 .. raw:: latex \newpage