faraday 1.10.6 → 2.0.0.alpha.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -1
  3. data/README.md +16 -9
  4. data/examples/client_test.rb +1 -1
  5. data/lib/faraday/adapter/test.rb +2 -0
  6. data/lib/faraday/adapter.rb +0 -5
  7. data/lib/faraday/connection.rb +3 -86
  8. data/lib/faraday/encoders/nested_params_encoder.rb +3 -12
  9. data/lib/faraday/error.rb +7 -0
  10. data/lib/faraday/file_part.rb +122 -0
  11. data/lib/faraday/logging/formatter.rb +1 -0
  12. data/lib/faraday/middleware.rb +0 -1
  13. data/lib/faraday/middleware_registry.rb +15 -79
  14. data/lib/faraday/options.rb +3 -3
  15. data/lib/faraday/param_part.rb +53 -0
  16. data/lib/faraday/rack_builder.rb +1 -1
  17. data/lib/faraday/request/authorization.rb +26 -40
  18. data/lib/faraday/request/instrumentation.rb +2 -0
  19. data/lib/faraday/request/multipart.rb +108 -0
  20. data/lib/faraday/request/retry.rb +241 -0
  21. data/lib/faraday/request/url_encoded.rb +2 -0
  22. data/lib/faraday/request.rb +8 -24
  23. data/lib/faraday/response/json.rb +4 -4
  24. data/lib/faraday/response/logger.rb +2 -0
  25. data/lib/faraday/response/raise_error.rb +9 -1
  26. data/lib/faraday/response.rb +7 -20
  27. data/lib/faraday/utils/headers.rb +1 -1
  28. data/lib/faraday/utils.rb +9 -4
  29. data/lib/faraday/version.rb +1 -1
  30. data/lib/faraday.rb +6 -45
  31. data/spec/faraday/connection_spec.rb +78 -97
  32. data/spec/faraday/options/env_spec.rb +2 -2
  33. data/spec/faraday/params_encoders/nested_spec.rb +0 -30
  34. data/spec/faraday/rack_builder_spec.rb +5 -43
  35. data/spec/faraday/request/authorization_spec.rb +14 -36
  36. data/spec/faraday/request/instrumentation_spec.rb +5 -7
  37. data/spec/faraday/request/multipart_spec.rb +302 -0
  38. data/spec/faraday/request/retry_spec.rb +254 -0
  39. data/spec/faraday/request_spec.rb +0 -11
  40. data/spec/faraday/response/json_spec.rb +4 -6
  41. data/spec/faraday/response/raise_error_spec.rb +7 -4
  42. data/spec/faraday/utils_spec.rb +1 -1
  43. data/spec/faraday_spec.rb +4 -8
  44. data/spec/spec_helper.rb +0 -2
  45. data/spec/support/fake_safe_buffer.rb +1 -1
  46. data/spec/support/shared_examples/request_method.rb +5 -5
  47. metadata +25 -156
  48. data/lib/faraday/adapter/typhoeus.rb +0 -15
  49. data/lib/faraday/autoload.rb +0 -89
  50. data/lib/faraday/dependency_loader.rb +0 -39
  51. data/lib/faraday/deprecate.rb +0 -110
  52. data/lib/faraday/request/basic_authentication.rb +0 -20
  53. data/lib/faraday/request/token_authentication.rb +0 -20
  54. data/spec/faraday/adapter/em_http_spec.rb +0 -49
  55. data/spec/faraday/adapter/em_synchrony_spec.rb +0 -18
  56. data/spec/faraday/adapter/excon_spec.rb +0 -49
  57. data/spec/faraday/adapter/httpclient_spec.rb +0 -73
  58. data/spec/faraday/adapter/net_http_spec.rb +0 -64
  59. data/spec/faraday/adapter/patron_spec.rb +0 -18
  60. data/spec/faraday/adapter/rack_spec.rb +0 -8
  61. data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
  62. data/spec/faraday/deprecate_spec.rb +0 -147
  63. data/spec/faraday/response/middleware_spec.rb +0 -68
  64. data/spec/support/webmock_rack_app.rb +0 -68
