faraday 0.11.0 → 0.17.4
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +232 -0
- data/LICENSE.md +1 -1
- data/README.md +151 -13
- data/Rakefile +13 -0
- data/lib/faraday/adapter/em_http.rb +9 -9
- data/lib/faraday/adapter/em_synchrony.rb +5 -5
- data/lib/faraday/adapter/excon.rb +6 -4
- data/lib/faraday/adapter/httpclient.rb +5 -5
- data/lib/faraday/adapter/net_http.rb +27 -9
- data/lib/faraday/adapter/net_http_persistent.rb +34 -16
- data/lib/faraday/adapter/patron.rb +32 -13
- data/lib/faraday/adapter/rack.rb +1 -1
- data/lib/faraday/adapter/test.rb +21 -13
- data/lib/faraday/adapter/typhoeus.rb +4 -115
- data/lib/faraday/adapter.rb +2 -0
- data/lib/faraday/autoload.rb +1 -1
- data/lib/faraday/connection.rb +59 -12
- data/lib/faraday/deprecate.rb +109 -0
- data/lib/faraday/error.rb +130 -35
- data/lib/faraday/options.rb +31 -25
- data/lib/faraday/parameters.rb +2 -1
- data/lib/faraday/rack_builder.rb +26 -2
- data/lib/faraday/request/multipart.rb +7 -2
- data/lib/faraday/request/retry.rb +76 -17
- data/lib/faraday/request.rb +20 -0
- data/lib/faraday/response/logger.rb +3 -3
- data/lib/faraday/response/raise_error.rb +7 -3
- data/lib/faraday/response.rb +3 -3
- data/lib/faraday/utils.rb +18 -9
- data/lib/faraday.rb +9 -5
- data/spec/faraday/deprecate_spec.rb +147 -0
- data/spec/faraday/error_spec.rb +102 -0
- data/spec/faraday/response/raise_error_spec.rb +106 -0
- data/spec/spec_helper.rb +105 -0
- data/test/adapters/default_test.rb +14 -0
- data/test/adapters/em_http_test.rb +30 -0
- data/test/adapters/em_synchrony_test.rb +32 -0
- data/test/adapters/excon_test.rb +30 -0
- data/test/adapters/httpclient_test.rb +34 -0
- data/test/adapters/integration.rb +263 -0
- data/test/adapters/logger_test.rb +136 -0
- data/test/adapters/net_http_persistent_test.rb +114 -0
- data/test/adapters/net_http_test.rb +79 -0
- data/test/adapters/patron_test.rb +40 -0
- data/test/adapters/rack_test.rb +38 -0
- data/test/adapters/test_middleware_test.rb +157 -0
- data/test/adapters/typhoeus_test.rb +38 -0
- data/test/authentication_middleware_test.rb +65 -0
- data/test/composite_read_io_test.rb +109 -0
- data/test/connection_test.rb +738 -0
- data/test/env_test.rb +268 -0
- data/test/helper.rb +75 -0
- data/test/live_server.rb +67 -0
- data/test/middleware/instrumentation_test.rb +88 -0
- data/test/middleware/retry_test.rb +282 -0
- data/test/middleware_stack_test.rb +260 -0
- data/test/multibyte.txt +1 -0
- data/test/options_test.rb +333 -0
- data/test/parameters_test.rb +157 -0
- data/test/request_middleware_test.rb +126 -0
- data/test/response_middleware_test.rb +72 -0
- data/test/strawberry.rb +2 -0
- data/test/utils_test.rb +98 -0
- metadata +48 -7
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Faraday
|
4
|
+
# @param new_klass [Class] new Klass to use
|
5
|
+
#
|
6
|
+
# @return [Class] A modified version of new_klass that warns on
|
7
|
+
# usage about deprecation.
|
8
|
+
# @see Faraday::Deprecate
|
9
|
+
module DeprecatedClass
|
10
|
+
def self.proxy_class(origclass, ver = '1.0')
|
11
|
+
proxy = Class.new(origclass) do
|
12
|
+
const_set("ORIG_CLASS", origclass)
|
13
|
+
|
14
|
+
class << self
|
15
|
+
extend Faraday::Deprecate
|
16
|
+
|
17
|
+
def ===(other)
|
18
|
+
(superclass == const_get("ORIG_CLASS") && other.is_a?(superclass)) || super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
proxy.singleton_class.send(:deprecate, :new, "#{origclass}.new", ver)
|
23
|
+
proxy.singleton_class.send(:deprecate, :inherited, origclass.name, ver)
|
24
|
+
proxy
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Deprecation using semver instead of date, based on Gem::Deprecate
|
29
|
+
# Provides a single method +deprecate+ to be used to declare when
|
30
|
+
# something is going away.
|
31
|
+
#
|
32
|
+
# class Legacy
|
33
|
+
# def self.klass_method
|
34
|
+
# # ...
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def instance_method
|
38
|
+
# # ...
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# extend Faraday::Deprecate
|
42
|
+
# deprecate :instance_method, "X.z", '1.0'
|
43
|
+
#
|
44
|
+
# class << self
|
45
|
+
# extend Faraday::Deprecate
|
46
|
+
# deprecate :klass_method, :none, '1.0'
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
module Deprecate
|
50
|
+
def self.skip # :nodoc:
|
51
|
+
@skip ||= begin
|
52
|
+
case ENV['FARADAY_DEPRECATE'].to_s.downcase
|
53
|
+
when '1', 'warn' then :warn
|
54
|
+
else :skip
|
55
|
+
end
|
56
|
+
end
|
57
|
+
@skip == :skip
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.skip=(value) # :nodoc:
|
61
|
+
@skip = value ? :skip : :warn
|
62
|
+
end
|
63
|
+
|
64
|
+
# Temporarily turn off warnings. Intended for tests only.
|
65
|
+
def skip_during
|
66
|
+
original = Faraday::Deprecate.skip
|
67
|
+
Faraday::Deprecate.skip, = true
|
68
|
+
yield
|
69
|
+
ensure
|
70
|
+
Faraday::Deprecate.skip = original
|
71
|
+
end
|
72
|
+
|
73
|
+
# Simple deprecation method that deprecates +name+ by wrapping it up
|
74
|
+
# in a dummy method. It warns on each call to the dummy method
|
75
|
+
# telling the user of +repl+ (unless +repl+ is :none) and the
|
76
|
+
# semver that it is planned to go away.
|
77
|
+
# @param name [Symbol] the method symbol to deprecate
|
78
|
+
# @param repl [#to_s, :none] the replacement to use, when `:none` it will
|
79
|
+
# alert the user that no replacemtent is present.
|
80
|
+
# @param ver [String] the semver the method will be removed.
|
81
|
+
def deprecate(name, repl, ver)
|
82
|
+
class_eval do
|
83
|
+
gem_ver = Gem::Version.new(ver)
|
84
|
+
old = "_deprecated_#{name}"
|
85
|
+
alias_method old, name
|
86
|
+
define_method name do |*args, &block|
|
87
|
+
mod = is_a? Module
|
88
|
+
target = mod ? "#{self}." : "#{self.class}#"
|
89
|
+
target_message = if name == :inherited
|
90
|
+
"Inheriting #{self}"
|
91
|
+
else
|
92
|
+
"#{target}#{name}"
|
93
|
+
end
|
94
|
+
|
95
|
+
msg = [
|
96
|
+
"NOTE: #{target_message} is deprecated",
|
97
|
+
repl == :none ? ' with no replacement' : "; use #{repl} instead. ",
|
98
|
+
"It will be removed in or after version #{gem_ver}",
|
99
|
+
"\n#{target}#{name} called from #{Gem.location_of_caller.join(':')}"
|
100
|
+
]
|
101
|
+
warn "#{msg.join}." unless Faraday::Deprecate.skip
|
102
|
+
send old, *args, &block
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
module_function :deprecate, :skip_during
|
108
|
+
end
|
109
|
+
end
|
data/lib/faraday/error.rb
CHANGED
@@ -1,23 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday/deprecate'
|
4
|
+
|
5
|
+
# Faraday namespace.
|
1
6
|
module Faraday
|
2
|
-
|
3
|
-
class
|
7
|
+
# Faraday error base class.
|
8
|
+
class Error < StandardError
|
9
|
+
attr_reader :response, :wrapped_exception
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@wrapped_exception = nil
|
10
|
-
@response = response
|
11
|
-
|
12
|
-
if ex.respond_to?(:backtrace)
|
13
|
-
super(ex.message)
|
14
|
-
@wrapped_exception = ex
|
15
|
-
elsif ex.respond_to?(:each_key)
|
16
|
-
super("the server responded with status #{ex[:status]}")
|
17
|
-
@response = ex
|
18
|
-
else
|
19
|
-
super(ex.to_s)
|
20
|
-
end
|
11
|
+
def initialize(exc, response = nil)
|
12
|
+
@wrapped_exception = nil unless defined?(@wrapped_exception)
|
13
|
+
@response = nil unless defined?(@response)
|
14
|
+
super(exc_msg_and_response!(exc, response))
|
21
15
|
end
|
22
16
|
|
23
17
|
def backtrace
|
@@ -30,34 +24,135 @@ module Faraday
|
|
30
24
|
|
31
25
|
def inspect
|
32
26
|
inner = ''
|
33
|
-
if @wrapped_exception
|
34
|
-
|
35
|
-
|
36
|
-
if @response
|
37
|
-
inner << " response=#{@response.inspect}"
|
38
|
-
end
|
39
|
-
if inner.empty?
|
40
|
-
inner << " #{super}"
|
41
|
-
end
|
27
|
+
inner += " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
|
28
|
+
inner += " response=#{@response.inspect}" if @response
|
29
|
+
inner += " #{super}" if inner.empty?
|
42
30
|
%(#<#{self.class}#{inner}>)
|
43
31
|
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Pulls out potential parent exception and response hash, storing them in
|
36
|
+
# instance variables.
|
37
|
+
# exc - Either an Exception, a string message, or a response hash.
|
38
|
+
# response - Hash
|
39
|
+
# :status - Optional integer HTTP response status
|
40
|
+
# :headers - String key/value hash of HTTP response header
|
41
|
+
# values.
|
42
|
+
# :body - Optional string HTTP response body.
|
43
|
+
#
|
44
|
+
# If a subclass has to call this, then it should pass a string message
|
45
|
+
# to `super`. See NilStatusError.
|
46
|
+
def exc_msg_and_response!(exc, response = nil)
|
47
|
+
if @response.nil? && @wrapped_exception.nil?
|
48
|
+
@wrapped_exception, msg, @response = exc_msg_and_response(exc, response)
|
49
|
+
return msg
|
50
|
+
end
|
51
|
+
|
52
|
+
exc.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
# Pulls out potential parent exception and response hash.
|
56
|
+
def exc_msg_and_response(exc, response = nil)
|
57
|
+
return [exc, exc.message, response] if exc.respond_to?(:backtrace)
|
58
|
+
|
59
|
+
return [nil, "the server responded with status #{exc[:status]}", exc] \
|
60
|
+
if exc.respond_to?(:each_key)
|
61
|
+
|
62
|
+
[nil, exc.to_s, response]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Faraday client error class. Represents 4xx status responses.
|
67
|
+
class ClientError < Error
|
68
|
+
end
|
69
|
+
|
70
|
+
# Raised by Faraday::Response::RaiseError in case of a 400 response.
|
71
|
+
class BadRequestError < ClientError
|
72
|
+
end
|
73
|
+
|
74
|
+
# Raised by Faraday::Response::RaiseError in case of a 401 response.
|
75
|
+
class UnauthorizedError < ClientError
|
76
|
+
end
|
77
|
+
|
78
|
+
# Raised by Faraday::Response::RaiseError in case of a 403 response.
|
79
|
+
class ForbiddenError < ClientError
|
80
|
+
end
|
81
|
+
|
82
|
+
# Raised by Faraday::Response::RaiseError in case of a 404 response.
|
83
|
+
class ResourceNotFound < ClientError
|
44
84
|
end
|
45
85
|
|
46
|
-
|
47
|
-
class
|
48
|
-
|
86
|
+
# Raised by Faraday::Response::RaiseError in case of a 407 response.
|
87
|
+
class ProxyAuthError < ClientError
|
88
|
+
end
|
49
89
|
|
90
|
+
# Raised by Faraday::Response::RaiseError in case of a 409 response.
|
91
|
+
class ConflictError < ClientError
|
92
|
+
end
|
93
|
+
|
94
|
+
# Raised by Faraday::Response::RaiseError in case of a 422 response.
|
95
|
+
class UnprocessableEntityError < ClientError
|
96
|
+
end
|
97
|
+
|
98
|
+
# Faraday server error class. Represents 5xx status responses.
|
99
|
+
class ServerError < Error
|
100
|
+
end
|
101
|
+
|
102
|
+
# A unified client error for timeouts.
|
50
103
|
class TimeoutError < ClientError
|
51
|
-
def initialize(
|
52
|
-
super(
|
104
|
+
def initialize(exc = 'timeout', response = nil)
|
105
|
+
super(exc, response)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Raised by Faraday::Response::RaiseError in case of a nil status in response.
|
110
|
+
class NilStatusError < ServerError
|
111
|
+
def initialize(exc, response = nil)
|
112
|
+
exc_msg_and_response!(exc, response)
|
113
|
+
@response = unwrap_resp!(@response)
|
114
|
+
super('http status could not be derived from the server response')
|
53
115
|
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
extend Faraday::Deprecate
|
120
|
+
|
121
|
+
def unwrap_resp(resp)
|
122
|
+
if inner = (resp.keys.size == 1 && resp[:response])
|
123
|
+
return unwrap_resp(inner)
|
124
|
+
end
|
125
|
+
|
126
|
+
resp
|
127
|
+
end
|
128
|
+
|
129
|
+
alias_method :unwrap_resp!, :unwrap_resp
|
130
|
+
deprecate('unwrap_resp', nil, '1.0')
|
54
131
|
end
|
55
132
|
|
133
|
+
# A unified error for failed connections.
|
134
|
+
class ConnectionFailed < ClientError
|
135
|
+
end
|
136
|
+
|
137
|
+
# A unified client error for SSL errors.
|
56
138
|
class SSLError < ClientError
|
57
139
|
end
|
58
140
|
|
59
|
-
|
60
|
-
|
61
|
-
|
141
|
+
# Raised by FaradayMiddleware::ResponseMiddleware
|
142
|
+
class ParsingError < ClientError
|
143
|
+
end
|
144
|
+
|
145
|
+
# Exception used to control the Retry middleware.
|
146
|
+
#
|
147
|
+
# @see Faraday::Request::Retry
|
148
|
+
class RetriableResponse < ClientError
|
149
|
+
end
|
150
|
+
|
151
|
+
[:ClientError, :ConnectionFailed, :ResourceNotFound,
|
152
|
+
:ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
|
153
|
+
Error.const_set(
|
154
|
+
const,
|
155
|
+
DeprecatedClass.proxy_class(Faraday.const_get(const))
|
156
|
+
)
|
62
157
|
end
|
63
158
|
end
|
data/lib/faraday/options.rb
CHANGED
@@ -18,23 +18,20 @@ module Faraday
|
|
18
18
|
# Public
|
19
19
|
def update(obj)
|
20
20
|
obj.each do |key, value|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
value.
|
26
|
-
|
27
|
-
|
28
|
-
value = hash
|
21
|
+
sub_options = self.class.options_for(key)
|
22
|
+
if sub_options
|
23
|
+
new_value = sub_options.from(value) if value
|
24
|
+
elsif value.is_a?(Hash)
|
25
|
+
new_value = value.dup
|
26
|
+
else
|
27
|
+
new_value = value
|
29
28
|
end
|
30
29
|
|
31
|
-
self.send("#{key}=",
|
30
|
+
self.send("#{key}=", new_value) unless new_value.nil?
|
32
31
|
end
|
33
32
|
self
|
34
33
|
end
|
35
34
|
|
36
|
-
alias merge! update
|
37
|
-
|
38
35
|
# Public
|
39
36
|
def delete(key)
|
40
37
|
value = send(key)
|
@@ -48,17 +45,26 @@ module Faraday
|
|
48
45
|
end
|
49
46
|
|
50
47
|
# Public
|
51
|
-
def merge(
|
52
|
-
|
48
|
+
def merge!(other)
|
49
|
+
other.each do |key, other_value|
|
50
|
+
self_value = self.send(key)
|
51
|
+
sub_options = self.class.options_for(key)
|
52
|
+
new_value = (self_value && sub_options && other_value) ? self_value.merge(other_value) : other_value
|
53
|
+
self.send("#{key}=", new_value) unless new_value.nil?
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public
|
59
|
+
def merge(other)
|
60
|
+
dup.merge!(other)
|
53
61
|
end
|
54
|
-
|
62
|
+
|
55
63
|
# Public
|
56
|
-
def
|
64
|
+
def deep_dup
|
57
65
|
self.class.from(self)
|
58
66
|
end
|
59
67
|
|
60
|
-
alias clone dup
|
61
|
-
|
62
68
|
# Public
|
63
69
|
def fetch(key, *args)
|
64
70
|
unless symbolized_key_set.include?(key.to_sym)
|
@@ -66,7 +72,7 @@ module Faraday
|
|
66
72
|
if args.size > 0
|
67
73
|
send(key_setter, args.first)
|
68
74
|
elsif block_given?
|
69
|
-
send(key_setter,
|
75
|
+
send(key_setter, yield(key))
|
70
76
|
else
|
71
77
|
raise self.class.fetch_error_class, "key not found: #{key.inspect}"
|
72
78
|
end
|
@@ -156,8 +162,8 @@ module Faraday
|
|
156
162
|
@attribute_options ||= {}
|
157
163
|
end
|
158
164
|
|
159
|
-
def self.memoized(key)
|
160
|
-
memoized_attributes[key.to_sym] =
|
165
|
+
def self.memoized(key, &block)
|
166
|
+
memoized_attributes[key.to_sym] = block
|
161
167
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
162
168
|
def #{key}() self[:#{key}]; end
|
163
169
|
RUBY
|
@@ -196,8 +202,7 @@ module Faraday
|
|
196
202
|
end
|
197
203
|
|
198
204
|
class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
|
199
|
-
:timeout, :open_timeout, :boundary,
|
200
|
-
:oauth)
|
205
|
+
:timeout, :open_timeout, :write_timeout, :boundary, :oauth, :context)
|
201
206
|
|
202
207
|
def []=(key, value)
|
203
208
|
if key && key.to_sym == :proxy
|
@@ -209,7 +214,8 @@ module Faraday
|
|
209
214
|
end
|
210
215
|
|
211
216
|
class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
|
212
|
-
:cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth,
|
217
|
+
:cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth,
|
218
|
+
:version, :min_version, :max_version)
|
213
219
|
|
214
220
|
def verify?
|
215
221
|
verify != false
|
@@ -238,8 +244,8 @@ module Faraday
|
|
238
244
|
super(value)
|
239
245
|
end
|
240
246
|
|
241
|
-
memoized(:user) { uri.user && Utils.unescape(uri.user) }
|
242
|
-
memoized(:password) { uri.password && Utils.unescape(uri.password) }
|
247
|
+
memoized(:user) { uri && uri.user && Utils.unescape(uri.user) }
|
248
|
+
memoized(:password) { uri && uri.password && Utils.unescape(uri.password) }
|
243
249
|
end
|
244
250
|
|
245
251
|
class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
|
data/lib/faraday/parameters.rb
CHANGED
@@ -40,9 +40,10 @@ module Faraday
|
|
40
40
|
end
|
41
41
|
return buffer.chop
|
42
42
|
elsif value.is_a?(Array)
|
43
|
+
new_parent = "#{parent}%5B%5D"
|
44
|
+
return new_parent if value.empty?
|
43
45
|
buffer = ""
|
44
46
|
value.each_with_index do |val, i|
|
45
|
-
new_parent = "#{parent}%5B%5D"
|
46
47
|
buffer << "#{to_query.call(new_parent, val)}&"
|
47
48
|
end
|
48
49
|
return buffer.chop
|
data/lib/faraday/rack_builder.rb
CHANGED
@@ -49,10 +49,10 @@ module Faraday
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
def initialize(handlers = [])
|
52
|
+
def initialize(handlers = [], &block)
|
53
53
|
@handlers = handlers
|
54
54
|
if block_given?
|
55
|
-
build(&
|
55
|
+
build(&block)
|
56
56
|
elsif @handlers.empty?
|
57
57
|
# default stack, if nothing else is configured
|
58
58
|
self.request :url_encoded
|
@@ -84,6 +84,7 @@ module Faraday
|
|
84
84
|
use_symbol(Faraday::Middleware, klass, *args, &block)
|
85
85
|
else
|
86
86
|
raise_if_locked
|
87
|
+
warn_middleware_after_adapter if adapter_set?
|
87
88
|
@handlers << self.class::Handler.new(klass, *args, &block)
|
88
89
|
end
|
89
90
|
end
|
@@ -105,6 +106,7 @@ module Faraday
|
|
105
106
|
def insert(index, *args, &block)
|
106
107
|
raise_if_locked
|
107
108
|
index = assert_index(index)
|
109
|
+
warn_middleware_after_adapter if inserting_after_adapter?(index)
|
108
110
|
handler = self.class::Handler.new(*args, &block)
|
109
111
|
@handlers.insert(index, handler)
|
110
112
|
end
|
@@ -136,6 +138,8 @@ module Faraday
|
|
136
138
|
#
|
137
139
|
# Returns a Faraday::Response.
|
138
140
|
def build_response(connection, request)
|
141
|
+
warn 'WARNING: No adapter was configured for this request' unless adapter_set?
|
142
|
+
|
139
143
|
app.call(build_env(connection, request))
|
140
144
|
end
|
141
145
|
|
@@ -200,6 +204,26 @@ module Faraday
|
|
200
204
|
raise StackLocked, "can't modify middleware stack after making a request" if locked?
|
201
205
|
end
|
202
206
|
|
207
|
+
def warn_middleware_after_adapter
|
208
|
+
warn "WARNING: Unexpected middleware set after the adapter. " \
|
209
|
+
"This won't be supported from Faraday 1.0."
|
210
|
+
end
|
211
|
+
|
212
|
+
def adapter_set?
|
213
|
+
@handlers.any? { |handler| is_adapter?(handler) }
|
214
|
+
end
|
215
|
+
|
216
|
+
def inserting_after_adapter?(index)
|
217
|
+
adapter_index = @handlers.find_index { |handler| is_adapter?(handler) }
|
218
|
+
return false if adapter_index.nil?
|
219
|
+
|
220
|
+
index > adapter_index
|
221
|
+
end
|
222
|
+
|
223
|
+
def is_adapter?(handler)
|
224
|
+
handler.klass.ancestors.include? Faraday::Adapter
|
225
|
+
end
|
226
|
+
|
203
227
|
def use_symbol(mod, key, *args, &block)
|
204
228
|
use(mod.lookup_middleware(key), *args, &block)
|
205
229
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
require File.expand_path("../url_encoded", __FILE__)
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
module Faraday
|
4
5
|
class Request::Multipart < Request::UrlEncoded
|
5
6
|
self.mime_type = 'multipart/form-data'.freeze
|
6
|
-
|
7
|
+
DEFAULT_BOUNDARY_PREFIX = "-----------RubyMultipartPost".freeze unless defined? DEFAULT_BOUNDARY_PREFIX
|
7
8
|
|
8
9
|
def call(env)
|
9
10
|
match_content_type(env) do |params|
|
10
|
-
env.request.boundary ||=
|
11
|
+
env.request.boundary ||= unique_boundary
|
11
12
|
env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}"
|
12
13
|
env.body = create_multipart(env, params)
|
13
14
|
end
|
@@ -44,6 +45,10 @@ module Faraday
|
|
44
45
|
return body
|
45
46
|
end
|
46
47
|
|
48
|
+
def unique_boundary
|
49
|
+
"#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
|
50
|
+
end
|
51
|
+
|
47
52
|
def process_params(params, prefix = nil, pieces = nil, &block)
|
48
53
|
params.inject(pieces || []) do |all, (key, value)|
|
49
54
|
key = "#{prefix}[#{key}]" if prefix
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
1
3
|
module Faraday
|
2
4
|
# Catches exceptions and retries each request a limited number of times.
|
3
5
|
#
|
@@ -10,7 +12,7 @@ module Faraday
|
|
10
12
|
#
|
11
13
|
# Faraday.new do |conn|
|
12
14
|
# conn.request :retry, max: 2, interval: 0.05,
|
13
|
-
# interval_randomness: 0.5, backoff_factor: 2
|
15
|
+
# interval_randomness: 0.5, backoff_factor: 2,
|
14
16
|
# exceptions: [CustomException, 'Timeout::Error']
|
15
17
|
# conn.adapter ...
|
16
18
|
# end
|
@@ -19,11 +21,15 @@ module Faraday
|
|
19
21
|
# interval that is random between 0.1 and 0.15
|
20
22
|
#
|
21
23
|
class Request::Retry < Faraday::Middleware
|
22
|
-
|
24
|
+
DEFAULT_EXCEPTIONS = [Errno::ETIMEDOUT, 'Timeout::Error',
|
25
|
+
Faraday::TimeoutError, Faraday::RetriableResponse
|
26
|
+
].freeze
|
23
27
|
IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
|
24
28
|
|
25
29
|
class Options < Faraday::Options.new(:max, :interval, :max_interval, :interval_randomness,
|
26
|
-
:backoff_factor, :exceptions, :methods, :retry_if
|
30
|
+
:backoff_factor, :exceptions, :methods, :retry_if, :retry_block,
|
31
|
+
:retry_statuses)
|
32
|
+
|
27
33
|
DEFAULT_CHECK = lambda { |env,exception| false }
|
28
34
|
|
29
35
|
def self.from(value)
|
@@ -55,8 +61,7 @@ module Faraday
|
|
55
61
|
end
|
56
62
|
|
57
63
|
def exceptions
|
58
|
-
Array(self[:exceptions] ||=
|
59
|
-
Error::TimeoutError])
|
64
|
+
Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
|
60
65
|
end
|
61
66
|
|
62
67
|
def methods
|
@@ -67,6 +72,13 @@ module Faraday
|
|
67
72
|
self[:retry_if] ||= DEFAULT_CHECK
|
68
73
|
end
|
69
74
|
|
75
|
+
def retry_block
|
76
|
+
self[:retry_block] ||= Proc.new {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def retry_statuses
|
80
|
+
Array(self[:retry_statuses] ||= [])
|
81
|
+
end
|
70
82
|
end
|
71
83
|
|
72
84
|
# Public: Initialize middleware
|
@@ -83,8 +95,8 @@ module Faraday
|
|
83
95
|
# (default: 1)
|
84
96
|
# exceptions - The list of exceptions to handle. Exceptions can be
|
85
97
|
# given as Class, Module, or String. (default:
|
86
|
-
# [Errno::ETIMEDOUT, Timeout::Error,
|
87
|
-
#
|
98
|
+
# [Errno::ETIMEDOUT, 'Timeout::Error',
|
99
|
+
# Faraday::TimeoutError, Faraday::RetriableResponse])
|
88
100
|
# methods - A list of HTTP methods to retry without calling retry_if. Pass
|
89
101
|
# an empty Array to call retry_if for all exceptions.
|
90
102
|
# (defaults to the idempotent HTTP methods in IDEMPOTENT_METHODS)
|
@@ -94,18 +106,21 @@ module Faraday
|
|
94
106
|
# if the exception produced is non-recoverable or if the
|
95
107
|
# the HTTP method called is not idempotent.
|
96
108
|
# (defaults to return false)
|
109
|
+
# retry_block - block that is executed after every retry. Request environment, middleware options,
|
110
|
+
# current number of retries and the exception is passed to the block as parameters.
|
97
111
|
def initialize(app, options = nil)
|
98
112
|
super(app)
|
99
113
|
@options = Options.from(options)
|
100
114
|
@errmatch = build_exception_matcher(@options.exceptions)
|
101
115
|
end
|
102
116
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
117
|
+
def calculate_sleep_amount(retries, env)
|
118
|
+
retry_after = calculate_retry_after(env)
|
119
|
+
retry_interval = calculate_retry_interval(retries)
|
120
|
+
|
121
|
+
return if retry_after && retry_after > @options.max_interval
|
122
|
+
|
123
|
+
retry_after && retry_after >= retry_interval ? retry_after : retry_interval
|
109
124
|
end
|
110
125
|
|
111
126
|
def call(env)
|
@@ -113,14 +128,25 @@ module Faraday
|
|
113
128
|
request_body = env[:body]
|
114
129
|
begin
|
115
130
|
env[:body] = request_body # after failure env[:body] is set to the response body
|
116
|
-
@app.call(env)
|
131
|
+
@app.call(env).tap do |resp|
|
132
|
+
raise Faraday::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status)
|
133
|
+
end
|
117
134
|
rescue @errmatch => exception
|
118
135
|
if retries > 0 && retry_request?(env, exception)
|
119
136
|
retries -= 1
|
120
|
-
|
121
|
-
|
137
|
+
rewind_files(request_body)
|
138
|
+
@options.retry_block.call(env, @options, retries, exception)
|
139
|
+
if (sleep_amount = calculate_sleep_amount(retries + 1, env))
|
140
|
+
sleep sleep_amount
|
141
|
+
retry
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
if exception.is_a?(Faraday::RetriableResponse)
|
146
|
+
exception.response
|
147
|
+
else
|
148
|
+
raise
|
122
149
|
end
|
123
|
-
raise
|
124
150
|
end
|
125
151
|
end
|
126
152
|
|
@@ -150,5 +176,38 @@ module Faraday
|
|
150
176
|
@options.methods.include?(env[:method]) || @options.retry_if.call(env, exception)
|
151
177
|
end
|
152
178
|
|
179
|
+
def rewind_files(body)
|
180
|
+
return unless body.is_a?(Hash)
|
181
|
+
body.each do |_, value|
|
182
|
+
if value.is_a? UploadIO
|
183
|
+
value.rewind
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# MDN spec for Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
189
|
+
def calculate_retry_after(env)
|
190
|
+
response_headers = env[:response_headers]
|
191
|
+
return unless response_headers
|
192
|
+
|
193
|
+
retry_after_value = env[:response_headers]["Retry-After"]
|
194
|
+
|
195
|
+
# Try to parse date from the header value
|
196
|
+
begin
|
197
|
+
datetime = DateTime.rfc2822(retry_after_value)
|
198
|
+
datetime.to_time - Time.now.utc
|
199
|
+
rescue ArgumentError
|
200
|
+
retry_after_value.to_f
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def calculate_retry_interval(retries)
|
205
|
+
retry_index = @options.max - retries
|
206
|
+
current_interval = @options.interval * (@options.backoff_factor ** retry_index)
|
207
|
+
current_interval = [current_interval, @options.max_interval].min
|
208
|
+
random_interval = rand * @options.interval_randomness.to_f * @options.interval
|
209
|
+
|
210
|
+
current_interval + random_interval
|
211
|
+
end
|
153
212
|
end
|
154
213
|
end
|