faraday 0.16.2 → 0.17.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/README.md +347 -18
  4. data/lib/faraday.rb +175 -93
  5. data/lib/faraday/adapter.rb +22 -36
  6. data/lib/faraday/adapter/em_http.rb +99 -142
  7. data/lib/faraday/adapter/em_http_ssl_patch.rb +17 -23
  8. data/lib/faraday/adapter/em_synchrony.rb +60 -104
  9. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +15 -18
  10. data/lib/faraday/adapter/excon.rb +55 -100
  11. data/lib/faraday/adapter/httpclient.rb +39 -61
  12. data/lib/faraday/adapter/net_http.rb +51 -104
  13. data/lib/faraday/adapter/net_http_persistent.rb +27 -48
  14. data/lib/faraday/adapter/patron.rb +35 -54
  15. data/lib/faraday/adapter/rack.rb +12 -28
  16. data/lib/faraday/adapter/test.rb +53 -86
  17. data/lib/faraday/adapter/typhoeus.rb +1 -4
  18. data/lib/faraday/autoload.rb +36 -47
  19. data/lib/faraday/connection.rb +179 -321
  20. data/lib/faraday/error.rb +32 -80
  21. data/lib/faraday/middleware.rb +28 -4
  22. data/lib/faraday/options.rb +186 -35
  23. data/lib/faraday/parameters.rb +197 -4
  24. data/lib/faraday/rack_builder.rb +56 -67
  25. data/lib/faraday/request.rb +36 -68
  26. data/lib/faraday/request/authorization.rb +30 -42
  27. data/lib/faraday/request/basic_authentication.rb +7 -14
  28. data/lib/faraday/request/instrumentation.rb +27 -45
  29. data/lib/faraday/request/multipart.rb +48 -79
  30. data/lib/faraday/request/retry.rb +170 -197
  31. data/lib/faraday/request/token_authentication.rb +10 -15
  32. data/lib/faraday/request/url_encoded.rb +23 -41
  33. data/lib/faraday/response.rb +16 -23
  34. data/lib/faraday/response/logger.rb +69 -22
  35. data/lib/faraday/response/raise_error.rb +14 -36
  36. data/lib/faraday/upload_io.rb +67 -0
  37. data/lib/faraday/utils.rb +245 -28
  38. metadata +5 -22
  39. data/lib/faraday/adapter_registry.rb +0 -28
  40. data/lib/faraday/dependency_loader.rb +0 -37
  41. data/lib/faraday/deprecated_class.rb +0 -28
  42. data/lib/faraday/encoders/flat_params_encoder.rb +0 -94
  43. data/lib/faraday/encoders/nested_params_encoder.rb +0 -171
  44. data/lib/faraday/file_part.rb +0 -128
  45. data/lib/faraday/logging/formatter.rb +0 -92
  46. data/lib/faraday/middleware_registry.rb +0 -129
  47. data/lib/faraday/options/connection_options.rb +0 -22
  48. data/lib/faraday/options/env.rb +0 -181
  49. data/lib/faraday/options/proxy_options.rb +0 -28
  50. data/lib/faraday/options/request_options.rb +0 -21
  51. data/lib/faraday/options/ssl_options.rb +0 -59
  52. data/lib/faraday/param_part.rb +0 -53
  53. data/lib/faraday/utils/headers.rb +0 -139
  54. data/lib/faraday/utils/params_hash.rb +0 -61
  55. data/spec/external_adapters/faraday_specs_setup.rb +0 -14
@@ -1,239 +1,212 @@
1
- # frozen_string_literal: true
1
+ require 'date'
2
2
 