@@ -6,59 +6,26 @@ module Faraday
6
6
  # Adds the ability for other modules to register and lookup
7
7
  # middleware classes.
8
8
  module MiddlewareRegistry
9
+ def registered_middleware
10
+ @registered_middleware ||= {}
11
+ end
12
+
9
13
  # Register middleware class(es) on the current module.
10
14
  #
11
- # @param autoload_path [String] Middleware autoload path
12
- # @param mapping [Hash{
13
- # Symbol => Module,
14
- # Symbol => Array<Module, Symbol, String>,
15
- # }] Middleware mapping from a lookup symbol to a reference to the
16
- # middleware.
17
- # Classes can be expressed as:
18
- # - a fully qualified constant
19
- # - a Symbol
20
- # - a Proc that will be lazily called to return the former
21
- # - an array is given, its first element is the constant or symbol,
22
- # and its second is a file to `require`.
15
+ # @param mappings [Hash] Middleware mappings from a lookup symbol to a middleware class.
23
16
  # @return [void]
24
17
  #
25
18
  # @example Lookup by a constant
26
19
  #
27
20
  # module Faraday
28
- # class Whatever
21
+ # class Whatever < Middleware
29
22
  # # Middleware looked up by :foo returns Faraday::Whatever::Foo.
30
- # register_middleware foo: Foo
31
- # end
32
- # end
33
- #
34
- # @example Lookup by a symbol
35
- #
36
- # module Faraday
37
- # class Whatever
38
- # # Middleware looked up by :bar returns
39
- # # Faraday::Whatever.const_get(:Bar)
40
- # register_middleware bar: :Bar
41
- # end
42
- # end
43
- #
44
- # @example Lookup by a symbol and string in an array
45
- #
46
- # module Faraday
47
- # class Whatever
48
- # # Middleware looked up by :baz requires 'baz' and returns
49
- # # Faraday::Whatever.const_get(:Baz)
50
- # register_middleware baz: [:Baz, 'baz']
23
+ # register_middleware(foo: Whatever)
51
24
  # end
52
25
  # end
53
- #
54
- def register_middleware(autoload_path = nil, mapping = nil)
55
- if mapping.nil?
56
- mapping = autoload_path
57
- autoload_path = nil
58
- end
26
+ def register_middleware(**mappings)
59
27
  middleware_mutex do
60
- @middleware_autoload_path = autoload_path if autoload_path
61
- (@registered_middleware ||= {}).update(mapping)
28
+ registered_middleware.update(mappings)
62
29
  end
63
30
  end
64
31
 
@@ -66,7 +33,7 @@ module Faraday
66
33
  #
67
34
  # @param key [Symbol] key for the registered middleware.
68
35
  def unregister_middleware(key)
69
- @registered_middleware.delete(key)
36
+ registered_middleware.delete(key)
70
37
  end
71
38
 
72
39
  # Lookup middleware class with a registered Symbol shortcut.
@@ -78,16 +45,15 @@ module Faraday
78
45
  # @example
79
46
  #
80
47
  # module Faraday
81
- # class Whatever
82
- # register_middleware foo: Foo
48
+ # class Whatever < Middleware
49
+ # register_middleware(foo: Whatever)
83
50
  # end
84
51
  # end
85
52
  #
86
- # Faraday::Whatever.lookup_middleware(:foo)
87
- # # => Faraday::Whatever::Foo
88
- #
53
+ # Faraday::Middleware.lookup_middleware(:foo)
54
+ # # => Faraday::Whatever
89
55
  def lookup_middleware(key)
90
- load_middleware(key) ||
56
+ registered_middleware[key] ||
91
57
  raise(Faraday::Error, "#{key.inspect} is not registered on #{self}")
92
58
  end
