Kotlin + Rest TemplateでのHttpリクエスト(GET編)

# Rest Templateとは
Rest TemplateはREST APIを呼び出すためにSpring Frameworkが提供しているHTTPクライアントです。
Httpヘッダやボディ部を設定し、指定のURLにGETまたはPOSTで送信、返ってきた結果をjsonやobjectとして取得するなどが主な機能として挙げられます。
雑にまとめてしまえば`curl`コマンドをプログラムで叩くためのものです。

# 目次
・環境
・Beanの設定
・getForObject
・exchange
　・応用編
・おまけ
・参考文献

# 環境
Kotlin:1.3.21
Spring Boot:2.1.5

# Beanの設定
（※ご指摘のコメントをいただいたので追記。dais39さん、ありがとうございます。）

デフォルトのSpring BootではRestTemplateのBeanは自動で生成されないので、そのままだとDIコンテナから使用することができません。
なので`RestTemplateBuilder`を使用してBeanをDIコンテナに登録します。

```kotlin:RestConfig.kt
@Configuration
class RestConfig{
  @Bean
  fun restTemplate(builder: RestTemplateBuilder): RestTemplate{
    return builder.build()
  }
}
```

今回は`@Configuration`クラスを用いて定義しました。



# getForObject
get送信をする方法には`getForObject`メソッドを使う方法と`exchange`メソッドを使う方法の二種類があります。
主に`getForObject`はリクエストヘッダを送る必要がない場合に使用されます。

今回はSpring Bootが提供している`http://gturnquist-quoters.cfapps.io/api/random`にアクセスしてjsonと自作のオブジェクト`SampleObject`を取得してみます。

```kotlin:SampleController.kt
import ...

@RestController
// DIコンテナに登録されているRestTemplate()を注入します。
class SampleController(private val restTemplate: RestTemplate) {

    // リクエスト送信先のURLを設定します。
    private val uri: String = "http://gturnquist-quoters.cfapps.io/api/random"

    // jsonファイルでのレスポンス取得
    @GetMapping("/get/json")
    fun getJson(): String? {
        return restTemplate.getForObject(uri, String::class.java)
    }

    // objectでのレスポンス取得
    @GetMapping("/get/object")
    fun getObject(): String? {
        val sampleObject = restTemplate.getForObject(uri, SampleObject::class.java)

        return sampleObject.quote
    }
}
```

```kotlin:SmapleObject.kt
import ...

data class SampleObject (
    val type: String,

    val value: Value
)

data class Value (
    val id: Int,

    val quote: String
)
```

