class Faraday::Request::Retry
Catches exceptions and retries each request a limited number of times.
By default, it retries 2 times and handles only timeout exceptions. It can be configured with an arbitrary number of retries, a list of exceptions to handle, a retry interval, a percentage of randomness to add to the retry interval, and a backoff factor.
Examples
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 ... end
This example will result in a first interval that is random between 0.05 and 0.075 and a second interval that is random between 0.1 and 0.15
Constants
- IDEMPOTENT_METHODS
Public Class Methods
Public: Initialize middleware
Options: max - Maximum number of retries (default: 2) interval - Pause in seconds between retries (default: 0) interval_randomness - The maximum random interval amount expressed
as a float between 0 and 1 to use in addition to the interval. (default: 0)
max_interval - An upper limit for the interval (default: Float::MAX) backoff_factor - The amount to multiple each successive retry's
interval amount by in order to provide backoff (default: 1)
exceptions - The list of exceptions to handle. Exceptions can be
given as Class, Module, or String. (default: [Errno::ETIMEDOUT, 'Timeout::Error', Error::TimeoutError, Faraday::Error::RetriableResponse])
methods - A list of HTTP methods to retry without calling retry_if. Pass
an empty Array to call retry_if for all exceptions. (defaults to the idempotent HTTP methods in IDEMPOTENT_METHODS)
retry_if - block that will receive the env object and the exception raised
and should decide if the code should retry still the action or not independent of the retry count. This would be useful if the exception produced is non-recoverable or if the the HTTP method called is not idempotent. (defaults to return false)
retry_block - block that is executed after every retry. Request environment, middleware options,
current number of retries and the exception is passed to the block as parameters.
# File lib/faraday/request/retry.rb, line 109 def initialize(app, options = nil) super(app) @options = Options.from(options) @errmatch = build_exception_matcher(@options.exceptions) end
Public Instance Methods
Private: construct an exception matcher object.
An exception matcher for the rescue clause can usually be any object that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
# File lib/faraday/request/retry.rb, line 155 def build_exception_matcher(exceptions) matcher = Module.new (class << matcher; self; end).class_eval do define_method(:===) do |error| exceptions.any? do |ex| if ex.is_a? Module error.is_a? ex else error.class.to_s == ex.to_s end end end end matcher end
# File lib/faraday/request/retry.rb, line 115 def calculate_sleep_amount(retries, env) retry_after = calculate_retry_after(env) retry_interval = calculate_retry_interval(retries) return if retry_after && retry_after > @options.max_interval retry_after && retry_after >= retry_interval ? retry_after : retry_interval end
# File lib/faraday/request/retry.rb, line 124 def call(env) retries = @options.max request_body = env[:body] begin env[:body] = request_body # after failure env[:body] is set to the response body @app.call(env).tap do |resp| raise Faraday::Error::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status) end rescue @errmatch => exception if retries > 0 && retry_request?(env, exception) retries -= 1 rewind_files(request_body) @options.retry_block.call(env, @options, retries, exception) if (sleep_amount = calculate_sleep_amount(retries + 1, env)) sleep sleep_amount retry end end if exception.is_a?(Faraday::Error::RetriableResponse) exception.response else raise end end end
Private Instance Methods
MDN spec for Retry-After header: developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
# File lib/faraday/request/retry.rb, line 187 def calculate_retry_after(env) response_headers = env[:response_headers] return unless response_headers retry_after_value = env[:response_headers]["Retry-After"] # Try to parse date from the header value begin datetime = DateTime.rfc2822(retry_after_value) datetime.to_time - Time.now.utc rescue ArgumentError retry_after_value.to_f end end
# File lib/faraday/request/retry.rb, line 202 def calculate_retry_interval(retries) retry_index = @options.max - retries current_interval = @options.interval * (@options.backoff_factor ** retry_index) current_interval = [current_interval, @options.max_interval].min random_interval = rand * @options.interval_randomness.to_f * @options.interval current_interval + random_interval end
# File lib/faraday/request/retry.rb, line 173 def retry_request?(env, exception) @options.methods.include?(env[:method]) || @options.retry_if.call(env, exception) end
# File lib/faraday/request/retry.rb, line 177 def rewind_files(body) return unless body.is_a?(Hash) body.each do |_, value| if value.is_a? UploadIO value.rewind end end end