faraday 1.0.0 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +221 -1
- data/LICENSE.md +1 -1
- data/README.md +19 -14
- data/examples/client_spec.rb +36 -4
- data/examples/client_test.rb +43 -4
- data/lib/faraday/adapter/test.rb +61 -43
- data/lib/faraday/adapter.rb +3 -16
- data/lib/faraday/adapter_registry.rb +3 -1
- data/lib/faraday/connection.rb +25 -78
- data/lib/faraday/encoders/flat_params_encoder.rb +9 -2
- data/lib/faraday/encoders/nested_params_encoder.rb +9 -4
- data/lib/faraday/error.rb +23 -8
- data/lib/faraday/logging/formatter.rb +1 -0
- data/lib/faraday/methods.rb +6 -0
- data/lib/faraday/middleware.rb +14 -5
- data/lib/faraday/middleware_registry.rb +15 -79
- data/lib/faraday/options/proxy_options.rb +4 -0
- data/lib/faraday/options.rb +7 -11
- data/lib/faraday/rack_builder.rb +34 -30
- data/lib/faraday/request/authorization.rb +32 -36
- data/lib/faraday/request/instrumentation.rb +2 -0
- data/lib/faraday/request/json.rb +55 -0
- data/lib/faraday/request/url_encoded.rb +5 -1
- data/lib/faraday/request.rb +13 -23
- data/lib/faraday/response/json.rb +54 -0
- data/lib/faraday/response/logger.rb +4 -4
- data/lib/faraday/response/raise_error.rb +20 -1
- data/lib/faraday/response.rb +8 -22
- data/lib/faraday/utils/headers.rb +3 -3
- data/lib/faraday/utils.rb +21 -8
- data/lib/faraday/version.rb +5 -0
- data/lib/faraday.rb +44 -59
- data/spec/faraday/adapter/test_spec.rb +377 -0
- data/spec/faraday/connection_spec.rb +147 -51
- data/spec/faraday/error_spec.rb +15 -0
- data/spec/faraday/middleware_spec.rb +32 -6
- data/spec/faraday/options/env_spec.rb +2 -2
- data/spec/faraday/options/proxy_options_spec.rb +7 -0
- data/spec/faraday/params_encoders/flat_spec.rb +8 -0
- data/spec/faraday/params_encoders/nested_spec.rb +8 -0
- data/spec/faraday/rack_builder_spec.rb +144 -38
- data/spec/faraday/request/authorization_spec.rb +19 -24
- data/spec/faraday/request/instrumentation_spec.rb +5 -7
- data/spec/faraday/request/json_spec.rb +111 -0
- data/spec/faraday/request/url_encoded_spec.rb +13 -1
- data/spec/faraday/request_spec.rb +6 -6
- data/spec/faraday/response/json_spec.rb +117 -0
- data/spec/faraday/response/raise_error_spec.rb +66 -0
- data/spec/faraday/utils_spec.rb +62 -1
- data/spec/support/fake_safe_buffer.rb +1 -1
- data/spec/support/helper_methods.rb +0 -37
- data/spec/support/shared_examples/adapter.rb +2 -2
- data/spec/support/shared_examples/request_method.rb +43 -28
- metadata +16 -48
- data/UPGRADING.md +0 -55
- data/lib/faraday/adapter/em_http.rb +0 -285
- data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -62
- data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -69
- data/lib/faraday/adapter/em_synchrony.rb +0 -150
- data/lib/faraday/adapter/excon.rb +0 -124
- data/lib/faraday/adapter/httpclient.rb +0 -151
- data/lib/faraday/adapter/net_http.rb +0 -209
- data/lib/faraday/adapter/net_http_persistent.rb +0 -91
- data/lib/faraday/adapter/patron.rb +0 -132
- data/lib/faraday/adapter/rack.rb +0 -75
- data/lib/faraday/adapter/typhoeus.rb +0 -15
- data/lib/faraday/autoload.rb +0 -95
- data/lib/faraday/dependency_loader.rb +0 -37
- data/lib/faraday/file_part.rb +0 -128
- data/lib/faraday/param_part.rb +0 -53
- data/lib/faraday/request/basic_authentication.rb +0 -20
- data/lib/faraday/request/multipart.rb +0 -99
- data/lib/faraday/request/retry.rb +0 -239
- data/lib/faraday/request/token_authentication.rb +0 -20
- data/spec/faraday/adapter/em_http_spec.rb +0 -47
- data/spec/faraday/adapter/em_synchrony_spec.rb +0 -16
- data/spec/faraday/adapter/excon_spec.rb +0 -49
- data/spec/faraday/adapter/httpclient_spec.rb +0 -73
- data/spec/faraday/adapter/net_http_persistent_spec.rb +0 -57
- data/spec/faraday/adapter/net_http_spec.rb +0 -64
- data/spec/faraday/adapter/patron_spec.rb +0 -18
- data/spec/faraday/adapter/rack_spec.rb +0 -8
- data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
- data/spec/faraday/composite_read_io_spec.rb +0 -80
- data/spec/faraday/request/multipart_spec.rb +0 -274
- data/spec/faraday/request/retry_spec.rb +0 -242
- data/spec/faraday/response/middleware_spec.rb +0 -52
- data/spec/support/webmock_rack_app.rb +0 -68
data/lib/faraday/autoload.rb
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Faraday
|
4
|
-
# Adds the ability for other modules to manage autoloadable
|
5
|
-
# constants.
|
6
|
-
#
|
7
|
-
# @api private
|
8
|
-
module AutoloadHelper
|
9
|
-
# Registers the constants to be auto loaded.
|
10
|
-
#
|
11
|
-
# @param prefix [String] The require prefix. If the path is inside Faraday,
|
12
|
-
# then it will be prefixed with the root path of this loaded
|
13
|
-
# Faraday version.
|
14
|
-
# @param options [{ Symbol => String }] library names.
|
15
|
-
#
|
16
|
-
# @example
|
17
|
-
#
|
18
|
-
# Faraday.autoload_all 'faraday/foo',
|
19
|
-
# Bar: 'bar'
|
20
|
-
#
|
21
|
-
# # requires faraday/foo/bar to load Faraday::Bar.
|
22
|
-
# Faraday::Bar
|
23
|
-
#
|
24
|
-
# @return [void]
|
25
|
-
def autoload_all(prefix, options)
|
26
|
-
if prefix =~ %r{^faraday(/|$)}i
|
27
|
-
prefix = File.join(Faraday.root_path, prefix)
|
28
|
-
end
|
29
|
-
|
30
|
-
options.each do |const_name, path|
|
31
|
-
autoload const_name, File.join(prefix, path)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Loads each autoloaded constant. If thread safety is a concern,
|
36
|
-
# wrap this in a Mutex.
|
37
|
-
#
|
38
|
-
# @return [void]
|
39
|
-
def load_autoloaded_constants
|
40
|
-
constants.each do |const|
|
41
|
-
const_get(const) if autoload?(const)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Filters the module's contents with those that have been already
|
46
|
-
# autoloaded.
|
47
|
-
#
|
48
|
-
# @return [Array<Class, Module>]
|
49
|
-
def all_loaded_constants
|
50
|
-
constants
|
51
|
-
.map { |c| const_get(c) }
|
52
|
-
.select { |a| a.respond_to?(:loaded?) && a.loaded? }
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Adapter is the base class for all Faraday adapters.
|
57
|
-
# @see lib/faraday/adapter.rb Original class location
|
58
|
-
class Adapter
|
59
|
-
extend AutoloadHelper
|
60
|
-
autoload_all 'faraday/adapter',
|
61
|
-
NetHttp: 'net_http',
|
62
|
-
NetHttpPersistent: 'net_http_persistent',
|
63
|
-
EMSynchrony: 'em_synchrony',
|
64
|
-
EMHttp: 'em_http',
|
65
|
-
Typhoeus: 'typhoeus',
|
66
|
-
Patron: 'patron',
|
67
|
-
Excon: 'excon',
|
68
|
-
Test: 'test',
|
69
|
-
Rack: 'rack',
|
70
|
-
HTTPClient: 'httpclient'
|
71
|
-
end
|
72
|
-
|
73
|
-
# Request represents a single HTTP request for a Faraday adapter to make.
|
74
|
-
# @see lib/faraday/request.rb Original class location
|
75
|
-
class Request
|
76
|
-
extend AutoloadHelper
|
77
|
-
autoload_all 'faraday/request',
|
78
|
-
UrlEncoded: 'url_encoded',
|
79
|
-
Multipart: 'multipart',
|
80
|
-
Retry: 'retry',
|
81
|
-
Authorization: 'authorization',
|
82
|
-
BasicAuthentication: 'basic_authentication',
|
83
|
-
TokenAuthentication: 'token_authentication',
|
84
|
-
Instrumentation: 'instrumentation'
|
85
|
-
end
|
86
|
-
|
87
|
-
# Response represents the returned value of a sent Faraday request.
|
88
|
-
# @see lib/faraday/response.rb Original class location
|
89
|
-
class Response
|
90
|
-
extend AutoloadHelper
|
91
|
-
autoload_all 'faraday/response',
|
92
|
-
RaiseError: 'raise_error',
|
93
|
-
Logger: 'logger'
|
94
|
-
end
|
95
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Faraday
|
4
|
-
# DependencyLoader helps Faraday adapters and middleware load dependencies.
|
5
|
-
module DependencyLoader
|
6
|
-
attr_reader :load_error
|
7
|
-
|
8
|
-
# Executes a block which should try to require and reference dependent
|
9
|
-
# libraries
|
10
|
-
def dependency(lib = nil)
|
11
|
-
lib ? require(lib) : yield
|
12
|
-
rescue LoadError, NameError => e
|
13
|
-
self.load_error = e
|
14
|
-
end
|
15
|
-
|
16
|
-
def new(*)
|
17
|
-
unless loaded?
|
18
|
-
raise "missing dependency for #{self}: #{load_error.message}"
|
19
|
-
end
|
20
|
-
|
21
|
-
super
|
22
|
-
end
|
23
|
-
|
24
|
-
def loaded?
|
25
|
-
load_error.nil?
|
26
|
-
end
|
27
|
-
|
28
|
-
def inherited(subclass)
|
29
|
-
super
|
30
|
-
subclass.send(:load_error=, load_error)
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
attr_writer :load_error
|
36
|
-
end
|
37
|
-
end
|
data/lib/faraday/file_part.rb
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'stringio'
|
4
|
-
|
5
|
-
# multipart-post gem
|
6
|
-
require 'composite_io'
|
7
|
-
require 'parts'
|
8
|
-
|
9
|
-
module Faraday
|
10
|
-
# Multipart value used to POST a binary data from a file or
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
# payload = { file: Faraday::FilePart.new("file_name.ext", "content/type") }
|
14
|
-
# http.post("/upload", payload)
|
15
|
-
#
|
16
|
-
|
17
|
-
# @!method initialize(filename_or_io, content_type, filename = nil, opts = {})
|
18
|
-
#
|
19
|
-
# @param filename_or_io [String, IO] Either a String filename to a local
|
20
|
-
# file or an open IO object.
|
21
|
-
# @param content_type [String] String content type of the file data.
|
22
|
-
# @param filename [String] Optional String filename, usually to add context
|
23
|
-
# to a given IO object.
|
24
|
-
# @param opts [Hash] Optional Hash of String key/value pairs to describethis
|
25
|
-
# this uploaded file. Expected Header keys include:
|
26
|
-
# * Content-Transfer-Encoding - Defaults to "binary"
|
27
|
-
# * Content-Disposition - Defaults to "form-data"
|
28
|
-
# * Content-Type - Defaults to the content_type argument.
|
29
|
-
# * Content-ID - Optional.
|
30
|
-
#
|
31
|
-
# @return [Faraday::FilePart]
|
32
|
-
#
|
33
|
-
# @!attribute [r] content_type
|
34
|
-
# The uploaded binary data's content type.
|
35
|
-
#
|
36
|
-
# @return [String]
|
37
|
-
#
|
38
|
-
# @!attribute [r] original_filename
|
39
|
-
# The base filename, taken either from the filename_or_io or filename
|
40
|
-
# arguments in #initialize.
|
41
|
-
#
|
42
|
-
# @return [String]
|
43
|
-
#
|
44
|
-
# @!attribute [r] opts
|
45
|
-
# Extra String key/value pairs to make up the header for this uploaded file.
|
46
|
-
#
|
47
|
-
# @return [Hash]
|
48
|
-
#
|
49
|
-
# @!attribute [r] io
|
50
|
-
# The open IO object for the uploaded file.
|
51
|
-
#
|
52
|
-
# @return [IO]
|
53
|
-
FilePart = ::UploadIO
|
54
|
-
|
55
|
-
# Multipart value used to POST a file.
|
56
|
-
#
|
57
|
-
# @deprecated Use FilePart instead of this class. It behaves identically, with
|
58
|
-
# a matching name to ParamPart.
|
59
|
-
UploadIO = ::UploadIO
|
60
|
-
|
61
|
-
Parts = ::Parts
|
62
|
-
|
63
|
-
# Similar to, but not compatible with CompositeReadIO provided by the
|
64
|
-
# multipart-post gem.
|
65
|
-
# https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb
|
66
|
-
class CompositeReadIO
|
67
|
-
def initialize(*parts)
|
68
|
-
@parts = parts.flatten
|
69
|
-
@ios = @parts.map(&:to_io)
|
70
|
-
@index = 0
|
71
|
-
end
|
72
|
-
|
73
|
-
# @return [Integer] sum of the lengths of all the parts
|
74
|
-
def length
|
75
|
-
@parts.inject(0) { |sum, part| sum + part.length }
|
76
|
-
end
|
77
|
-
|
78
|
-
# Rewind each of the IOs and reset the index to 0.
|
79
|
-
#
|
80
|
-
# @return [void]
|
81
|
-
def rewind
|
82
|
-
@ios.each(&:rewind)
|
83
|
-
@index = 0
|
84
|
-
end
|
85
|
-
|
86
|
-
# Read from IOs in order until `length` bytes have been received.
|
87
|
-
#
|
88
|
-
# @param length [Integer, nil]
|
89
|
-
# @param outbuf [String, nil]
|
90
|
-
def read(length = nil, outbuf = nil)
|
91
|
-
got_result = false
|
92
|
-
outbuf = outbuf ? (+outbuf).replace('') : +''
|
93
|
-
|
94
|
-
while (io = current_io)
|
95
|
-
if (result = io.read(length))
|
96
|
-
got_result ||= !result.nil?
|
97
|
-
result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
|
98
|
-
outbuf << result
|
99
|
-
length -= result.length if length
|
100
|
-
break if length&.zero?
|
101
|
-
end
|
102
|
-
advance_io
|
103
|
-
end
|
104
|
-
!got_result && length ? nil : outbuf
|
105
|
-
end
|
106
|
-
|
107
|
-
# Close each of the IOs.
|
108
|
-
#
|
109
|
-
# @return [void]
|
110
|
-
def close
|
111
|
-
@ios.each(&:close)
|
112
|
-
end
|
113
|
-
|
114
|
-
def ensure_open_and_readable
|
115
|
-
# Rubinius compatibility
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
def current_io
|
121
|
-
@ios[@index]
|
122
|
-
end
|
123
|
-
|
124
|
-
def advance_io
|
125
|
-
@index += 1
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
data/lib/faraday/param_part.rb
DELETED
@@ -1,53 +0,0 @@
|
|
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
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'base64'
|
4
|
-
|
5
|
-
module Faraday
|
6
|
-
class Request
|
7
|
-
# Authorization middleware for Basic Authentication.
|
8
|
-
class BasicAuthentication < load_middleware(:authorization)
|
9
|
-
# @param login [String]
|
10
|
-
# @param pass [String]
|
11
|
-
#
|
12
|
-
# @return [String] a Basic Authentication header line
|
13
|
-
def self.header(login, pass)
|
14
|
-
value = Base64.encode64([login, pass].join(':'))
|
15
|
-
value.delete!("\n")
|
16
|
-
super(:Basic, value)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,99 +0,0 @@
|
|
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? DEFAULT_BOUNDARY_PREFIX
|
12
|
-
DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
|
13
|
-
end
|
14
|
-
|
15
|
-
# Checks for files in the payload, otherwise leaves everything untouched.
|
16
|
-
#
|
17
|
-
# @param env [Faraday::Env]
|
18
|
-
def call(env)
|
19
|
-
match_content_type(env) do |params|
|
20
|
-
env.request.boundary ||= unique_boundary
|
21
|
-
env.request_headers[CONTENT_TYPE] +=
|
22
|
-
"; boundary=#{env.request.boundary}"
|
23
|
-
env.body = create_multipart(env, params)
|
24
|
-
end
|
25
|
-
@app.call env
|
26
|
-
end
|
27
|
-
|
28
|
-
# @param env [Faraday::Env]
|
29
|
-
def process_request?(env)
|
30
|
-
type = request_type(env)
|
31
|
-
env.body.respond_to?(:each_key) && !env.body.empty? && (
|
32
|
-
(type.empty? && has_multipart?(env.body)) ||
|
33
|
-
(type == self.class.mime_type)
|
34
|
-
)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Returns true if obj is an enumerable with values that are multipart.
|
38
|
-
#
|
39
|
-
# @param obj [Object]
|
40
|
-
# @return [Boolean]
|
41
|
-
def has_multipart?(obj) # rubocop:disable Naming/PredicateName
|
42
|
-
if obj.respond_to?(:each)
|
43
|
-
(obj.respond_to?(:values) ? obj.values : obj).each do |val|
|
44
|
-
return true if val.respond_to?(:content_type) || has_multipart?(val)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
false
|
48
|
-
end
|
49
|
-
|
50
|
-
# @param env [Faraday::Env]
|
51
|
-
# @param params [Hash]
|
52
|
-
def create_multipart(env, params)
|
53
|
-
boundary = env.request.boundary
|
54
|
-
parts = process_params(params) do |key, value|
|
55
|
-
part(boundary, key, value)
|
56
|
-
end
|
57
|
-
parts << Faraday::Parts::EpiloguePart.new(boundary)
|
58
|
-
|
59
|
-
body = Faraday::CompositeReadIO.new(parts)
|
60
|
-
env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
|
61
|
-
body
|
62
|
-
end
|
63
|
-
|
64
|
-
def part(boundary, key, value)
|
65
|
-
if value.respond_to?(:to_part)
|
66
|
-
value.to_part(boundary, key)
|
67
|
-
else
|
68
|
-
Faraday::Parts::Part.new(boundary, key, value)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# @return [String]
|
73
|
-
def unique_boundary
|
74
|
-
"#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
|
75
|
-
end
|
76
|
-
|
77
|
-
# @param params [Hash]
|
78
|
-
# @param prefix [String]
|
79
|
-
# @param pieces [Array]
|
80
|
-
def process_params(params, prefix = nil, pieces = nil, &block)
|
81
|
-
params.inject(pieces || []) do |all, (key, value)|
|
82
|
-
key = "#{prefix}[#{key}]" if prefix
|
83
|
-
|
84
|
-
case value
|
85
|
-
when Array
|
86
|
-
values = value.inject([]) { |a, v| a << [nil, v] }
|
87
|
-
process_params(values, key, all, &block)
|
88
|
-
when Hash
|
89
|
-
process_params(value, key, all, &block)
|
90
|
-
else
|
91
|
-
# rubocop:disable Performance/RedundantBlockCall
|
92
|
-
all << block.call(key, value)
|
93
|
-
# rubocop:enable Performance/RedundantBlockCall
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
@@ -1,239 +0,0 @@
|
|
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.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
|
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 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
|
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
|
-
error.class.to_s == 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
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
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
|
14
|
-
|
15
|
-
def initialize(app, token, options = nil)
|
16
|
-
super(app, token, options)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|