93
59
 
@@ -95,35 +61,5 @@ module Faraday
95
61
  @middleware_mutex ||= Monitor.new
96
62
  @middleware_mutex.synchronize(&block)
97
63
  end
98
-
99
- def fetch_middleware(key)
100
- defined?(@registered_middleware) && @registered_middleware[key]
101
- end
102
-
103
- def load_middleware(key)
104
- value = fetch_middleware(key)
105
- case value
106
- when Module
107
- value
108
- when Symbol, String
109
- middleware_mutex do
110
- @registered_middleware[key] = const_get(value)
111
- end
112
- when Proc
113
- middleware_mutex do
114
- @registered_middleware[key] = value.call
115
- end
116
- when Array
117
- middleware_mutex do
118
- const, path = value
119
- if (root = @middleware_autoload_path)
120
- path = "#{root}/#{path}"
121
- end
122
- require(path)
123
- @registered_middleware[key] = const
124
- end
125
- load_middleware(key)
126
- end
127
- end
128
64
  end
129
65
  end
@@ -104,7 +104,7 @@ module Faraday
104
104
 
105
105
  # Public
106
106
  def each_key(&block)
107
- return to_enum(:each_key) unless block_given?
107
+ return to_enum(:each_key) unless block
108
108
 
109
109
  keys.each(&block)
110
110
  end
@@ -118,7 +118,7 @@ module Faraday
118
118
 
119
119
  # Public
120
120
  def each_value(&block)
121
- return to_enum(:each_value) unless block_given?
121
+ return to_enum(:each_value) unless block
122
122
 
123
123
  values.each(&block)
124
124
  end
@@ -168,7 +168,7 @@ module Faraday
168
168
  end
169
169
 
170
170
  def self.memoized(key, &block)
171
- unless block_given?
171
+ unless block
172
172
  raise ArgumentError, '#memoized must be called with a block'
173
173
  end
174
174
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ # Multipart value used to POST data with a content type.
5
+ class ParamPart
6
+ # @param value [String] Uploaded content as a String.
7
+ # @param content_type [String] String content type of the value.
8
+ # @param content_id [String] Optional String of this value's Content-ID.
9
+ #
10
+ # @return [Faraday::ParamPart]
11
+ def initialize(value, content_type, content_id = nil)
12
+ @value = value
13
+ @content_type = content_type
14
+ @content_id = content_id
15
+ end
16
+
17
+ # Converts this value to a form part.
18
+ #
19
+ # @param boundary [String] String multipart boundary that must not exist in
20
+ # the content exactly.
21
+ # @param key [String] String key name for this value.
22
+ #
23
+ # @return [Faraday::Parts::Part]
24
+ def to_part(boundary, key)
25
+ Faraday::Parts::Part.new(boundary, key, value, headers)
26
+ end
27
+
28
+ # Returns a Hash of String key/value pairs.
29
+ #
30
+ # @return [Hash]
31
+ def headers
32
+ {
33
+ 'Content-Type' => content_type,
34
+ 'Content-ID' => content_id
35
+ }
36
+ end
37
+
38
+ # The content to upload.
39
+ #
40
+ # @return [String]
41
+ attr_reader :value
42
+
43
+ # The value's content type.
44
+ #
45
+ # @return [String]
46
+ attr_reader :content_type
47
+
48
+ # The value's content ID, if given.
49
+ #
50
+ # @return [String, nil]
51
+ attr_reader :content_id
52
+ end
53
+ end
@@ -61,7 +61,7 @@ module Faraday
61
61
  def initialize(handlers = [], adapter = nil, &block)
62
62
  @adapter = adapter
63
63
  @handlers = handlers
64
- if block_given?
64
+ if block
65
65
  build(&block)
66
66
  elsif @handlers.empty?
67
67
  # default stack, if nothing else is configured
@@ -1,53 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
-
5
3
  module Faraday
6
4
  class Request