3
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.15.
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
4
+ # Catches exceptions and retries each request a limited number of times.
5
+ #
6
+ # By default, it retries 2 times and handles only timeout exceptions. It can
7
+ # be configured with an arbitrary number of retries, a list of exceptions to
8
+ # handle, a retry interval, a percentage of randomness to add to the retry
9
+ # interval, and a backoff factor.
10
+ #
11
+ # Examples
12
+ #
13
+ # Faraday.new do |conn|
14
+ # conn.request :retry, max: 2, interval: 0.05,
15
+ # interval_randomness: 0.5, backoff_factor: 2,
16
+ # exceptions: [CustomException, 'Timeout::Error']
17
+ # conn.adapter ...
18
+ # end
19
+ #
20
+ # This example will result in a first interval that is random between 0.05 and 0.075 and a second
21
+ # interval that is random between 0.1 and 0.15
22
+ #
23
+ class Request::Retry < Faraday::Middleware
24
+
25
+ DEFAULT_EXCEPTIONS = [Errno::ETIMEDOUT, 'Timeout::Error', Error::TimeoutError, Faraday::Error::RetriableResponse].freeze
26
+ IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
27
+
28
+ class Options < Faraday::Options.new(:max, :interval, :max_interval, :interval_randomness,
29
+ :backoff_factor, :exceptions, :methods, :retry_if, :retry_block,
30
+ :retry_statuses)
31
+
32
+ DEFAULT_CHECK = lambda { |env,exception| false }
33
+
34
+ def self.from(value)
35
+ if Integer === value
36
+ new(value)
37
+ else
38
+ super(value)
51
39
  end
40
+ end
52
41
 
53
- def interval
54
- (self[:interval] ||= 0).to_f
55
- end
42
+ def max
43
+ (self[:max] ||= 2).to_i
44
+ end
56
45
 
57
- def max_interval
58
- (self[:max_interval] ||= Float::MAX).to_f
59
- end
46
+ def interval
47
+ (self[:interval] ||= 0).to_f
48
+ end
60
49
 
61
- def interval_randomness
62
- (self[:interval_randomness] ||= 0).to_f
63
- end
50
+ def max_interval
51
+ (self[:max_interval] ||= Float::MAX).to_f
52
+ end
64
53
 
65
- def backoff_factor
66
- (self[:backoff_factor] ||= 1).to_f
67
- end
54
+ def interval_randomness
55
+ (self[:interval_randomness] ||= 0).to_f
56
+ end
68
57
 
69
- def exceptions
70
- Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
71
- end
58
+ def backoff_factor
59
+ (self[:backoff_factor] ||= 1).to_f
60
+ end
72
61
 
73
- def methods
74
- Array(self[:methods] ||= IDEMPOTENT_METHODS)
75
- end
62
+ def exceptions
63
+ Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
64
+ end
76
65
 
77
- def retry_if
78
- self[:retry_if] ||= DEFAULT_CHECK
79
- end
66
+ def methods
67
+ Array(self[:methods] ||= IDEMPOTENT_METHODS)
68
+ end
80
69
 
81
- def retry_block
82
- self[:retry_block] ||= proc {}
83
- end
70
+ def retry_if
71
+ self[:retry_if] ||= DEFAULT_CHECK
72
+ end
84
73
 
85
- def retry_statuses
86
- Array(self[:retry_statuses] ||= [])
87
- end
74
+ def retry_block
75
+ self[:retry_block] ||= Proc.new {}
88
76
  end
89
77
 
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 after
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)
78
+ def retry_statuses
79
+ Array(self[:retry_statuses] ||= [])
126
80
  end
81
+ end
127
82
 
128
- def calculate_sleep_amount(retries, env)
129
- retry_after = calculate_retry_after(env)
130
- retry_interval = calculate_retry_interval(retries)
83
+ # Public: Initialize middleware
84
+ #
85
+ # Options:
86
+ # max - Maximum number of retries (default: 2)
87
+ # interval - Pause in seconds between retries (default: 0)
88
+ # interval_randomness - The maximum random interval amount expressed
89
+ # as a float between 0 and 1 to use in addition to the
90
+ # interval. (default: 0)
91
+ # max_interval - An upper limit for the interval (default: Float::MAX)
92
+ # backoff_factor - The amount to multiple each successive retry's
93
+ # interval amount by in order to provide backoff
94
+ # (default: 1)
95
+ # exceptions - The list of exceptions to handle. Exceptions can be
96
+ # given as Class, Module, or String. (default:
97
+ # [Errno::ETIMEDOUT, 'Timeout::Error',
98
+ # Error::TimeoutError, Faraday::Error::RetriableResponse])
99
+ # methods - A list of HTTP methods to retry without calling retry_if. Pass
100
+ # an empty Array to call retry_if for all exceptions.
101
+ # (defaults to the idempotent HTTP methods in IDEMPOTENT_METHODS)
102
+ # retry_if - block that will receive the env object and the exception raised
103
+ # and should decide if the code should retry still the action or
104
+ # not independent of the retry count. This would be useful
105
+ # if the exception produced is non-recoverable or if the
106
+ # the HTTP method called is not idempotent.
107
+ # (defaults to return false)
108
+ # retry_block - block that is executed after every retry. Request environment, middleware options,
109
+ # current number of retries and the exception is passed to the block as parameters.
110
+ def initialize(app, options = nil)
111
+ super(app)
112
+ @options = Options.from(options)
113
+ @errmatch = build_exception_matcher(@options.exceptions)
114
+ end
131
115
 
