経緯:
サービス内において、Faradayを利用して外部のAPIをcallしている(salesforceとかそういうとこ)
だがリクエストに対してたまにTimeoutエラー(Faraday::TimeoutError)を返してエラーになる
そこでFaradayにretryオプションを指定して特定のエラーが起きた時のみretryをかけたい。
https://github.com/lostisland/faraday/blob/master/lib/faraday/request/retry.rb
faradayの中を調べるとこう書けとある。
# @example Configure Retry middleware using intervals
# Faraday.new do |conn|
# conn.request(:retry, max: 2,
# interval: 0.05,
# interval_randomness: 0.5,
# backoff_factor: 2,
# exceptions: [CustomException, 'Timeout::Error'])
#
# conn.adapter(:net_http) # NB: Last middleware must be the adapter
# end
sampleの通りに書いたらretryできた。
コードを書いたらテストも書かないといけない。
テストを書かないとt_wadaに こんな感じ で「圧」をかけられる
ちなみにリクエストをテストするにはWebMockのstub_requestがよい
https://github.com/bblimke/webmock
サンプルにある通り
stub_request(:post, "www.example.com").
to_return(body: "abc")
こんな感じでテストが書ける。
テストの中でfaradayをMockせずとも外にリクエストを投げずにテストできる。
CIサーバの自動テストでも安心
今回やりたいタイムアウトの表現はこんな感じで書ける。
subject { (なんか post している処理) }
request = stub_request(:post, "www.example.com").
to_timeout.times(2).then. # 2回タイムアウトして
to_return(body: "abc") # その後正常に値が返る
expect(subject.status).to eq(200) # 正常にリクエストが返ってくる
expect(subject.body).to eq('abc')
expect(request).to have_been_requested.times(3)
エラーで終わる場合はこんな感じ
request = stub_request(:post, "www.example.com").
to_timeout.times(3).then. # 3回タイムアウトして
to_return(body: "abc") # もし次があればその後正常に値が返る
expect { subject }.to raise_error(Faraday::ConnectionFailed)
expect(request).to have_been_requested.times(3)
stub_requestはリクエストがパラメータなどの条件を満たさなかったり、3回呼ばれるはずが4回とか呼ばれたりするとエラーになるので安心
ただし回数が足らなくてもエラーにならないので、have_been_requested.times(回数)でチェックする必要がある
なお、実は上記テストは失敗する。
なぜかと言うと、webmockのstub_requestがto_timeoutで返すエラーは
Faraday::ConnectionFailedであり、faradayのretryされるエラーにはデフォルトではFaraday::ConnectionFailedは含まれない。
https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/net_http.rb#L64-L89
ここを見るとfaradayはtimeoutエラーを
・相手がrequestを受け取れなかったらFaraday::ConnectionFailed
・相手がresponseを返せずTimeout::Error, Errno::ETIMEDOUTになったらFaraday::TimeoutError
と区別している。
そしてfaradayのretryはデフォルトでは Faraday::ConnectionFailedをretry対象に含まない
対策は二つ。
テストをFaraday::TimeoutErrorに合わせる
request = stub_request(:post, "www.example.com").
to_raise(Faraday::TimeoutError).times(3) # 3回タイムアウトする
expect { subject }.to raise_error(Faraday::TimeoutError)
expect(request).to have_been_requested.times(3)
もう一つはfaradayにFaraday::Error::ConnectionFailedでもretryしてくれるよう設定する
Faraday.new do |conn|
conn.request(:retry, max: 2,
interval: 0.05,
exceptions: Faraday::Request::Retry::DEFAULT_EXCEPTIONS + [Faraday::Error::ConnectionFailed]
)
...
end
以上終わり