7
5
  # Request middleware for the Authorization HTTP header
8
6
  class Authorization < Faraday::Middleware
9
- unless defined?(::Faraday::Request::Authorization::KEY)
10
- KEY = 'Authorization'
11
- end
12
-
13
- # @param type [String, Symbol]
14
- # @param token [String, Symbol, Hash]
15
- # @return [String] a header value
16
- def self.header(type, token)
17
- case token
18
- when String, Symbol, Proc
19
- token = token.call if token.is_a?(Proc)
20
- "#{type} #{token}"
21
- when Hash
22
- build_hash(type.to_s, token)
23
- else
24
- raise ArgumentError,
25
- "Can't build an Authorization #{type}" \
26
- "header from #{token.inspect}"
27
- end
28
- end
29
-
30
- # @param type [String]
31
- # @param hash [Hash]
32
- # @return [String] type followed by comma-separated key=value pairs
33
- # @api private
34
- def self.build_hash(type, hash)
35
- comma = ', '
36
- values = []
37
- hash.each do |key, value|
38
- value = value.call if value.is_a?(Proc)
39
- values << "#{key}=#{value.to_s.inspect}"
40
- end
41
- "#{type} #{values * comma}"
42
- end
7
+ KEY = 'Authorization'
43
8
 
44
9
  # @param app [#call]
45
10
  # @param type [String, Symbol] Type of Authorization
46
- # @param param [String, Symbol, Hash, Proc] parameter to build the Authorization header.
11
+ # @param params [Array<String, Proc>] parameters to build the Authorization header.
12
+ # If the type is `:basic`, then these can be a login and password pair.
13
+ # Otherwise, a single value is expected that will be appended after the type.
47
14
  # This value can be a proc, in which case it will be invoked on each request.
48
- def initialize(app, type, param)
15
+ def initialize(app, type, *params)
49
16
  @type = type
50
- @param = param
17
+ @params = params
51
18
  super(app)
52
19
  end
53
20
 
@@ -55,8 +22,27 @@ module Faraday
55
22
  def on_request(env)
56
23
  return if env.request_headers[KEY]
57
24
 
58
- env.request_headers[KEY] = self.class.header(@type, @param)
25
+ env.request_headers[KEY] = header_from(@type, *@params)
26
+ end
27
+
28
+ private
29
+
30
+ # @param type [String, Symbol]
31
+ # @param params [Array]
32
+ # @return [String] a header value
33
+ def header_from(type, *params)
34
+ if type.to_s.casecmp('basic').zero? && params.size == 2
35
+ Utils.basic_header_from(*params)
36
+ elsif params.size != 1
37
+ raise ArgumentError, "Unexpected params received (got #{params.size} instead of 1)"
38
+ else
39
+ value = params.first
40
+ value = value.call if value.is_a?(Proc)
41
+ "#{type} #{value}"
42
+ end
59
43
  end
60
44
  end
61
45
  end
62
46
  end
47
+
48
+ Faraday::Request.register_middleware(authorization: Faraday::Request::Authorization)
@@ -52,3 +52,5 @@ module Faraday
52
52
  end
53
53
  end
54
54
  end
