5.2. RESTクライアント(HTTPクライアント)¶
目次
5.2.1. Overview¶
本節では、Spring Frameworkが提供するorg.springframework.web.client.RestTemplate
を使用してRESTful Web Service(REST API)を呼び出す実装方法について説明する。
5.2.1.1. RestTemplate
とは¶
RestTemplate
は、REST API(Web API)を呼び出すためのメソッドを提供するクラスであり、Spring Frameworkが提供するHTTPクライアントである。
具体的な実装方法の説明を行う前に、RestTemplate
がどのようにREST API(Web API)にアクセスしているかを説明する。
項番 | コンポーネント | 説明 |
---|---|---|
(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
を使用する。
非同期処理の実装例については、非同期リクエストを参照されたい。
5.2.1.1.1. HttpMessageConverter
¶
org.springframework.http.converter.HttpMessageConverter
は、アプリケーションで扱うJavaオブジェクトとサーバと通信するための電文(JSON等)を相互に変換するためのインタフェースである。
RestTemplate
を使用した場合、デフォルトで以下のHttpMessageConverter
の実装クラスが登録される。
項番 | クラス名 | 説明 | サポート型 |
---|---|---|---|
(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 [1] |
(4)
|
org.springframework.http.converter.xml. SourceHttpMessageConverter |
「HTTPボディ(XML)⇔XMLソースオブジェクト」変換用のクラス。
デフォルトでXML用のメディアタイプ(
text/xml ,application/xml ,application/*-xml )をサポートする。 |
Source [2] |
(5)
|
org.springframework.http.converter.support. AllEncompassingFormHttpMessageConverter |
「HTTPボディ⇔
MultiValueMap オブジェクト」変換用のクラス。FormHttpMessageConverter の拡張クラスで、multipartのパートデータとしてXMLとJSONへの変換がサポートされている。デフォルトでフォームデータ用のメディアタイプ(
application/x-www-form-urlencoded ,multipart/form-data )をサポートする。
デフォルトで登録されるパートデータ変換用の
HttpMessageConveter は、AllEncompassingFormHttpMessageConverterと FormHttpMessageConverterのソースを参照されたい。なお、任意のHttpMessageConverter を登録することもできる。 |
MultiValueMap [3] |
Note
AllEncompassingFormHttpMessageConverterのメディアタイプがmultipart/form-dataの場合について
メディアタイプがmultipart/form-data
の場合、「MultiValueMap
オブジェクト から HTTPボディ」への変換は可能だが、「HTTPボディ から MultiValueMap
オブジェクト」への変換は現状サポートされていない。よって、「HTTPボディ から MultiValueMap
オブジェクト」への変換を行いたい場合は、独自に実装する必要がある。
項番 | クラス名 | 説明 | サポート型 |
---|---|---|---|
(6)
|
org.springframework.http.converter.feed. AtomFeedHttpMessageConverter |
「HTTPボディ(Atom)⇔Atomフィードオブジェクト」変換用のクラス。
デフォルトでATOM用のメディアタイプ(
application/atom+xml )をサポートする。(ROMEがクラスパスに存在する場合に登録される)
|
Feed [4] |
(7)
|
org.springframework.http.converter.feed. RssChannelHttpMessageConverter |
「HTTPボディ(RSS)⇔Rssチャネルオブジェクト」変換用のクラス。
デフォルトでRSS用のメディアタイプ(
application/rss+xml )をサポートする。(ROMEがクラスパスに存在する場合に登録される)
|
Channel [5] |
(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をクラスパスに登録するにはJAXBの削除を参照されたい。 |
Object (JavaBean) |
(11)
|
org.springframework.http.converter.json. GsonHttpMessageConverter |
「HTTPボディ(JSON)⇔JavaBean」変換用のクラス。
デフォルトでJSON用のメディアタイプ(
application/json ,application/*+json )をサポートする。(Gsonがクラスパスに存在する場合に登録される)
|
Object (JavaBean)Map |
[1] | org.springframework.core.io パッケージのクラス |
[2] | jakarta.xml.transform パッケージのクラス |
[3] | org.springframework.util パッケージのクラス |
[4] | com.rometools.rome.feed.atom パッケージのクラス |
[5] | com.rometools.rome.feed.rss パッケージのクラス |
5.2.1.1.2. 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
の実装クラスは以下の通りである。
項番 | クラス名 | 説明 |
---|---|---|
(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などのライブラリの利用を検討されたい。
5.2.1.1.3. 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メソッドを呼び出すことで取得することができる。
5.2.1.1.4. ClientHttpRequestInterceptor
¶
org.springframework.http.client.ClientHttpRequestInterceptor
は、サーバとの通信の前後に共通的な処理を実装するためのインタフェースである。
ClientHttpRequestInterceptor
を使用すると、
- サーバとの通信ログ
- 認証ヘッダの設定
といった共通的な処理をRestTemplate
に適用することができる。
Note
ClientHttpRequestInterceptorの動作仕様
ClientHttpRequestInterceptor
は複数適用することができ、指定した順番でチェーン実行される。これはサーブレットフィルタの動作によく似ており、最後に実行されるチェーン先としてClientHttpRequest
によるHTTP通信処理が登録されている。例えば、ある条件に一致した際にサーバとの通信処理をキャンセルしたいという要件があった場合は、チェーン先を呼びださなければよい。
この仕組みを活用すると、
- サーバとの通信の閉塞
- 通信処理のリトライ
といった処理を適用することもできる。
5.2.2. How to use¶
本節では、RestTemplate
を使用したクライアント処理の実装方法について説明する。
Note
RestTemplateがサポートするHTTPメソッドについて
本ガイドラインでは、GETメソッドとPOSTメソッドを使用したクライアント処理の実装例のみを紹介するが、RestTemplate
は他のHTTPメソッド(PUT, PATCH, DELETE, HEAD, OPTIONSなど)もサポートしており、同じような要領で使用することができる。
詳細はRestTemplateのJavadocを参照されたい。
5.2.2.1. RestTemplate
のセットアップ¶
RestTemplate
を使用する場合は、RestTemplate
をDIコンテナに登録し、RestTemplate
を利用するコンポーネントにインジェクションする。
5.2.2.1.1. 依存ライブラリ設定¶
RestTemplate
を使用するためにpom.xml
に、Spring Frameworkのspring-webライブラリを追加する。pom.xml
に追加する。<dependencies>
<!-- (1) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
Note
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。
項番 | 説明 |
---|---|
(1)
|
Spring Frameworkの
spring-web ライブラリをdependenciesに追加する。 |
5.2.2.1.2. RestTemplate
のbean定義¶
RestTemplate
のbean定義を行い、DIコンテナに登録する。
bean定義ファイル(applicationContext.xml)の定義例
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate" /> <!-- (1) -->
項番 | 説明 |
---|---|
(1)
|
RestTemplate をデフォルト設定のまま利用する場合は、デフォルトコンストラクタを使用してbeanを登録する。 |
Note
RestTemplateのカスタマイズ方法
HTTP通信処理をカスタマイズする場合は、以下のようなbean定義となる。
<bean id="clientHttpRequestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory"> <!-- (1) --> <!-- Set properties for customize a http communication (omit on this sample) --> </bean> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <constructor-arg ref="clientHttpRequestFactory" /> <!-- (2) --> </bean>
項番 説明 (1) (2)ClientHttpRequestFactory
を引数に指定するコンストラクタを使用してbeanを登録する。
なお、HttpMessageConverter
、ResponseErrorHandler
、ClientHttpRequestInterceptor
のカスタマイズ方法については、
を参照されたい。
5.2.2.1.3. RestTemplate
の利用¶
RestTemplate
を利用する場合は、DIコンテナに登録されているRestTemplate
をインジェクションする。
RestTemplateのインジェクション例
@Service
public class AccountServiceImpl implements AccountService {
@Inject
RestTemplate restTemplate;
// omitted
}
5.2.2.2. GETリクエストの送信¶
RestTemplate
は、GETリクエストを行うためのメソッドを複数提供している。
- 通常は
getForObject
メソッド又はgetForEntity
メソッドを使用する。 - 任意のヘッダを設定するなどリクエストに細かい設定を行いたい場合は、
org.springframework.http.RequestEntity
とexchange
メソッドを使用する。
5.2.2.2.1. getForObject
メソッドを使用した実装¶
レスポンスボディのみ取得できればよい場合は、getForObject
メソッドを使用する。
getForObjectメソッドの使用例
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
URI uri;
メソッド内部
User user = restTemplate.getForObject(uri, User.class); // (1)
項番 | 説明 |
---|---|
(1)
|
getForObject メソッドを使用した場合は、戻り値はレスポンスボディの値になる。レスポンスボディのデータは
HttpMessageConverter によって第2引数に指定したJavaクラスへ変換された後、返却される。 |
5.2.2.2.2. getForEntity
メソッドを使用した実装¶
HTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合は、getForEntity
メソッドを使用する。
getForEntityメソッドの使用例
ResponseEntity<User> responseEntity =
restTemplate.getForEntity(uri, User.class); // (1)
HttpStatus statusCode = responseEntity.getStatusCode(); // (2)
HttpHeaders header = responseEntity.getHeaders(); // (3)
User user = responseEntity.getBody(); // (4)
項番 | 説明 |
---|---|
(1)
|
getForEntity メソッドを使用した場合は、戻り値はorg.springframework.http.ResponseEntity となる。 |
(2)
|
HTTPステータスコードは
getStatusCode メソッドを用いて取得する。 |
(3)
|
レスポンスヘッダは
getHeaders メソッドを用いて取得する。 |
(4)
|
レスポンスボディは
getBody メソッドを用いて取得する。 |
Note
ResponseEntityとは
ResponseEntity
はHTTPレスポンスを表すクラスで、HTTPステータスコード、レスポンスヘッダ、レスポンスボディの情報を取得することができる。
詳細はResponseEntityのJavadocを参照されたい。
5.2.2.2.3. exchange
メソッドを使用した実装¶
リクエストヘッダを指定する必要がある場合は、org.springframework.http.RequestEntity
を生成しexchange
メソッドを使用する。
exchangeメソッドの使用例
import部
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
URI uri;
メソッド内部
RequestEntity requestEntity = RequestEntity
.get(uri)//(1)
.build();//(2)
ResponseEntity<User> responseEntity =
restTemplate.exchange(requestEntity, User.class);//(3)
User user = responseEntity.getBody();//(4)
項番 | 説明 |
---|---|
(1)
|
RequestEntity のget メソッドを使用し、GETリクエスト用のリクエストビルダを生成する。パラメータにURIを設定する。
|
(2)
|
RequestEntity.HeadersBuilder のbuild メソッドを使用し、RequestEntity オブジェクトを作成する。 |
(3)
|
exchange メソッドを使用し、リクエストを送信する。第二引数に、レスポンスデータの型を指定する。レスポンスは、
ResponseEntity<T> になる。型パラメータに、レスポンスデータの型を設定する。 |
(4)
|
getBody メソッドを使用し、レスポンスボディのデータを取得する。 |
Note
RequestEntityとは
RequestEntity
はHTTPリクエストを表すクラスで、接続URI、HTTPメソッド、リクエストヘッダ、リクエストボディを設定することができる。
詳細はRequestEntityのJavadocを参照されたい。
なお、リクエストヘッダの設定方法については、リクエストヘッダの設定を参照されたい。
5.2.2.3. POSTリクエストの送信¶
RestTemplate
は、POSTリクエストを行うためのメソッドを複数提供している。
- 通常は、
postForObject
、postForEntity
を使用する。 - 任意のヘッダを設定するなどリクエストに細かい設定を行いたい場合は、
RequestEntity
とexchange
メソッドを使用する。
5.2.2.3.1. postForObject
メソッドを使用した実装¶
POSTした結果としてレスポンスボディのみ取得できればよい場合は、postForObject
メソッドを使用する。
postForObjectメソッドの使用例
User user = new User();
// omitted
User user = restTemplate.postForObject(uri, user, User.class); // (1)
項番 | 説明 |
---|---|
(1)
|
postForObject メソッドは、簡易にPOSTリクエストを実装できる。第二引数には、
HttpMessageConverter によってリクエストボディに変換されるJavaオブジェクトを設定する。postForObject メソッドを使用した場合は、戻り値はレスポンスボディの値になる。 |
5.2.2.3.2. postForEntity
メソッドを使用した実装¶
POSTした結果としてHTTPステータスコード、レスポンスヘッダ、レスポンスボディを取得する必要がある場合は、postForEntity
メソッドを使用する。
postForEntityメソッドの使用例
User user = new User();
// omitted
ResponseEntity<User> responseEntity =
restTemplate.postForEntity(uri, user, User.class); // (1)
項番 | 説明 |
---|---|
(1)
|
postForEntity メソッドもgetForObject メソッドと同様に簡易にPOSTリクエストを実装できる。postForEntity メソッドを使用した場合は、戻り値はResponseEntity となる。レスポンスボディの値は、
ResponseEntity から取得する。 |
5.2.2.3.3. exchange
メソッドを使用した実装¶
リクエストヘッダを指定する必要がある場合は、RequestEntity
を生成しexchange
メソッドを使用する。
exchangeメソッドの使用例
import部
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
フィールド宣言部
@Value("${api.url:http://localhost:8080/api}")
URI uri;
メソッド内部
User user = new User();
// omitted
RequestEntity<User> requestEntity = RequestEntity//(1)
.post(uri)//(2)
.body(user);//(3)
ResponseEntity<User> responseEntity =
restTemplate.exchange(requestEntity, User.class);//(4)
項番 | 説明 |
---|---|
(1)
|
RequestEntity を使用して、リクエストを生成する。型パラメータに、リクエストボディに設定するデータの型を指定する。 |
(2)
|
post メソッドを使用し、POSTリクエスト用のリクエストビルダを生成する。パラメータにURIを設定する。 |
(3)
|
RequestEntity.BodyBuilder のbody メソッドを使用し、RequestEntity オブジェクトを作成する。パラメータにリクエストボディに変換するJavaオブジェクトを設定する。
|
(4)
|
exchange メソッドを使用し、リクエストを送信する。 |
5.2.2.4. コレクション形式のデータ取得¶
サーバから応答されるレスポンスボディの電文(JSON等)がコレクション形式の場合は、以下のような実装となる。
コレクション形式のデータの取得例
ResponseEntity<List<User>> responseEntity = //(1)
restTemplate.exchange(requestEntity, new ParameterizedTypeReference<List<User>>(){}); //(2)
List<User> userList = responseEntity.getBody();//(3)
項番 | 説明 |
---|---|
(1)
|
ResponseEntity の型パラメータにList <レスポンスデータの型>を指定する。 |
(2)
|
exchange メソッドの第二引数にorg.springframework.core.ParameterizedTypeReference のインスタンスを指定し、型パラメータにList <レスポンスデータの型>を指定する。 |
(3)
|
getBody メソッドで、レスポンスボディのデータを取得する。 |
5.2.2.5. リクエストヘッダの設定¶
RequestEntity
とexchange
メソッドを使用すると、RequestEntity
のメソッドを使用して特定のヘッダ及び任意のヘッダを設定することができる。について説明する。
5.2.2.5.1. Content-Typeヘッダの設定¶
サーバへデータを送信する場合は、通常Content-Typeヘッダの指定が必要となる。
Content-Typeヘッダの設定例
User user = new User();
// omitted
RequestEntity<User> requestEntity = RequestEntity
.post(uri)
.contentType(MediaType.APPLICATION_JSON) // (1)
.body(user);
項番 | 説明 |
---|---|
(1)
|
RequestEntity.BodyBuilder のcontentType メソッドを使用し、Context-Typeヘッダの値を指定する。上記の実装例では、データ形式がJSONであることを示す「
application/json 」を設定している。 |
5.2.2.5.2. Acceptヘッダの設定¶
Acceptヘッダの設定例
User user = new User();
// omitted
RequestEntity<User> requestEntity = RequestEntity
.post(uri)
.accept(MediaType.APPLICATION_JSON) // (1)
.body(user);
項番 | 説明 |
---|---|
(1)
|
RequestEntity.HeadersBuilder のaccept メソッドを使用して、Acceptヘッダの値を設定する。上記の実装例では、取得可能なデータ形式がJSONであることを示す「
application/json 」を設定している。 |
5.2.2.5.3. 任意のリクエストヘッダの設定¶
サーバへアクセスするために、リクエストヘッダの設定が必要になるケースがある。
任意ヘッダの設定例
User user = new User();
// omitted
RequestEntity<User> requestEntity = RequestEntity
.post(uri)
.header("Authorization", "Basic " + base64Credentials) // (1)
.body(user);
項番 | 説明 |
---|---|
(1)
|
RequestEntity.HeadersBuilder のheader メソッドを使用してリクエストヘッダの名前と値を設定する。上記の実装例では、AuthorizationヘッダにBasic認証に必要な資格情報を設定している。
|
5.2.2.6. エラーハンドリング¶
5.2.2.6.1. 例外ハンドリング(デフォルトの動作)¶
RestTemplate
のデフォルト実装(DefaultResponseErrorHandler
)では、
- レスポンスコードがクライアントエラー系(4xx)の場合は、
HttpClientErrorException
- レスポンスコードがサーバエラー系(5xx)の場合は、
HttpServerErrorException
- レスポンスコードが未定義のコード(ユーザ定義のカスタムコード)の場合は、
UnknownHttpStatusCodeException
が発生するため、必要に応じてこれらの例外をハンドリングする必要がある。
例外ハンドリングの実装例
Note
以下の実装例は、サーバエラーが発生した際の例外ハンドリングの一例である。
アプリケーションの要件に応じて適切な例外ハンドリングを行うこと。
フィールド宣言部
@Value("${api.retry.maxCount}")
int retryMaxCount;
@Value("${api.retry.retryWaitTimeCoefficient}")
int retryWaitTimeCoefficient;
メソッド内部
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
}
}
項番 | 説明 |
---|---|
(1)
|
例外をキャッチしてエラー処理を行う。サーバエラー(500系)の場合、
HttpServerErrorException をキャッチする。 |
5.2.2.6.2. ResponseEntity
の返却(エラーハンドラの拡張)¶
org.springframework.web.client.ResponseErrorHandler
インタフェースの実装クラスをRestTemplate
に設定することで、独自のエラー処理を行うことができる。
以下の例では、サーバエラー及びクライアントエラーが発生した場合でもResponseEntity
を返すようにエラーハンドラを拡張している。
エラーハンドラの実装クラスの作成例
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.
}
}
項番 | 説明 |
---|---|
(1)
|
ResponseErrorHandler インタフェースの実装クラスを作成する。上記の作成例では、デフォルトのエラーハンドラの実装クラスである
DefaultResponseErrorHandler を拡張し、サーバエラー及びクライアントエラーが発生した際に例外を発生させずに
ResponseEntity が返るようにしている。 |
bean定義ファイル(applicationContext.xml)の定義例
<bean id="customErrorHandler" class="com.example.restclient.CustomErrorHandler" /> <!-- (1) -->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="errorHandler" ref="customErrorHandler" /><!-- (2) -->
</bean>
項番 | 説明 |
---|---|
(1)
|
ResponseErrorHandler の実装クラスのbean定義を行う。 |
(2)
|
errorHandler プロパティにResponseErrorHandler のbeanをインジェクションする。 |
クライアント処理の実装例
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
}
}
項番 | 説明 |
---|---|
(1)
|
上記の実装例では、エラー時にも
ResponseEntity を返すようにエラーハンドラを拡張しているので、返却されたResponseEntity からHTTPステータスコードを取得して、処理結果が正常であったか確認する必要がある。 |
(2)
|
エラー発生時も返却された
ResponseEntity からHTTPステータスコードを取得して、その値に応じて処理を制御することができる。 |
5.2.2.7. 通信タイムアウトの設定¶
サーバとの通信に対してタイムアウト時間を指定したい場合は、以下のようなbean定義を行う。
bean定義ファイル(applicationContext.xml)の定義例
<bean id="clientHttpRequestFactory"
class="org.springframework.http.client.SimpleClientHttpRequestFactory">
<property name="connectTimeout" value="${api.connectTimeout: 2000}" /><!-- (1) -->
<property name="readTimeout" value="${api.readTimeout: 2000}" /><!-- (2) -->
</bean>
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg ref="clientHttpRequestFactory" />
</bean>
項番 | 説明 |
---|---|
(1)
|
connectTimeout プロパティにサーバとの接続タイムアウト時間(ミリ秒)を設定する。タイムアウト発生時は
org.springframework.web.client.ResourceAccessException が発生する。 |
(2)
|
readTimeout プロパティにレスポンスデータの読み込みタイムアウト時間(ミリ秒)を設定する。タイムアウト発生時は
ResourceAccessException が発生する。 |
Note
タイムアウト発生時の起因例外
ResourceAccessException
は起因例外をラップしており、接続タイムアウト及び読み込みタイムアウト発生時の起因例外は共にjava.net.SocketTimeoutException
である。デフォルト実装(SimpleClientHttpRequestFactory
)を使用した場合は、どちらのタイムアウトが発生したかを例外クラスの種類で区別できないという点を補足しておく。
なお、他のHttpRequestFactory
を使用した場合の動作は未検証であるため、起因例外が上記と異なる可能性がある。他のHttpRequestFactory
を使用する場合は、タイムアウト時に発生する例外を把握した上で適切な例外ハンドリングを行うこと。
5.2.2.8. SSL自己署名証明書の使用¶
テスト環境などでSSL自己署名証明書を使用する場合は、以下のように実装する。
FactoryBeanの実装例
RestTemplate
のBean定義で、コンストラクタの引数に渡す org.springframework.http.client.ClientHttpRequestFactory
を作成するための org.springframework.beans.factory.FactoryBean
を実装する。HttpClientConnectionManager
やHttpClient
の設定値は業務要件に応じ適切に設定されたい。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<ClientHttpRequestFactory>,
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();
}
}
}
項番 | 説明 |
---|---|
(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ハンドシェイク完了前に そのため、SSLハンドシェイク中に通信障害や通信相手のハードウェア障害等に遭遇し通信相手からのパケットが届かない時間が長く続く場合(クライアント側で通信の継続を諦めなければならない場合)以外においては、 SSLハンドシェイク完了後のHTTPSリクエストに対するレスポンスのタイムアウトには、後述の
という具合に使い分けること。なお、 |
(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)
|
|
(15)
|
作成した
HttpClient を利用するClientHttpRequestFactory を作成する。 |
(16)
|
FactoryBean で生成したオブジェクトのライフサイクルはDIコンテナで管理されないため、破棄時に特定の処理を実行するにはFactoryBean にDisposableBean インタフェースのdestroy メソッドを実装する必要がある。ここでは、
getObject メソッドで生成した HttpComponentsClientHttpRequestFactory のdestroy メソッドを呼び出し、HttpClient をクローズしている。このため、生成したオブジェクトを変数に保持している。 |
Note
通信先のアプリケーションがTLS 1.2以前のバージョンにしか対応していない等の理由により、使用するTLSのバージョンをJVMレベルで変更するにはHTTP通信におけるTLS(Transport Layer Security) v1.3のサポートを参照されたい。
ただし、JVMレベルで設定してしまうと一つのクライアントアプリからTLS 1.2とTLS 1.3を利用した別々のアプリケーションに接続するような要件を実現することができない。このような場合は、HttpClient
ごとに利用するTLSのバージョンを指定するような実装を検討されたい。
HttpClient
および HttpClientBuilder
を使用するためには、Apache HttpComponents HttpClient のライブラリが必要となる。pom.xml
に追加し、Apache HttpComponents HttpClient を依存ライブラリに追加する。pom.xml
<dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> </dependency>
Note
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。
bean定義ファイル(applicationContext.xml)の定義例
SSL自己署名証明書を使用したSSL通信を行う RestTemplate
を定義する。
<bean id="httpsRestTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="com.example.restclient.RequestFactoryBean"> <!-- (1) -->
<property name="keyStoreFileName" value="${rscl.keystore.filename}" />
<property name="keyStorePassword" value="${rscl.keystore.password}" />
</bean>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(1)
|
作成した
RequestFactoryBean を RestTemplate のコンストラクタに指定する。RequestFactoryBean には、キーストアファイルのファイル名とパスワードを渡す。 |
RestTemplateの使用方法
RestTemplate
の使い方については、SSL自己署名証明書を使用しない場合と同じである。
5.2.2.9. Basic認証¶
サーバがBasic認証を要求する場合は、以下のように実装する。このとき、Java標準のjava.util.Base64
を使用する。
Basic認証の実装例
フィールド宣言部
@Value("${api.auth.username}")
String username;
@Value("${api.auth.password}")
String password;
メソッド内部
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();
項番 | 説明 |
---|---|
(1)
|
ユーザ名とパスワードを「”
: ” 」でつなげる。 |
(2)
|
(1)をバイト配列に変換して、Base64エンコードする。
|
(3)
|
AuthorizationヘッダをBasic認証の資格情報を設定する。
|
Note
Spring Security 5より、org.springframework.security.crypto.codec.Base64
は非推奨になったため、Java標準のjava.util.Base64
に置き換えることを推奨する。
5.2.2.10. ファイルアップロード(マルチパートリクエスト)¶
RestTemplate
を使用してファイルアップロード(マルチパートリクエスト)を行う場合は、以下のように実装する。
ファイルアップロードの実装例
MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();//(1)
multiPartBody.add("file", new ClassPathResource("/uploadFiles/User.txt"));//(2)
RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity
.post(uri)
.contentType(MediaType.MULTIPART_FORM_DATA)//(3)
.body(multiPartBody);//(4)
項番 | 説明 |
---|---|
(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
(ファイル名をサーバに連携できない)
5.2.2.11. ファイルダウンロード¶
RestTeamplate
を使用してファイルダウンロードを行う場合は、以下のように実装する。
ファイルダウンロードの実装例(ファイルサイズが小さい場合)
RequestEntity requestEntity = RequestEntity
.get(uri)
.build();
ResponseEntity<byte[]> responseEntity =
restTemplate.exchange(requestEntity, byte[].class);//(1)
byte[] downloadContent = responseEntity.getBody();//(2)
項番 | 説明 |
---|---|
(1)
|
ダウンロードファイルを指定したデータ型で扱う。ここでは、バイト配列を指定。
|
(2)
|
レスポンスボディからダウンロードしたファイルのデータを取得する。
|
Warning
サイズの大きいファイルをダウンロードする際の注意点
サイズの大きなファイルをデフォルトで登録されているHttpMessageConverter
を使用して byte
配列で取得すると、 java.lang.OutOfMemoryError
が発生する可能性がある。そのため、サイズの大きなファイルをダウンロードしたい場合は、レスポンスから InputStream
を取得して、ダウンロードデータを少しずつファイルに書き出す必要がある。
ファイルダウンロードの実装例(ファイルサイズが大きい場合)
// (1)
final ResponseExtractor<ResponseEntity<File>> responseExtractor =
new ResponseExtractor<ResponseEntity<File>>() {
// (2)
@Override
public ResponseEntity<File> 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<File> responseEntity = this.restTemplate.execute(targetUri,
HttpMethod.GET, null, responseExtractor);
if (HttpStatus.OK.equals(responseEntity.getStatusCode())) {
File getFile = responseEntity.getBody();
// omitted
}
項番 | 説明 |
---|---|
(1)
|
RestTemplate#execute で取得されたレスポンスから、RestTemplate#execute の戻り値を作成するための処理を作成する。 |
(2)
|
レスポンスボディ(
InputStream )からデータを読込み、ファイルを作成する。作成したファイルとHTTPヘッダ、ステータスコードを
ResponseEntity<File> に格納して返却する。 |
(3)
|
RestTemplate#execute を使用して、ファイルのダウンロードを行う。 |
ファイルダウンロードの実装例(ファイルサイズが大きい場合(ResponseEntityを使わない例))
ステータスコードの判定やHTTPヘッダの参照等が不要な場合は、 以下のようにResponseEntity
ではなく File
を返却すればよい。
final ResponseExtractor<File> responseExtractor = new ResponseExtractor<File>() {
@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
5.2.2.12. RESTfulなURL(URIテンプレート)を扱う方法と実装例¶
RESTfulなURLを扱うには、URIテンプレートを使用して実装を行えばよい。
getForObjectメソッドでの使用例
フィールド宣言部
@Value("${api.serverUrl}/api/users/{userId}") // (1)
String uriStr;
メソッド内部
User user = restTemplate.getForObject(uriStr, User.class, "0001"); // (2)
項番 | 説明 |
---|---|
(1)
|
URIテンプレートの変数{userId}は、
RestTeamplate の使用時に指定の値に変換される。 |
(2)
|
URIテンプレートの変数1つ目が
getForObject メソッドの第3引数に指定した値で置換され、『http://localhost:8080/api/users/0001 』として処理される。 |
exchangeメソッドでの使用例
@Value("${api.serverUrl}/api/users/{action}") // (1)
String uriStr;
メソッド内部
URI targetUri = UriComponentsBuilder.fromUriString(uriStr).
buildAndExpand("create").toUri(); //(2)
User user = new User();
// omitted
RequestEntity<User> requestEntity = RequestEntity
.post(targetUri)
.body(user);
ResponseEntity<User> responseEntity = restTemplate.exchange(requestEntity, User.class);
項番 | 説明 |
---|---|
(1)
|
URIテンプレートの変数{action}は、
RestTeamplate の使用時に指定の値に変換される。 |
(2)
|
UriComponentsBuilder を使用することで、URIテンプレートの変数1つ目がbuildAndExpand の引数で指定した値に置換され、『http://localhost:8080/api/users/create 』のURIが作成される。詳細はUriComponentsBuilderのJavadocを参照されたい。
|
5.2.3. How to extend¶
本節では、RestTemplate
の拡張方法について説明する。
5.2.3.1. 任意のHttpMessageConverter
を登録する方法¶
デフォルトで登録されている HttpMessageConverter
で電文変換の要件を満たせない場合は、任意のHttpMessageConverter
を登録することができる。ただし、デフォルトで登録されていたHttpMessageConverter
は削除されるので、必要なHttpMessageConverter
をすべて個別に登録する必要がある。
bean定義ファイル(applicationContext.xml)の定義例
<bean id="jaxb2CollectionHttpMessageConverter"
class="org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter" /> <!-- (1) -->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters"> <!-- (2) -->
<list>
<ref bean="jaxb2CollectionHttpMessageConverter" />
</list>
</property>
</bean>
項番 | 説明 |
---|---|
(1)
|
登録する
HttpMessageConverter の実装クラスをbean定義する。 |
(2)
|
messageConverters プロパティに登録するHttpMessageConverter のbeanをインジェクションする。 |
5.2.3.2. 共通処理の適用(ClientHttpRequestInterceptor
)¶
ClientHttpRequestInterceptor
を使用することで、サーバとの通信処理の前後に任意の処理を実行させることができる。5.2.3.2.1. ロギング処理¶
サーバとの通信ログを出力したい場合は、以下のような実装を行う。
通信ログ出力の実装例
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)
}
}
項番 | 説明 |
---|---|
(1)
|
ClientHttpRequestInterceptor インタフェースを実装する。 |
(2)
|
リクエストする前に行いたい共通処理を実装する。
上記の実装例では、リクエストヘッダーとリクエストボディの内容をログに出力している。
|
(3)
|
intercept メソッドの引数として受け取ったClientHttpRequestExecution のexecute メソッドを実行し、リクエストの送信を実行する。 |
(4)
|
レスポンスを受け取った後に行いたい共通処理を実装する。
上記の実装例では、レスポンスヘッダの内容をログに出力している。
|
(5)
|
(4)と同様に、ステータスコードの内容をログに出力している。
|
(6)
|
(3)で受信したレスポンスをリターンする。
|
bean定義ファイル(applicationContext.xml)の定義例
<!-- (1) -->
<bean id="loggingInterceptor" class="com.example.restclient.LoggingInterceptor" />
項番 | 説明 |
---|---|
(1)
|
ClientHttpRequestInterceptor の実装クラスのbean定義を行う。 |
5.2.3.2.2. Basic認証用のリクエストヘッダ設定処理¶
サーバにアクセスするためにBasic認証用のリクエストヘッダを設定する必要がある場合は、以下のようなbean定義を行う。
bean定義ファイル(applicationContext.xml)の定義例
<!-- (1) -->
<bean id="basicAuthInterceptor" class="org.springframework.http.client.support.BasicAuthorizationInterceptor">
<constructor-arg name="username" value="${api.auth.username}" /><!-- (2) -->
<constructor-arg name="password" value="${api.auth.password}" /><!-- (3) -->
</bean>
項番 | 説明 |
---|---|
(1)
|
ClientHttpRequestInterceptor インタフェースを実装したBasicAuthorizationInterceptor のbean定義を行う。 |
(2)
|
コンストラクタ引数の
username にユーザ名を設定する。 |
(3)
|
コンストラクタ引数の
password にパスワードを設定する。 |
5.2.3.2.3. ClientHttpRequestInterceptor
の適用¶
RestTemplate
に作成したClientHttpRequestInterceptor
を適用する場合は、以下のようなbean定義を行う。
bean定義ファイル(applicationContext.xml)の定義例
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="interceptors"><!-- (1) -->
<list>
<ref bean="basicAuthInterceptor" />
<ref bean="loggingInterceptor" />
</list>
</property>
</bean>
項番 | 説明 |
---|---|
(1)
|
interceptors プロパティにClientHttpRequestInterceptor のbeanをインジェクションする。複数のbeanをインジェクションした場合は、リストの先頭から順にチェーン実行される。
上記の例だと、
BasicAuthorizationInterceptor -> LoggingInterceptor -> ClientHttpRequest の順番でリクエスト前の処理が実行される。(レスポンス後の処理は順番が逆転する) |
5.2.3.3. 非同期リクエスト¶
非同期リクエストを行う場合は、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をサポートしているわけではない点に注意されたい。
5.2.3.3.1. WebClient
のセットアップ¶
WebClient
を使用する場合は、WebClient
をDIコンテナに登録し、WebClient
を利用するコンポーネントにインジェクションする。5.2.3.3.1.1. 依存ライブラリ設定¶
WebClient
を使用するためにpom.xml
に、Spring Frameworkのspring-webfluxライブラリを追加する。pom.xml
に追加する。<dependencies>
<!-- (1) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<!-- (2) -->
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
</dependencies>
項番 | 説明 |
---|---|
(1)
|
spring-webflux ライブラリをdependenciesに追加する。 |
(1)
|
reactor-netty ライブラリをdependenciesに追加する。 |
Note
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。
5.2.3.3.2. 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)
@Configuration
public class WebClientConfig {
@Bean
public WebClient defaultWebClient() {
// (1)
return WebClient.builder().build();
}
}
項番 | 説明 | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
(1)
|
WebClient.Builder をbuild することでWebClientを生成しBeanとして登録する。WebClient.Builder はオプションを付けることでカスタマイズすることが可能である。設定できるオプションは以下の通り。
|
5.2.3.3.3. 非同期リクエストの実装¶
非同期リクエストの実装例
フィールド宣言部
@Inject
WecClient webClient;
メソッド内部
CompletableFuture<ResponseEntity<User>> responseEntity = this.webClient
.get() // (1)
.uri(uri) // (1)
.retrieve() // (2)
.toEntity(User.class) // (2)
.doOnSuccess(r -> { // (3)
// omitted
})
.doOnError(t -> { // (3)
// omitted
})
.toFuture(); // (4)
項番 | 説明 |
---|---|
(1)
|
WebClient の各メソッドを使用して、非同期リクエストを送信する。上記の実装例では、
uri に対しGETメソッドでHTTPリクエストを送信している。使用できるメソッドについてはWebClientのJavadocを参照されたい。
|
(2)
|
処理結果の取得方法を定義する。
この例では、
User.class でレスポンスを受け取れるよう定義している。 |
(3)
|
成功のレスポンスが返ってきた場合の処理を
doOnSuccess に実装する。エラーが発生した場合の処理を
doOnError に実装する。 |
(4)
|
非同期処理の結果をCompletableFutureでラップした
ResponseEntity<User> として受け取る。Note WebClientの返却値について Reactive APIが使用する戻り値の型は、単数の場合はreactor.core.publisher.Mono、複数の場合はreactor.core.publisher.Fluxが一般的に用いられるが、この例では Tip
|
5.2.3.3.4. カスタマイズ¶
WebClient.Builder
をカスタマイズし、WebClientにデフォルトで設定する方法を説明する。5.2.3.3.4.1. コネクションプールの設定¶
Bean定義の実装例(WebClientConfig.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;
}
項番 | 説明 |
---|---|
(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を登録する。 |
5.2.3.3.4.2. フィルターの設定¶
org.springframework.web.reactive.function.client.ExchangeFilterFunction
を実装することで、サーバとの通信処理前に任意の処理を実行させることができる。doOnSuccess
に実装すればよい。通信ログ出力の実装例
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<ClientResponse> filter(ClientRequest request,
ExchangeFunction next) {
LOGGER.info("External Request to {}", request.url()); // (2)
return next.exchange(request); // (3)
}
}
項番 | 説明 |
---|---|
(1)
|
ExchangeFilterFunction インタフェースを実装する。 |
(2)
|
非同期リクエストを送信する前に実行する処理を実装する。
上記の実装例では、通信先のURLを出力している。
|
(3)
|
次の
ExchangeFilterFunction を呼び出す。 |
Bean定義の実装例(WebClientConfig.java)
@Configuration
public class WebClientConfig {
@Bean
public WebClient loggingWebClient() {
// @formatter:off
WebClient webClient = WebClient.builder()
.filter(new LoggingExchangeFilterFunction()) // (1)
.build();
// @formatter:on
return webClient;
}
項番 | 説明 |
---|---|
(1)
|
ExchangeFilterFunction の実装クラスをfilterに登録する。Note 複数のFilterを登録したい場合は、
リストに登録された順番に処理されるようになる。 |
5.2.4. Appendix¶
5.2.4.1. HTTP Proxyサーバの設定方法¶
RestTemplate
のBean定義にてHTTP Proxyサーバの設定が必要となる。RestTemplate
毎にHTTP Proxyサーバの設定を行う例を紹介する。RestTemplate
毎のHTTP Proxyサーバの設定は、ClientHttpRequestFactory
インタフェースのデフォルト実装であるSimpleClientHttpRequestFactory
に付与することが可能である。ただし、SimpleClientHttpRequestFactory
では資格情報を設定することはできないため、Proxy認証を行う場合はHttpComponentsClientHttpRequestFactory
を使用する。HttpComponentsClientHttpRequestFactory
は、Apache HttpComponents HttpClient
を用いてリクエストを生成するClientHttpRequestFactory
インタフェースの実装クラスである。5.2.4.1.1. SimpleClientHttpRequestFactoryを使用したHTTP Proxyサーバの設定方法¶
資格情報が不要なHTTP Proxyサーバの接続先の指定については、RestTemplate
がデフォルトで使用するSimpleClientHttpRequestFactory
で指定することが可能である。
Bean定義ファイル
<!-- (1) -->
<bean id="inetSocketAddress" class="java.net.InetSocketAddress" >
<constructor-arg name="hostname" value="${rscl.http.proxyHost}" /> <!-- (2) -->
<constructor-arg name="port" value="${rscl.http.proxyPort}" /> <!-- (3) -->
</bean>
<!-- (4) -->
<bean id="simpleClientRestTemplate" class="org.springframework.web.client.RestTemplate" >
<constructor-arg>
<!-- (5) -->
<bean class="org.springframework.http.client.SimpleClientHttpRequestFactory">
<!-- (6) -->
<property name="proxy">
<bean class="java.net.Proxy" >
<!-- (7) -->
<constructor-arg name="type">
<util:constant static-field="java.net.Proxy.Type.HTTP"/>
</constructor-arg>
<constructor-arg name="sa" ref="inetSocketAddress"/>
</bean>
</property>
</bean>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(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 を設定する。 |
5.2.4.1.2. HttpComponentsClientHttpRequestFactoryを使用したHTTP Proxyサーバの設定方法¶
5.2.4.1.2.1. HTTP Proxyサーバの指定方法¶
資格情報が必要なHTTP Proxyサーバの接続先の指定は、RestTemplate
に対して、HttpComponentsClientHttpRequestFactory
を使用し指定する。
pom.xml
<!-- (1) -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
項番 | 説明 |
---|---|
(1)
|
HttpComponentsClientHttpRequestFactory 内で使用するApache HTTP Client を使用するために、Apache HttpComponents Client をpom.xml の依存ライブラリに追加する。 |
Note
上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。
上記の依存ライブラリはterasoluna-gfw-parentが依存しているSpring Bootで管理されている。
Bean定義ファイル
<!-- (1) -->
<bean id="proxyHttpClientBuilder" class="org.apache.hc.client5.http.impl.classic.HttpClientBuilder" factory-method="create">
<!-- (2) -->
<property name="proxy">
<bean class="org.apache.hc.core5.http.HttpHost">
<constructor-arg name="hostname" value="${rscl.http.proxyHost}" />
<constructor-arg name="port" value="${rscl.http.proxyPort}" />
</bean>
</property>
</bean>
<!-- (5) -->
<bean id="proxyRestTemplate" class="org.springframework.web.client.RestTemplate" >
<constructor-arg>
<!-- (6) -->
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<!-- (7) -->
<constructor-arg>
<bean factory-bean="proxyHttpClientBuilder" factory-method="build" />
</constructor-arg>
</bean>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(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 を設定する。 |
5.2.4.1.2.2. HTTP Proxyサーバの資格情報の指定方法¶
HTTP Proxyサーバにアクセスする際に資格情報(ユーザ名とパスワード)が必要な場合は、org.apache.http.impl.client.BasicCredentialsProvider
を使用し資格情報を設定する。
BasicCredentialsProvider
のsetCredentials
メソッドが引数を2つ取るため、セッターインジェクションを利用してBeanを生成することができない。このため、org.springframework.beans.factory.FactoryBean
を利用してBeanを生成する。
FactoryBeanクラス
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<BasicCredentialsProvider> {
// (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;
}
}
項番 | 説明 |
---|---|
(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定義ファイル
<bean id="proxyHttpClientBuilder" class="org.apache.hc.client5.http.impl.classic.HttpClientBuilder" factory-method="create">
<!-- (1) -->
<property name="defaultCredentialsProvider">
<bean class="com.example.restclient.BasicCredentialsProviderFactoryBean" />
</property>
<property name="proxy">
<bean id="proxyHost" class="org.apache.hc.core5.http.HttpHost">
<constructor-arg name="hostname" value="${rscl.http.proxyHost}" />
<constructor-arg name="port" value="${rscl.http.proxyPort}" />
</bean>
</property>
</bean>
<bean id="proxyRestTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<constructor-arg>
<bean factory-bean="proxyHttpClientBuilder" factory-method="build" />
</constructor-arg>
</bean>
</constructor-arg>
</bean>
項番 | 説明 |
---|---|
(1)
|
HttpClientBuilder のdefaultCredentialsProvider プロパティに、BasicCredentialsProvider を設定する。BasicCredentialsProvider は、FactoryBean を実装したBasicCredentialsProviderFactoryBean を使用しBeanを作成する。 |