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