132
- return if retry_after && retry_after > @options.max_interval
116
+ def calculate_sleep_amount(retries, env)
117
+ retry_after = calculate_retry_after(env)
118
+ retry_interval = calculate_retry_interval(retries)
133
119
 
134
- if retry_after && retry_after >= retry_interval
135
- retry_after
136
- else
137
- retry_interval
138
- end
139
- end
120
+ return if retry_after && retry_after > @options.max_interval
140
121
 
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
122
+ retry_after && retry_after >= retry_interval ? retry_after : retry_interval
123
+ end
163
124
 
164
- raise unless e.is_a?(Faraday::RetriableResponse)
125
+ def call(env)
126
+ retries = @options.max
127
+ request_body = env[:body]
128
+ begin
129
+ env[:body] = request_body # after failure env[:body] is set to the response body
130
+ @app.call(env).tap do |resp|
131
+ raise Faraday::Error::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status)
132
+ end
133
+ rescue @errmatch => exception
134
+ if retries > 0 && retry_request?(env, exception)
135
+ retries -= 1
136
+ rewind_files(request_body)
137
+ @options.retry_block.call(env, @options, retries, exception)
138
+ if (sleep_amount = calculate_sleep_amount(retries + 1, env))
139
+ sleep sleep_amount
140
+ retry
141
+ end
142
+ end
165
143
 
166
- e.response
144
+ if exception.is_a?(Faraday::Error::RetriableResponse)
145
+ exception.response
146
+ else
147
+ raise
167
148
  end
168
149
  end
150
+ end
169
151
 
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
- error.class.to_s == ex.to_s
188
- end
152
+ # Private: construct an exception matcher object.
153
+ #
154
+ # An exception matcher for the rescue clause can usually be any object that
155
+ # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
156
+ def build_exception_matcher(exceptions)
157
+ matcher = Module.new
158
+ (class << matcher; self; end).class_eval do
159
+ define_method(:===) do |error|
160
+ exceptions.any? do |ex|
161
+ if ex.is_a? Module
162
+ error.is_a? ex
163
+ else
164
+ error.class.to_s == ex.to_s
189
165
  end
190
166
  end
191
167
  end
192
- matcher
193
168
  end
169
+ matcher
170
+ end
194
171
 
195
- private
196
-
197
- def retry_request?(env, exception)
198
- @options.methods.include?(env[:method]) ||
199
- @options.retry_if.call(env, exception)
200
- end
172
+ private
201
173
 
202
- def rewind_files(body)
203
- return unless body.is_a?(Hash)
174
+ def retry_request?(env, exception)
175
+ @options.methods.include?(env[:method]) || @options.retry_if.call(env, exception)
176
+ end
204
177
 
205
- body.each do |_, value|
206
- value.rewind if value.is_a?(UploadIO)
178
+ def rewind_files(body)
179
+ return unless body.is_a?(Hash)
180
+ body.each do |_, value|
181
+ if value.is_a? UploadIO
182
+ value.rewind
207
183
  end
208
184
  end
185
+ end
209
186
 
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
187
+ # MDN spec for Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
188
+ def calculate_retry_after(env)
189
+ response_headers = env[:response_headers]
190
+ return unless response_headers
215
191
 
216
- retry_after_value = env[:response_headers]['Retry-After']
192
+ retry_after_value = env[:response_headers]["Retry-After"]
217
193
 
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
194
+ # Try to parse date from the header value
195
+ begin
196
+ datetime = DateTime.rfc2822(retry_after_value)
197
+ datetime.to_time - Time.now.utc
198
+ rescue ArgumentError
199
+ retry_after_value.to_f
225
200
  end
