FaradayでHTTPリクエストのリトライ処理を実装する

# はじめに
Railsのプロジェクトに限ったことではないですが外部のAPIを呼び出すはよくあることだと思います.
RubyにはいくつかHTTPクライアントのGemがありRailsのプロジェクトで利用できますが今回は`Faraday`というGemを用います。
APIが構えてあるサーバやAPIまでのネットワークなど一時的な高負荷などが原因でAPIへのリクエストが失敗したときに同じ内容のリクエストをかけたいケースがあります.
`Faraday`を用いることでリトライの処理が簡単に記述できるので紹介していきます。

https://github.com/lostisland/faraday

# インストール
``` ruby:Gemfile
gem 'faraday'
```
or

```bash:console
gem install faraday
```

# faraday-retry
`Faraday`でリトライを実装する場合`faraday`のとは別に`faraday-retry`が必要です.

``` ruby:Gemfile
gem 'faraday-retry'
```

or

```sh
# console
gem install faraday-retry
```

https://github.com/lostisland/faraday-retry

## ver 1.x vs 2.x
`faraday`の(2023/10/08)最新のバージョンは`2.7.11`ですが`1系`であれば`faraday`をインストールすれば`faraday-retry`がバンドルされてきますが`2系`の場合、別途`faraday-retry`をインストールする必要があるようです.
インストールする`faraday`のバージョンによって設定が異なるので注意が必要です.

## Gemfile.lock
### v1.10.0
``` ruby:Gemfile.lock
# === 略 ====
    faraday (1.10.0)
      faraday-em_http (~> 1.0)
      faraday-em_synchrony (~> 1.0)
      faraday-excon (~> 1.1)
      faraday-httpclient (~> 1.0)
      faraday-multipart (~> 1.0)
      faraday-net_http (~> 1.0)
      faraday-net_http_persistent (~> 1.0)
      faraday-patron (~> 1.0)
      faraday-rack (~> 1.0)
      faraday-retry (~> 1.0)
      ruby2_keywords (>= 0.0.4)
    faraday-em_http (1.0.0)
    faraday-em_synchrony (1.0.0)
    faraday-excon (1.1.0)
    faraday-httpclient (1.0.1)
    faraday-multipart (1.0.4)
      multipart-post (~> 2)
    faraday-net_http (1.0.1)
    faraday-net_http_persistent (1.2.0)
    faraday-patron (1.0.0)
    faraday-rack (1.0.0)
    faraday-retry (1.0.3)
# === 略 ====
```

### v2.7.11
``` ruby:Gemfile.lock
# === 略 ====
    faraday (2.7.11)
      base64
      faraday-net_http (>= 2.0, < 3.1)
      ruby2_keywords (>= 0.0.4)
    faraday-net_http (3.0.2)
# === 略 ====
```

# プログラムを書く
実際にプログラムを書いてリトライの挙動を確認してみます.
今回は簡単なAPIを作成して試してみます.
(命名は適当なので気にしないでください)

## リクエストする側
`index`を呼ぶと`faraday`でリクエスト処理が記述されたメソッドを呼びます.
リクエスト結果のステータスが`408`だった場合にリトライを最大5回するように設定してあります.
Faradayのインスタンスを`new`する際にretry用の設定を引数として渡してやればOKです.


[特定の例外](https://github.com/lostisland/faraday-retry#specify-which-exceptions-should-trigger-a-retry)が発生した場合にリトライされるようにもできます.

https://lostisland.github.io/faraday/#/middleware/included/raising-errors

``` ruby:show_controller.rb
class ShowController < ApplicationController

  def index
    response = api_request
    render json: response
  end
  
  private 

  def api_request
    retry_options = { # リトライの設定
      max: 5,
      interval: 0.05,
      interval_randomness: 0.5,
      backoff_factor: 2,
      methods: %i[get post],
      retry_statuses: [408]
    }

    connection = Faraday.new(url: "http://localhost:3001", headers: { 'Content-Type' => 'application/json' } ) do |f|
      f.request :retry, retry_options
      f.response :logger, Rails.logger
      f.adapter Faraday.default_adapter
    end

    res = connection.post('/server/api') do |req|
      req.headers['Content-Type'] = 'application/json'
      req.body = { hoge: 100 }.to_json
    end
    res
  end
end

```

## API側
ほぼ何もしないAPIですが`408`を返して強制的にタイムアウト判定させて`faraday`にリトライさせます.
``` ruby:server_controller.rb
class ServerController < ApplicationController
  def api
    render json: { hogehoge: 'api' }, status: 408
  end
end
```

## 実行
上のウィンドウで`show/index`を`curl`で呼び出してFaradayにリクエストさせています.
APIは必ず`408`ステータスを返すのでタイムアウトになりリトライをします.
`api_request`にメソッドにループや例外処理がないのにAPI側に5回リクエストされた形跡があり`Faraday`がリトライしたことが伺えます.

![qiita_curl.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311437/c79bc6b6-51e5-f447-c957-6dd0d9a1e13c.gif)

# まとめ
`Faraday`を用いたHTTPリクエストのリトライ処理を紹介しました、`Faraday`は高機能で他にも色々な機能があります.
`Faraday`を導入する場合、単なるHTTPのクライアントとしてのみ使うともったいないので数ある機能を深掘りして活用してくようにしたいです。