class Faraday::Retry::Middleware
This class provides the main implementation for your middleware. Your middleware can implement any of the following methods:
-
on_request - called when the request is being prepared
-
on_complete - called when the response is being processed
Optionally, you can also override the following methods from Faraday::Middleware
-
initialize(app, options = {}) - the initializer method
-
call(env) - the main middleware invocation method. This already calls on_request and on_complete, so you normally don’t need to override it. You may need to in case you need to “wrap” the request or need more control (see “retry” middleware: github.com/lostisland/faraday/blob/main/lib/faraday/request/retry.rb#L142). IMPORTANT: Remember to call ‘@app.call(env)` or `super` to not interrupt the middleware chain!
Constants
- DEFAULT_EXCEPTIONS
- IDEMPOTENT_METHODS
Public Class Methods
@param app [#call] @param options [Hash] @option options [Integer] :max (2) Maximum number of retries @option options [Integer] :interval (0) Pause in seconds between retries @option options [Integer] :interval_randomness (0) The maximum random
interval amount expressed as a float between 0 and 1 to use in addition to the interval.
@option options [Integer] :max_interval (Float::MAX) An upper limit
for the interval
@option options [Integer] :backoff_factor (1) The amount to multiply
each successive retry's interval amount by in order to provide backoff
@option options [Array] :exceptions ([ Errno::ETIMEDOUT,
'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse]) The list of exceptions to handle. Exceptions can be given as Class, Module, or String.
@option options [Array] :methods (the idempotent HTTP methods
in IDEMPOTENT_METHODS) A list of HTTP methods to retry without calling retry_if. Pass an empty Array to call retry_if for all exceptions.
@option options [Block] :retry_if (false) 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.
@option options [Block] :retry_block block that is executed before
every retry. The block will be yielded keyword arguments: * env [Faraday::Env]: Request environment * options [Faraday::Options]: middleware options * retry_count [Integer]: how many retries have already occured (starts at 0) * exception [Exception]: exception that triggered the retry, will be the synthetic `Faraday::RetriableResponse` if the retry was triggered by something other than an exception. * will_retry_in [Float]: retry_block is called *before* the retry delay, actual retry will happen in will_retry_in number of seconds.
@option options [Array] :retry_statuses Array of Integer HTTP status
codes or a single Integer value that determines whether to raise a Faraday::RetriableResponse exception based on the HTTP status code of an HTTP response.
@option options [Block] :header_parser_block block that will receive
the the value of the retry header and should return the number of seconds to wait before retrying the request. This is useful if the value of the header is not a number of seconds or a RFC 2822 formatted date.
# File lib/faraday/retry/middleware.rb, line 127 def initialize(app, options = nil) super(app) @options = Options.from(options) @errmatch = build_exception_matcher(@options.exceptions) end
Public Instance Methods
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.
@param exceptions [Array] @api private @return [Module] an exception matcher
# File lib/faraday/retry/middleware.rb, line 185 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 Object.const_defined?(ex.to_s) && error.is_a?(Object.const_get(ex.to_s)) end end end end matcher end
# File lib/faraday/retry/middleware.rb, line 133 def calculate_sleep_amount(retries, env) retry_after = [calculate_retry_after(env), calculate_rate_limit_reset(env)].compact.max retry_interval = calculate_retry_interval(retries) return if retry_after && retry_after > @options.max_interval if retry_after && retry_after >= retry_interval retry_after else retry_interval end end
@param env [Faraday::Env]
# File lib/faraday/retry/middleware.rb, line 147 def call(env) retries = @options.max request_body = env[:body] begin # after failure env[:body] is set to the response body env[:body] = request_body @app.call(env).tap do |resp| raise Faraday::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status) end rescue @errmatch => e if retries.positive? && retry_request?(env, e) retries -= 1 rewind_files(request_body) if (sleep_amount = calculate_sleep_amount(retries + 1, env)) @options.retry_block.call( env: env, options: @options, retry_count: @options.max - (retries + 1), exception: e, will_retry_in: sleep_amount ) sleep sleep_amount retry end end raise unless e.is_a?(Faraday::RetriableResponse) e.response end end
Private Instance Methods
RFC for RateLimit Header Fields for HTTP: www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-05.html#name-fields-definition
# File lib/faraday/retry/middleware.rb, line 222 def calculate_rate_limit_reset(env) reset_header = @options.rate_limit_reset_header || 'RateLimit-Reset' parse_retry_header(env, reset_header) end
MDN spec for Retry-After header: developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
# File lib/faraday/retry/middleware.rb, line 229 def calculate_retry_after(env) retry_header = @options.rate_limit_retry_header || 'Retry-After' parse_retry_header(env, retry_header) end
# File lib/faraday/retry/middleware.rb, line 234 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/retry/middleware.rb, line 245 def parse_retry_header(env, header) response_headers = env[:response_headers] return unless response_headers retry_after_value = env[:response_headers][header] if @options.header_parser_block @options.header_parser_block.call(retry_after_value) else # 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 end
# File lib/faraday/retry/middleware.rb, line 206 def retry_request?(env, exception) @options.methods.include?(env[:method]) || @options.retry_if.call(env, exception) end
# File lib/faraday/retry/middleware.rb, line 211 def rewind_files(body) return unless defined?(UploadIO) return unless body.is_a?(Hash) body.each do |_, value| value.rewind if value.is_a?(UploadIO) end end