Spring Boot 1.4+でRestTemplate(HTTPクライアント)を使う

Spring Boot 1.4から`RestTemplate`のBuilderクラス(`org.springframework.boot.web.client.RestTemplateBuilder`)が追加され、自動コンフィギュレーションされます。`RestTemplateBuilder`を利用すると、共通的なコンフィギュレーションを適用しつつ、個別のカスタマイズも簡単に適用することができるようになります。

> **Note: RestTemplateって何もの？**
>
> `RestTemplate`は、Spring Frameworkが提供しているHTTPクライアントクラスです。デフォルトではJava標準の`HttpURLConnection`が使われますが、Apache HttpClient、Netty 4、OkHTTP 2/3を利用してHTTP通信することもできます。
> `RestTemplate`については、[TERASOLUNAのガイドライン](http://terasolunaorg.github.io/guideline/5.2.0.RELEASE/ja/ArchitectureInDetail/RestClient.html)をみるとよいでしょう :smile: 

# 動作検証環境

* Spring Boot 1.4.0.RELEASE

# 開発プロジェクトの作成

開発プロジェクトは、[SPRING INITIALIZR](https://start.spring.io/)から作成できます。その際、Dependenciesに「Web」を指定することを忘れないでください！！
なお、本投稿の内容はArtifactに「demo-resttemplate」を指定した前提で説明しています。

# `RestTemplateBuilder`を使ってみる

ここでは、GitHub上で管理している`README.md`の中身を返却するREST APIを作ってみます。

```java:src/main/java/com/example/DemoResttemplateApplication.java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestOperations;

@SpringBootApplication
public class DemoResttemplateApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoResttemplateApplication.class, args);
    }

    @RestController
    static class DemoRestController {

        private final RestOperations restOperations;

        public DemoRestController(RestTemplateBuilder restTemplateBuilder) { // RestTemplateBuilderをコンストラクタインジェクション！！
            this.restOperations = restTemplateBuilder.build(); // Builderのbuildメソッドを呼び出しRestTemplateを生成
        }

        @GetMapping("/readme")
        String readme() {
            // GitHubのとあるリポジトリのREADME.mdにアクセスして取得したBODYを返却
            return restOperations.getForObject("https://raw.githubusercontent.com/kazuki43zoo/qiita-materials/master/README.md", String.class);
        }
    }

}
```

Spring Bootを起動します。

```bash:コンソール
$ ./mvnw spring-boot:run
...
2016-08-06 11:14:35.033  INFO 46375 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-08-06 11:14:35.038  INFO 46375 --- [           main] com.example.DemoResttemplateApplication  : Started DemoResttemplateApplication in 1.829 seconds (JVM running for 4.118)
```

Spring Bootに組み込まれているTomcatが8080ポートで立ち上がったので、`http://localhost:8080/readme`にアクセスしてみましょう。ブラウザでもよいですが、ここではcurlコマンドを使ってアクセスしてみます。

```text:コンソール
$ curl -D - http://localhost:8080/readme
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 47
Date: Sat, 06 Aug 2016 02:17:13 GMT

# qiita-materials
Materials for Qiita articles
```

答え合わせのために本体にもアクセスしてみます。レスポンスヘッダー以外は同じですね :v: 

```text:コンソール
$ curl -D - https://raw.githubusercontent.com/kazuki43zoo/qiita-materials/master/README.md
HTTP/1.1 200 OK
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-XSS-Protection: 1; mode=block
ETag: "d84556f2372fa145a69cc7e17af7f257b4e697a9"
Content-Type: text/plain; charset=utf-8
Cache-Control: max-age=300
X-Geo-Block-List:
X-GitHub-Request-Id: 2BF94822:2E50A:1DC63E7:57A548AA
Content-Length: 47
Accept-Ranges: bytes
Date: Sat, 06 Aug 2016 02:18:10 GMT
Via: 1.1 varnish
Connection: keep-alive
X-Served-By: cache-nrt6133-NRT
X-Cache: HIT
X-Cache-Hits: 1
Vary: Authorization,Accept-Encoding
Access-Control-Allow-Origin: *
X-Fastly-Request-ID: 3d88bff242a7a53376924b0fc48778eff613931d
Expires: Sat, 06 Aug 2016 02:23:10 GMT
Source-Age: 56

# qiita-materials
Materials for Qiita articles
```

なお、このサンプルコードはGitHubで公開しています。

* https://github.com/kazuki43zoo/qiita-materials/tree/master/spring-boot/demo-resttemplate

# 直接`RestTemplate`使うのとなんか違うの？

上のようなシンプルな例だと、ほぼ同じですね :sweat_smile: 
でも全く同じではないのです。それは、仮にApache  HttpClient、Netty、OkHTTPのライブラリがクラスパス上にあると、それらを自動検出してくれるからです。これは直接`RestTemplate`を使うとサポートされていない仕組みです。

以降では、`RestTemplateBuilder`の機能を簡単に紹介していきます。

# 3rdパーティ製ライブラリとの連携

`RestTemplate`は、Java標準の`HttpURLConnection`に加えて、Apache HttpClient、Netty 4、OkHTTP 2/3を利用してHTTP通信を行うことができます。`RestTemplateBuilder`を使用すると、クラスパス上にあるクラスを検査して利用するライブラリを自動検出してくれます。

仮にすべてのライブラリがクラスパス上にあった場合の優先順位は以下の通りです。

* Apache HttpClient
* OkHTTP 3
* OkHTTP 2
* Netty
* Java標準の`HttpURLConnection` (デフォルト)

# タイムアウトの指定

HTTP通信のタイムアウト（接続タイムアウト、読み込みタイムアウト）を簡単に指定することができます。

```java
this.restOperations = restTemplateBuilder
        .setConnectTimeout(3000)
        .setReadTimeout(5000)
        .build();
```

# Basic認証用のAuthorizationヘッダの追加

Basic認証が必要なサイトにアクセスする際に指定するユーザ名とパスワードを簡単に指定することができます。

```java
this.restOperations = restTemplateBuilder
        .basicAuthorization("username", "password")
        .build();
```

# ルートURI(ベースURI)の指定

URI内のルート(ベース)部分を指定することができます。

```java
this.restOperations = restTemplateBuilder
        .rootUri("https://raw.githubusercontent.com")
        .build();
```

実際にリソースにアクセスする際は、ルートURI(ベースURI)以降のパスを指定するだけでOKです。

```java
@GetMapping("/readme")
String readme() {
    return restOperations.getForObject("/kazuki43zoo/qiita-materials/master/README.md", String.class);
}
```

# `HttpMessageConverter`のカスタマイズ

デフォルトでいくつかの標準的な`HttpMessageConverter`と開発者がBean定義した`HttpMessageConverter`が自動で組み込まれる仕組みになっていますが、明示的に追加したり、使用するクラスを限定したりすることもできます。

追加する場合は、`additionalMessageConverters`メソッドを利用します。
 
```java
this.restOperations = restTemplateBuilder
        .additionalMessageConverters(new CustomHttpMessageConverter())
        .build();
```

適用する`HttpMessageConverter`を限定する場合は、`messageConverters`メソッドを利用します。このメソッドを使うとデフォルトで組み込まれる`HttpMessageConverter`は無効化されます。

```java
this.restOperations = restTemplateBuilder
        .messageConverters(new CustomHttpMessageConverter())
        .build();
```

`RestTemplate`に適用されるデフォルトの`HttpMessageConverter`だけを使いたい場合は、`defaultMessageConverters`メソッドを利用します。

```java
this.restOperations = restTemplateBuilder
        .defaultMessageConverters()
        .build();
```

# エラー応答のカスタマイズ :+1: 

`RestTemplate`では、HTTPステータスがエラー系(4xx, 5xx)の場合は、専用の例外が発生します。この動作をカスタマイズしたい場合は、`ResponseErrorHandler`インタフェースの実装クラスを作成して`RestTemplate`に適用する必要があります。

```java
this.restOperations = restTemplateBuilder
        .errorHandler(new CustomResponseErrorHandler())
        .build();
```

# URIテンプレートの扱いのカスタマイズ

接続先を指定する際にURIテンプレートを使用することができますが、このURIテンプレートの扱いをカスタマイズしたい場合は、`UriTemplateHandler`インタフェースの実装クラスを作成して`RestTemplate`に適用する必要があります。

```java
this.restOperations = restTemplateBuilder
        .uriTemplateHandler(new CustomUriTemplateHandler)
        .build();
```

実際には`、UriTemplateHandler`の実装クラスを作成するのではなく、`DefaultUriTemplateHandler`をカスタマイズしたものを設定するケースの方が多いと思います。以下は、「RFC 3986のセクション２」で定義されている「unreserved」以外の文字もURLエンコーディングするようにカスタマイズしています。詳しくは「[こちら](http://qiita.com/kazuki43zoo/items/53d2ca25c174ff618331#resttemplate%E3%81%A8asyncresttemplate%E3%81%AB%E3%81%A6uri%E5%A4%89%E6%95%B0%E5%80%A4%E3%81%AB%E5%8E%B3%E6%A0%BC%E3%81%AAurl%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%92%E9%81%A9%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B-1)」をご覧ください。

```java
DefaultUriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();
uriTemplateHandler.setStrictEncoding(true);

this.restOperations = restTemplateBuilder
        .uriTemplateHandler(uriTemplateHandler)
        .build();
```

# HTTP通信クラスの指定

`RestTemplate`は、Java標準の`HttpURLConnection`に加えて、Apache HttpClient、Netty 4、OkHTTP 2/3を利用してHTTP通信を行うことができます。`RestTemplateBuilder`を使用すると、クラスパス上にあるクラスを検査して利用するライブラリを自動検出してくれますが、明示的に使うクラスを指定することもできます。

```java
this.restOperations = restTemplateBuilder
        .uriTemplateHandler(new OkHttp3ClientHttpRequestFactory()) // OkHTTP 3を利用する場合
        .build();
```

なお、自動検出を無効化することもできます。この場合は、Java標準の`HttpURLConnection`が使われます。

```java
this.restOperations = restTemplateBuilder
        .detectRequestFactory(false)
        .build();
```

# `RestTemplate`カスタマイズ用のコールバックインタフェースの利用

Spring Boot 1.4から、`RestTemplateBuilder`経由で生成する`RestTemplate`をカスタマイズするためのコールバックインタフェース(`org.springframework.boot.web.client.RestTemplateCustomizer`)が追加されています。

```java:RestTemplateCustomizerの抜粋
package org.springframework.boot.web.client;
import org.springframework.web.client.RestTemplate;

public interface RestTemplateCustomizer {
    void customize(RestTemplate restTemplate);
}
```

共通的なカスタマイズを行う場合は、個別にカスタマイズするのではなく、`RestTemplateCustomizer`の実装クラスを作成するのがよいでしょう。

```java
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
    @Override
    public void customize(RestTemplate restTemplate) {
        DefaultUriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();
        uriTemplateHandler.setStrictEncoding(true);
        new RestTemplateBuilder()
                .detectRequestFactory(false)
                .basicAuthorization("uesr", "password")
                .uriTemplateHandler(uriTemplateHandler)
                .errorHandler(new CustomResponseErrorHandler())
                .configure(restTemplate);
    }
}
```

```java
this.restOperations = restTemplateBuilder
        .additionalCustomizers(new CustomRestTemplateCustomizer())
        .build();
```

上の例では明示的に`CustomRestTemplateCustomizer`を適用してますが、DIコンテナに登録しておけば自動で`CustomRestTemplateCustomizer`を検出してくれる仕組みになっているので、以下のようにすることもできます。

```java
@Component // DIコンテナに登録
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
    @Override
    public void customize(RestTemplate restTemplate) {
        ...
    }
}
```
```java
this.restOperations = restTemplateBuilder.build(); // 明示的にadditionalCustomizersを呼び出す必要なし
```

# テスト用の`RestTemplate` (付録)

Spring Bootでは、REST APIなどのE2Eテスト向けに`TestRestTemplate`というクラスを提供しています。Spring Boot 1.4より前のバージョンでは`RestTemplate`の子クラスとして実装されていましたが、Spring 1.4からは`RestTemplate`は継承していません。正確にいうと、`RestTemplate`を継承しているクラスは非推奨になり、別パッケージに同名で新しいクラスが作られています。

* 1.4から非推奨：`org.springframework.boot.test.TestRestTemplate`
* 1.4から追加：`org.springframework.boot.test.web.client.TestRestTemplate`

本投稿の冒頭で紹介したサンプルコードに対するE2Eテストを実装すると以下のようなコードになります。

```java:src/test/java/com/example/DemoResttemplateApplicationTests.java
package com.example;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // ローカルの空きポートを使って組み込みTomcatを起動
public class DemoResttemplateApplicationTests {

    @Autowired
    TestRestTemplate testRestTemplate; // ローカルの空きポートで起動した組み込みTomcatにアクセスするためのURIが「ルート(ベース)URI」に設定されるTestRestTemplateがDIされる

    @Test
    public void contextLoads() {
        String resource = testRestTemplate.getForObject("/readme", String.class); // 「http://localhost:port」の部分の指定は不要
        Assertions.assertThat(resource)
                .isEqualTo("# qiita-materials\nMaterials for Qiita articles\n");
    }

}
```

いい感じです。

# まとめ

Spring Boot(1.4+)で`RestTemplate`を使う場合は、`RestTemplateBuilder`と`CustomRestTemplateCustomizer`を利用しましょう。また、REST APIのE2Eテストは`TestRestTemplate`を使って自動化しておくことを強くオススメします。（`TestRestTemplate`じゃなくてもいいけど、自動化しておきましょう！！）

# 参考サイト

* http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#boot-features-restclient
* http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#boot-features-rest-templates-test-utility
* http://terasolunaorg.github.io/guideline/5.2.0.RELEASE/ja/ArchitectureInDetail/RestClient.html