こんばんは。配達依存症がさっぱり治らないサーバサイドエンジニアの佐藤です。 この記事はトレタ Advent Calendar 2019の三日目です。
FaradayというHTTP クライアントライブラリ
みなさんRubyのFaradayというHTTP クライアントライブラリをご存知ですか?
Faradayを使えば外部のAPIと接続するクライアントを楽に記述することができます。ミドルウェアを差し込むことでリクエストやレスポンスをいじったり覗いたりすることができ、JSONに変換したい、アクセストークンを乗せたい、細かいログを取りたい、と言ったことを一箇所にまとめて記述することができます。いろいろできて便利なのですが、今日はその中でも raise_error middleware
という特定のステータスコードが返ってきた時例外を出してくれるミドルウェアに苦しんだ話を書きます。
raise_error middleware と retry middleware
raise_error middleware
は400や404と言ったClientErrorや500のようなServerErrorのステータスコードが返ってきた時に、それに応じた例外を出してくれます。これによってステータスコードを確認して処理を抜けるようなコードを書いたりする必要もなくrubyの例外処理を使って様々なErrorに対応することができます。
これとよく組み合わせて利用されるのが retry middleware
です。
retryはその名前の通り特定の例外や条件においてリクエストを自動でリトライさせることができます。例外がトリガーにできるため、上記raise_errorと組み合わせることでタイムアウトしてしまったらとりあえずもう一度やり直す、のような処理をmiddlewareの設定のみで行うことができるようになります。
raise_errorがraiseするもの
さてここまで揃えば話は単純で、raise_errorが上げてくれる諸々の例外に合わせて必要なretry設定をretry middlewareでしてあげれば良い。そう思っていました。 ところがこれ、実際にやってみると faraday (0.17.1) (執筆時最新バージョン)では期待通りに動作しません。なぜなら、404,407を除く400〜600は全てClientErrorが吐かれるからです。 (該当のソースコードはこちら)
これ最初知らなくてハマりました。よく出会うであろう404はちゃんと専用の Faraday::ResourceNotFound
が返ってくるんです。他の4xxが Faraday::ClientError
なのはわかるとして、5xxまでClientErrorだったのは最初全くの想定外で、そうだと理解できるまで結構な時間を費やしました。
ちなみに Faraday:: ServerError
ですが、ステータスコードが取得できないケース(ServerError)やタイムアウト時(TimeoutError)に出てきます。
5xxでリトライしてもらうには(4xxでリトライしないためには)
幸いなことにretry middlewareには retry_if
という条件を指定してリトライさせる機能があります。こちらを利用してClientErrorの中からさらに4xx以外の時にのみretryさせることができるようになります。ちょっと本末転倒感はあるんですが、それでもfaradayとmiddlewareの設定で完結させることができるために、見通しはよくなる気がします。
サンプルコード
conn = Faraday.new(:url => 'http://example.com') do |faraday| faraday.request :retry, methods: [], # retry_ifを無視してリトライするHTTP methods。無視して欲しく無いのでemptyにする exceptions: [Faraday::Error::TimeoutError, Faraday::Error::ConnectionFailed, Faraday::Error::ClientError], retry_if: ->(env, _exception) { !(400..499).include?(env.status) } # status codeの4xx以外ならリトライ faraday.response :raise_error faraday.adapter Faraday.default_adapter end
補足:次のバージョンで直るらしい
今faradayのmasterブランチを見にいくと、ServerErrorがちゃんとだし分けられています。faradayはv0.x、つまりまだ開発版なのでv1.0.0のリリースで書き換わるのでしょう。その際のバージョンアップでは注意が必要です。
余談ですが、0.16.2の時に一瞬このServerErrorへの変更がお漏らしして、必死こいて対応してました。ようやく修正が終わって、その苦労をblogにしてやろう・・・と思った矢先に0.17でロールバックされてしまったので、本記事はその供養も兼ねています。
今年のトレタのアドベントカレンダーはこちら!