201
+ end
226
202
 
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
203
+ def calculate_retry_interval(retries)
204
+ retry_index = @options.max - retries
205
+ current_interval = @options.interval * (@options.backoff_factor ** retry_index)
206
+ current_interval = [current_interval, @options.max_interval].min
207
+ random_interval = rand * @options.interval_randomness.to_f * @options.interval
234
208
 
235
- current_interval + random_interval
236
- end
209
+ current_interval + random_interval
237
210
  end
238
211
  end
239
212
  end
@@ -1,20 +1,15 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Faraday
4
- class Request
5
- # TokenAuthentication is a middleware that adds a 'Token' header to a
6
- # Faraday request.
7
- class TokenAuthentication < load_middleware(:authorization)
8
- # Public
9
- def self.header(token, options = nil)
10
- options ||= {}
11
- options[:token] = token
12
- super(:Token, options)
13
- end
2
+ class Request::TokenAuthentication < Request.load_middleware(:authorization)
3
+ # Public
4
+ def self.header(token, options = nil)
5
+ options ||= {}
6
+ options[:token] = token
7
+ super(:Token, options)
8
+ end
14
9
 
15
- def initialize(app, token, options = nil)
16
- super(app, token, options)
17
- end
10
+ def initialize(app, token, options = nil)
11
+ super(app, token, options)
18
12
  end
19
13
  end
20
14
  end
15
+
@@ -1,54 +1,36 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Faraday
4
- class Request
5
- # Middleware for supporting urlencoded requests.
6
- class UrlEncoded < Faraday::Middleware
7
- CONTENT_TYPE = 'Content-Type' unless defined? CONTENT_TYPE
2
+ class Request::UrlEncoded < Faraday::Middleware
3
+ CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
8
4
 
9
- class << self
10
- attr_accessor :mime_type
11
- end
12
- self.mime_type = 'application/x-www-form-urlencoded'
5
+ class << self
6
+ attr_accessor :mime_type
7
+ end
8
+ self.mime_type = 'application/x-www-form-urlencoded'.freeze
13
9
 
14
- # Encodes as "application/x-www-form-urlencoded" if not already encoded or
15
- # of another type.
16
- #
17
- # @param env [Faraday::Env]
18
- def call(env)
19
- match_content_type(env) do |data|
20
- params = Faraday::Utils::ParamsHash[data]
21
- env.body = params.to_query(env.params_encoder)
22
- end
23
- @app.call env
10
+ def call(env)
11
+ match_content_type(env) do |data|
12
+ params = Faraday::Utils::ParamsHash[data]
13
+ env.body = params.to_query(env.params_encoder)
24
14
  end
15
+ @app.call env
16
+ end
25
17
 
26
- # @param env [Faraday::Env]
27
- # @yield [request_body] Body of the request
28
- def match_content_type(env)
29
- return unless process_request?(env)
30
-
18
+ def match_content_type(env)
19
+ if process_request?(env)
31
20
  env.request_headers[CONTENT_TYPE] ||= self.class.mime_type
32
21
  yield(env.body) unless env.body.respond_to?(:to_str)
33
22
  end
23
+ end
34
24
 
35
- # @param env [Faraday::Env]
36
- #
37
- # @return [Boolean] True if the request has a body and its Content-Type is
38
- # urlencoded.
39
- def process_request?(env)
40
- type = request_type(env)
41
- env.body && (type.empty? || (type == self.class.mime_type))
42
- end
25
+ def process_request?(env)
26
+ type = request_type(env)
27
+ env.body and (type.empty? or type == self.class.mime_type)
28
+ end
43
29
 
44
- # @param env [Faraday::Env]
45
- #
46
- # @return [String]
47
- def request_type(env)
48
- type = env.request_headers[CONTENT_TYPE].to_s
49
- type = type.split(';', 2).first if type.index(';')
50
- type
51
- end
30
+ def request_type(env)
31
+ type = env.request_headers[CONTENT_TYPE].to_s
32
+ type = type.split(';', 2).first if type.index(';')
33
+ type
52
34
  end
53
35
  end
54
36
  end