Laravel7.x から新しくなった Http クライアントのご紹介

[Laravel Advent Calendar 2020 - Qiita](https://qiita.com/advent-calendar/2020/laravel) の 22目 の記事です。
昨日は @nunulk さんの[Controller, UseCase, Service (および Model) の役割分担についての考察](https://qiita.com/nunulk/items/bc7c93a3dfb43b01dfab)の記事でした！
明日は @Lob さんの[Laravel on GAE Standard 環境構築の手引き](https://qiita.com/Lob/items/f11fbb852d179d83523c)の記事です！

## 概要

Laravel 6.x 以前は[GuzzleHTTPクライアント](https://github.com/guzzle/guzzle)をそのまま使用する形でしたが、Guzzleのラッパーとして新たにHTTPクライアントが搭載されました。
(Laravel内部ではGuzzleが使用されてます。)

## インストール

```shell-session
$ composer require guzzlehttp/guzzle
```

最新バージョンではデフォルトでGuzzleはインストールされているはず。

## ソースコード、ドキュメント

### Laravel7

- https://readouble.com/laravel/7.x/ja/http-client.html
- https://github.com/laravel/framework/tree/7.x/src/Illuminate/Http
- https://laravel.com/api/7.x/Illuminate/Http.html

### Laravel8

- https://readouble.com/laravel/8.x/ja/http-client.html
- https://github.com/laravel/framework/tree/8.x/src/Illuminate/Http
- https://laravel.com/api/8.x/Illuminate/Http.html

## Laravel HTTPクライアントの特徴

1. チェーンメソッド可能なリクエスト
2. JSONレスポンスへの簡単なアクセス
3. シンプルなリクエストを行うための定型的なセットアップが不要
4. 失敗したリクエストの再試行(リトライ)
5. 認証ヘッダー（ベーシック、ダイジェスト、ベアラー）の便利メソッド
6. テストのモック、スタブ

## 基本的な使い方

```php
use Illuminate\Support\Facades\Http;

// http response
$response = Http::get('https://github.com');
$response->body();

// json response
$response = Http::get('https://api.github.com');
$data = $response->json();
```

## データへのアクセス

`json()` を介してResponseデータにアクセスできます。

```php
$response = Http::get('https://api.github.com');
$data = $response->json();
$data['current_user_url']; 
```

Responseオブジェクトにも配列のようにアクセスできます。

```php
$response = Http::get('https://api.github.com');
$response['current_user_url'];
```

## Laravel 6.x 以前の書き方

```php
use GuzzleHttp\Client;

$client = new Client();
$response = $client->request('GET', 'https://api.github.com');
$body = $response->getBody();
$data = json_decode($body, true);
$data['current_user_url'];
```

色々面倒ですよね...
Laravel 7.x 以降でも上記の書き方は可能です。

## エラー処理

### Laravel 6.x 以前の書き方

```php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

try {
    $client = new Client();
    $client->request('GET', 'https://github.com/_abc_123_404');
} catch (RequestException $e) {
    if ($e->hasResponse()) {
        dump($e->getResponse());
    }
}
```

失敗すると例外が発生するので、try 〜 catch で囲む必要がありました。

```php
use Illuminate\Support\Facades\Http;

$response = Http::get('https://github.com/_abc_123_404');

if ($response->clientError()) {
    dump($response);
}
```

if 文でリクエスト結果に応じて条件分岐できて便利です。
メソッドもそれぞれ用意されてます。

```php
$response->ok();          // 200 == http status code
$response->successful();  // 200 <= http status code < 300
$response->failed();      // 400 <= http status code
$response->clientError(); // 400 <= http status code < 500
$response->serverError(); // 500 <= http status code
```

## タイムアウト

デフォルトのタイムアウトはとても長いです。(サーバー設定によってまちまちですが)
Laravel HTTPでは、`timeout()` メソッドで自分で定義できます。

```php
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\ConnectionException;

try {
    $response = Http::timeout(5)->get('http://127.0.0.1/api/slow');
} catch (ConnectionException $e) {
    // cURL error 28: Operation timed out after 5001 milliseconds with 0 bytes received (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://127.0.0.1/api/slow
    dump($e->getMessage());
}
```

- タイムアウトが発生すると `Illuminate\Http\Client\ConnectionException` の例外が発生します。
- `timeout()` は秒単位で定義できます。

## 失敗したリクエストの再試行(リトライ)

クライアントもしくはサーバー側のエラーが発生した場合にリトライ処理を行わせます。

```php
// 試行回数3回、待機時間100ミリ秒
$response = Http::retry(3, 100)->post(...);
```

## 認証ヘッダー（ベーシック、ダイジェスト、ベアラー）の便利メソッド

```php
// Basic認証
$response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(...);

// Digest認証
$response = Http::withDigestAuth('taylor@laravel.com', 'secret')->post(...);

// Bearerトークン
$response = Http::withToken('token')->post(...);
```

## テストのモック、スタブ

```php
use Illuminate\Support\Facades\Http;

Http::fake();

$response = Http::get('https://api.github.com');
$response->json(); // null
$response->status(); // 200
```

`Http::fake();` と1行入れると実際のURLを叩くことなく、200ステータスで空のレスポンスボディを返してくれます。

とても便利ですね！

```php
use Illuminate\Support\Facades\Http;

Http::fake([
    'api.github.com*' => Http::response(['current_user_url' => 'http://example.com']),
]);

$response = Http::get('https://api.github.com');
$response->json(); // ["current_user_url" => "http://example.com"]
$response->status(); // 200

$response = Http::get('https://api.github.com/abc/xyz');
$response->json(); // ["current_user_url" => "http://example.com"]
$response->status(); // 200
```

このURLを叩いたらこんなレスポンスを返す。
といった定義ももちろん書けます。

APIのテストもしやすくなりますね！

## dump, dd ヘルパーメソッド

- 2021.03.06追記
- [[8.x] Helper methods to dump requests of the Laravel HTTP client](https://github.com/laravel/framework/pull/36466)

上記のPRにて、`helper`, `dd`ヘルパーメソッドが追加されてます。
このヘルパーメソッドを利用すると送信されたリクエストの中身を確認できます。

```php
use Illuminate\Support\Facades\Http;

Http::dump()->get($url);
Http::dd()->post($url);
```

`Laravel 8.31.0` 以降に利用できます。

## 詳しくは...

公式ドキュメントが丁寧にまとめられているので、こちらをご参照ください。

- https://readouble.com/laravel/7.x/ja/http-client.html
- https://readouble.com/laravel/8.x/ja/http-client.html

## 所感

Laravel7.xが出てからだいぶ経ちましたが、Httpクライアントを使う機会がなく記事にまとめることができてませんでした。
この機会にまとめることができてよかったです。

## 参考

- https://laravel-news.com/laravel-http-client