55
+
56
+ Faraday::Request.register_middleware(instrumentation: Faraday::Request::Instrumentation)
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('url_encoded', __dir__)
4
+ require 'securerandom'
5
+
6
+ module Faraday
7
+ class Request
8
+ # Middleware for supporting multi-part requests.
9
+ class Multipart < UrlEncoded
10
+ self.mime_type = 'multipart/form-data'
11
+ unless defined?(::Faraday::Request::Multipart::DEFAULT_BOUNDARY_PREFIX)
12
+ DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
13
+ end
14
+
15
+ def initialize(app = nil, options = {})
16
+ super(app)
17
+ @options = options
18
+ end
19
+
20
+ # Checks for files in the payload, otherwise leaves everything untouched.
21
+ #
22
+ # @param env [Faraday::Env]
23
+ def call(env)
24
+ match_content_type(env) do |params|
25
+ env.request.boundary ||= unique_boundary
26
+ env.request_headers[CONTENT_TYPE] +=
27
+ "; boundary=#{env.request.boundary}"
28
+ env.body = create_multipart(env, params)
29
+ end
30
+ @app.call env
31
+ end
32
+
33
+ # @param env [Faraday::Env]
34
+ def process_request?(env)
35
+ type = request_type(env)
36
+ env.body.respond_to?(:each_key) && !env.body.empty? && (
37
+ (type.empty? && has_multipart?(env.body)) ||
38
+ (type == self.class.mime_type)
39
+ )
40
+ end
41
+
42
+ # Returns true if obj is an enumerable with values that are multipart.
43
+ #
44
+ # @param obj [Object]
45
+ # @return [Boolean]
46
+ def has_multipart?(obj) # rubocop:disable Naming/PredicateName
47
+ if obj.respond_to?(:each)
48
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
49
+ return true if val.respond_to?(:content_type) || has_multipart?(val)
50
+ end
51
+ end
52
+ false
53
+ end
54
+
55
+ # @param env [Faraday::Env]
56
+ # @param params [Hash]
57
+ def create_multipart(env, params)
58
+ boundary = env.request.boundary
59
+ parts = process_params(params) do |key, value|
60
+ part(boundary, key, value)
61
+ end
62
+ parts << Faraday::Parts::EpiloguePart.new(boundary)
63
+
64
+ body = Faraday::CompositeReadIO.new(parts)
65
+ env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
66
+ body
67
+ end
68
+
69
+ def part(boundary, key, value)
70
+ if value.respond_to?(:to_part)
71
+ value.to_part(boundary, key)
72
+ else
73
+ Faraday::Parts::Part.new(boundary, key, value)
74
+ end
75
+ end
76
+
77
+ # @return [String]
78
+ def unique_boundary
79
+ "#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
80
+ end
81
+
82
+ # @param params [Hash]
83
+ # @param prefix [String]
84
+ # @param pieces [Array]
85
+ def process_params(params, prefix = nil, pieces = nil, &block)
86
+ params.inject(pieces || []) do |all, (key, value)|
87
+ if prefix
88
+ key = @options[:flat_encode] ? prefix.to_s : "#{prefix}[#{key}]"
89
+ end
90
+
91
+ case value
92
+ when Array
93
+ values = value.inject([]) { |a, v| a << [nil, v] }
94
+ process_params(values, key, all, &block)
95
+ when Hash
96
+ process_params(value, key, all, &block)
97
+ else
98
+ # rubocop:disable Performance/RedundantBlockCall
99
+ all << block.call(key, value)
100
+ # rubocop:enable Performance/RedundantBlockCall
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ Faraday::Request.register_middleware(multipart: Faraday::Request::Multipart)
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ class Request
5
+ # Catches exceptions and retries each request a limited number of times.
6
+ #
7
+ # By default, it retries 2 times and handles only timeout exceptions. It can
8
+ # be configured with an arbitrary number of retries, a list of exceptions to
9
+ # handle, a retry interval, a percentage of randomness to add to the retry
10
+ # interval, and a backoff factor.
11
+ #
12
+ # @example Configure Retry middleware using intervals
13
+ # Faraday.new do |conn|
14
+ # conn.request(:retry, max: 2,
15
+ # interval: 0.05,
16
+ # interval_randomness: 0.5,
17
+ # backoff_factor: 2,
18
+ # exceptions: [CustomException, 'Timeout::Error'])
19
+ #
20
+ # conn.adapter(:net_http) # NB: Last middleware must be the adapter
21
+ # end
22
+ #
23
+ # This example will result in a first interval that is random between 0.05
24
+ # and 0.075 and a second interval that is random between 0.1 and 0.125.
25
+ class Retry < Faraday::Middleware
26
+ DEFAULT_EXCEPTIONS = [
27
+ Errno::ETIMEDOUT, 'Timeout::Error',
28
+ Faraday::TimeoutError, Faraday::RetriableResponse
29
+ ].freeze
30
+ IDEMPOTENT_METHODS = %i[delete get head options put].freeze
31
+
32
+ # Options contains the configurable parameters for the Retry middleware.
33
+ class Options < Faraday::Options.new(:max, :interval, :max_interval,
34
+ :interval_randomness,
35
+ :backoff_factor, :exceptions,
36
+ :methods, :retry_if, :retry_block,
37
+ :retry_statuses)
38
+
39
+ DEFAULT_CHECK = ->(_env, _exception) { false }
40
+
41
+ def self.from(value)
42
+ if value.is_a?(Integer)
43
+ new(value)
44
+ else
45
+ super(value)
46
+ end
47
+ end
48
+
49
+ def max
50
+ (self[:max] ||= 2).to_i
51
+ end
52
+
53
+ def interval
54
+ (self[:interval] ||= 0).to_f
55
+ end
56
+
57
+ def max_interval
58
+ (self[:max_interval] ||= Float::MAX).to_f
59
+ end
60
+
61
+ def interval_randomness
62
+ (self[:interval_randomness] ||= 0).to_f
63
+ end
64
+
65
+ def backoff_factor
66
+ (self[:backoff_factor] ||= 1).to_f
67
+ end
68
+
69
+ def exceptions
70
+ Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
71
+ end
72
+
73
+ def methods
74
+ Array(self[:methods] ||= IDEMPOTENT_METHODS)
75
+ end
76
+
77
+ def retry_if
78
+ self[:retry_if] ||= DEFAULT_CHECK
79
+ end
80
+
81
+ def retry_block
82
+ self[:retry_block] ||= proc {}
83
+ end
84
+
85
+ def retry_statuses
86
+ Array(self[:retry_statuses] ||= [])
87
+ end
88
+ end
89
+
90
+ # @param app [#call]
91
+ # @param options [Hash]
92
+ # @option options [Integer] :max (2) Maximum number of retries
93
+ # @option options [Integer] :interval (0) Pause in seconds between retries
94
+ # @option options [Integer] :interval_randomness (0) The maximum random
95
+ # interval amount expressed as a float between
96
+ # 0 and 1 to use in addition to the interval.
97
+ # @option options [Integer] :max_interval (Float::MAX) An upper limit
98
+ # for the interval
99
+ # @option options [Integer] :backoff_factor (1) The amount to multiply
100
+ # each successive retry's interval amount by in order to provide backoff
101
+ # @option options [Array] :exceptions ([ Errno::ETIMEDOUT,
102
+ # 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse])
103
+ # The list of exceptions to handle. Exceptions can be given as
104
+ # Class, Module, or String.
105
+ # @option options [Array] :methods (the idempotent HTTP methods
106
+ # in IDEMPOTENT_METHODS) A list of HTTP methods to retry without
107
+ # calling retry_if. Pass an empty Array to call retry_if
108
+ # for all exceptions.
109
+ # @option options [Block] :retry_if (false) block that will receive
110
+ # the env object and the exception raised
111
+ # and should decide if the code should retry still the action or
112
+ # not independent of the retry count. This would be useful
113
+ # if the exception produced is non-recoverable or if the
114
+ # the HTTP method called is not idempotent.
115
+ # @option options [Block] :retry_block block that is executed before
116
+ # every retry. Request environment, middleware options, current number
117
+ # of retries and the exception is passed to the block as parameters.
118
+ # @option options [Array] :retry_statuses Array of Integer HTTP status
119
+ # codes or a single Integer value that determines whether to raise
120
+ # a Faraday::RetriableResponse exception based on the HTTP status code
121
+ # of an HTTP response.
122
+ def initialize(app, options = nil)
123
+ super(app)
124
+ @options = Options.from(options)
125
+ @errmatch = build_exception_matcher(@options.exceptions)
126
+ end
127
+
128
+ def calculate_sleep_amount(retries, env)
129
+ retry_after = calculate_retry_after(env)
130
+ retry_interval = calculate_retry_interval(retries)
131
+
132
+ return if retry_after && retry_after > @options.max_interval
133
+
134
+ if retry_after && retry_after >= retry_interval
135
+ retry_after
136
+ else
137
+ retry_interval
138
+ end
139
+ end
140
+
141
+ # @param env [Faraday::Env]
142
+ def call(env)
143
+ retries = @options.max
144
+ request_body = env[:body]
145
+ begin
146
+ # after failure env[:body] is set to the response body
147
+ env[:body] = request_body
148
+ @app.call(env).tap do |resp|
149
+ if @options.retry_statuses.include?(resp.status)
150
+ raise Faraday::RetriableResponse.new(nil, resp)
151
+ end
152
+ end
153
+ rescue @errmatch => e
154
+ if retries.positive? && retry_request?(env, e)
155
+ retries -= 1
156
+ rewind_files(request_body)
157
+ @options.retry_block.call(env, @options, retries, e)
158
+ if (sleep_amount = calculate_sleep_amount(retries + 1, env))
159
+ sleep sleep_amount
160
+ retry
161
+ end
162
+ end
163
+
164
+ raise unless e.is_a?(Faraday::RetriableResponse)
165
+
166
+ e.response
167
+ end
168
+ end
169
+
170
+ # An exception matcher for the rescue clause can usually be any object
171
+ # that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
172
+ #
173
+ # @param exceptions [Array]
174
+ # @api private
175
+ # @return [Module] an exception matcher
176
+ def build_exception_matcher(exceptions)
177
+ matcher = Module.new
178
+ (
179
+ class << matcher
180
+ self
181
+ end).class_eval do
182
+ define_method(:===) do |error|
183
+ exceptions.any? do |ex|
184
+ if ex.is_a? Module
185
+ error.is_a? ex
186
+ else
187
+ Object.const_defined?(ex.to_s) && error.is_a?(Object.const_get(ex.to_s))
188
+ end
189
+ end
190
+ end
191
+ end
192
+ matcher
193
+ end
194
+
195
+ private
196
+
197
+ def retry_request?(env, exception)
198
+ @options.methods.include?(env[:method]) ||
199
+ @options.retry_if.call(env, exception)
200
+ end
201
+
202
+ def rewind_files(body)
203
+ return unless body.is_a?(Hash)
204
+
205
+ body.each do |_, value|
206
+ value.rewind if value.is_a?(UploadIO)
207
+ end
208
+ end
209
+
210
+ # MDN spec for Retry-After header:
211
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
212
+ def calculate_retry_after(env)
213
+ response_headers = env[:response_headers]
214
+ return unless response_headers
215
+
216
+ retry_after_value = env[:response_headers]['Retry-After']
217
+
218
+ # Try to parse date from the header value
219
+ begin
220
+ datetime = DateTime.rfc2822(retry_after_value)
221
+ datetime.to_time - Time.now.utc
222
+ rescue ArgumentError
223
+ retry_after_value.to_f
224
+ end
225
+ end
226
+
227
+ def calculate_retry_interval(retries)
228
+ retry_index = @options.max - retries
229
+ current_interval = @options.interval *
230
+ (@options.backoff_factor**retry_index)
231
+ current_interval = [current_interval, @options.max_interval].min
232
+ random_interval = rand * @options.interval_randomness.to_f *
233
+ @options.interval
234
+
235
+ current_interval + random_interval
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ Faraday::Request.register_middleware(retry: Faraday::Request::Retry)
@@ -54,3 +54,5 @@ module Faraday
54
54
  end
55
55
  end
56
56
  end
57
+
58
+ Faraday::Request.register_middleware(url_encoded: Faraday::Request::UrlEncoded)