これ[localhost:8080/get/json](http://localhost:8080/get/json)にアクセスするとjsonファイルが、[localhost:8080/get/object](http://localhost:8080/get/object)にアクセスするとランダムで表示される`quote`の値が表示されるようになりました。

もしクエリパラメータも送信したい場合はURLに文字列を合体させるか、exchangeの項目で説明する`UriComponentsBuilder`を使用しましょう。

# exchange
`exchange`メソッドはGET、POSTどちらでも送信することができます。
主にリクエストヘッダを用いてBasic認証やOAuth認証をする必要がある際に使用されます。

```kotlin:SampleController.kt
import ...

@RestController
class SampleController(private val restTemplate: RestTemplate) {

    private val uri: String = "http://gturnquist-quoters.cfapps.io/api/random"

    @GetMapping("/get/exchange")
    fun exchange(): ResponseEntity<String> {
        return rt.exchange(url, HttpMethod.GET, null, String::class.java)
    }
```

[localhost:8080/get/exchange](http://localhost:8080/get/exchange)にアクセスするとResponseEntity<String>型の結果が返されます。（見た目はjsonとほぼ一緒）

## 応用編

以下のコードでは次のようなことを行なっています。

1.UriComponentsBuilderを使って送信先URIの設定（パスやクエリーの設定）
2.リクエストヘッダの設定（Basic認証の準備）
3.exchangeメソッドでResponseEntityを取得し、それのbody部分のみ（json）を取得

認証が必要ないい感じのAPIがなかったため、今回は適当な架空のURLを設定しました。
また処理を理解しやすくするためにかなり助長に書いています。

```kotlin:SampleController.kt
import ...

@RestController
class SampleController(private val restTemplate: RestTemplate) {

    @GetMapping("/get/exchange/hoge")
    fun getExchange(): String? {

        // 1.リクエスト送信先のURLを設定します。
        // リクエスト送信先のベースURLを設定します。
        private val baseUri: String = "http://hogehoge.com"

        // 送信先のパスとクエリーを設定します。
        val getInstanceUrl = UriComponentsBuilder.fromHttpUrl(baseUri)
                .path("/api/fuga")
                .queryParam("comment", "test-text")
                .build()
                .toUri()
        // これで　getInstanceUrl = "http://hogehoge.com/api/fuga?comment=test-text"　となります。


        // 2.リクエストヘッダを設定します。
        // Basic認証の設定
        val userid: String = "1234567890"
        val password: String = "pass_word"

        // バイト列に変換してBase64でエンコード
        val plainCredentials: String = userid + ":" + password
        val base64Credentials: String = Base64.getEncoder()
                .encodeToString(plainCredentials.getBytes(StandardCharsets.UTF_8))

        // AuthorizationヘッダにBasic認証の資格情報を設定する。
        val headers = HttpHeaders()
        headers.set("Authorization", "Basic " + base64Credentials)

        // HttpEntityにセット。
        val entity: HttpEntity<String> = HttpEntity(headers)


        // 3.やっとexchangeメソッドの出番
        // 引数にURL, 送信メソッド, Entity, レスポンスデータの型をそれぞれ指定します。
        // レスポンスはResponseEntity<指定した型>になります。
        val responseEntity: ResponseEntity<String> =  restTemplate.exchange(getInstancesURL, HttpMethod.GET, entity, String::class.java)

        // bodyメソッドを使用することでbody部のjsonのみを出力します。
        return responseEntity.body
    }
}
```

### 複数のクエリパラメータを使用したい場合

複数のクエリを送信したい場合`queryParam`メソッドを何度も使用するのは面倒です。
なので`queryParams`メソッドを使用します。
このメソッドは引数が`MultiValueMap<String, String>`なので注意しましょう。

```kotlin:
        val queryParams: Map<String, String> = mapOf("name" to "taro", "age" to "20")

        val uriParams: MultiValueMap<String, String> = LinkedMultiValueMap()
        queryParams.forEach { key, value -> uriParams.add(key, value) }

        val getInstancesURL = UriComponentsBuilder.fromHttpUrl("http://hogehoge.com/api/fuga")
            .queryParams(uriParams)
            .build()
            .toUri()

        // getInstanceUrl = "http://hogehoge.com/api/fuga?name=taro&age=20"
```

### OAuth認証を行いたい場合

今度はBasic認証ではなくOAuth認証に変更してみます。

```kotlin:
        val accessToken: String = "abc1234567890"

        val headers = HttpHeaders()
        headers.set("Authorization", "Bearer $accessToken")

        // HttpEntityにセット。
        val entity: HttpEntity<String> = HttpEntity(headers)
```


# おまけ

オブジェクトとして取得する際にdata classで設定されていない値が帰ってきた場合の処理方法。

こんな感じでdata classを作った時に

```kotlin:SmapleObject.kt
import ...

data class SampleObject (
    val type: String,

    val value: Value
)

data class Value (
    val id: Int,

    val quote: String
)
```

こんな感じのjsonを取得すると

```json:ResponseJson
{
    "type": "success", 
    "value": { 
       "id": 1, 
       "quote": "test dummy." 
    } 
    "created_at": 1476294126
} 
```

こんな感じのエラーが出てきます。

```
Unrecognized field "created_at" (Class jp.example.SampleObject), not marked as ignorable
```

なのでそんな時は以下のようにして、未定義の値をスルーするようにしましょう。

```kotlin:SampleObject.kt
@JsonIgnoreProperties(ignoreUnknown=true)
data class SampleObject (
...
)
```



# 参考文献
[5.17. RESTクライアント（HTTPクライアント）](https://terasolunaorg.github.io/guideline/5.1.0.RELEASE/ja/ArchitectureInDetail/RestClient.html)
[8.Springが用意するWEBクライアント（RestTemplate）の使い方](https://sites.google.com/site/soracane/home/springnitsuite/spring-no-ji-nengnitsuite/08-springga-yong-yisuruwebkuraianto-resttemplate-no-shii-fang)
[Spring BootでRestTemplateを使ってREST APIを呼び出す方法](https://reasonable-code.com/resttemplate/)
[SpringBoot：外部のサービスに接続](https://web-dev.hatenablog.com/entry/spring-boot/intro/ex-service)
[Get and Post Lists of Objects with RestTemplate](https://www.baeldung.com/spring-rest-template-list)
[Spring MVC+RestTemplateで配列JSONをJavaBeanの配列又はListに変換する](https://qiita.com/kazuki43zoo/items/08f67a531484a6857a4e)
[かんたん！KotlinでJSONパース【Jackson】](https://qiita.com/gumimin/items/a0ab4be8aea8930b5619)
[Spring解体新書](https://www.amazon.co.jp/gp/product/B07H6XLXD7/ref=ppx_yo_dt_b_d_asin_title_o03?ie=UTF8&psc=1)
[Jackson の痒いところ Tips](https://qiita.com/komiya_atsushi/items/6e5b8e6690b82bca8198)
[Jackson使い方メモ](https://qiita.com/opengl-8080/items/b613b9b3bc5d796c840c)
[jacksonでデシリアライズする際に未知のプロパティを無視する](https://tm8r.hateblo.jp/entry/20130814/1376450418)
[35. Calling REST Services with RestTemplate : Part IV. Spring Boot features](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-resttemplate.html)