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

  • 31
    いいね
  • 0
    コメント

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のガイドラインをみるとよいでしょう :smile:

動作検証環境

  • Spring Boot 1.4.0.RELEASE

開発プロジェクトの作成

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

RestTemplateBuilderを使ってみる

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

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を起動します。

コンソール
$ ./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コマンドを使ってアクセスしてみます。

コンソール
$ 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:

コンソール
$ 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で公開しています。

直接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通信のタイムアウト(接続タイムアウト、読み込みタイムアウト)を簡単に指定することができます。

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

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

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

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

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

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

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

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

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

HttpMessageConverterのカスタマイズ

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

追加する場合は、additionalMessageConvertersメソッドを利用します。

this.restOperations = restTemplateBuilder
        .additionalMessageConverters(new CustomHttpMessageConverter())
        .build();

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

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

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

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

エラー応答のカスタマイズ :thumbsup:

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

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

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

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

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

実際には、UriTemplateHandlerの実装クラスを作成するのではなく、DefaultUriTemplateHandlerをカスタマイズしたものを設定するケースの方が多いと思います。以下は、「RFC 3986のセクション2」で定義されている「unreserved」以外の文字もURLエンコーディングするようにカスタマイズしています。詳しくは「こちら」をご覧ください。

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を使用すると、クラスパス上にあるクラスを検査して利用するライブラリを自動検出してくれますが、明示的に使うクラスを指定することもできます。

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

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

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

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

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

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

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

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

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);
    }
}
this.restOperations = restTemplateBuilder
        .additionalCustomizers(new CustomRestTemplateCustomizer())
        .build();

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

@Component // DIコンテナに登録
public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {
    @Override
    public void customize(RestTemplate restTemplate) {
        ...
    }
}
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テストを実装すると以下のようなコードになります。

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を使う場合は、RestTemplateBuilderCustomRestTemplateCustomizerを利用しましょう。また、REST APIのE2EテストはTestRestTemplateを使って自動化しておくことを強くオススメします。(TestRestTemplateじゃなくてもいいけど、自動化しておきましょう!!)

参考サイト