faraday 1.8.0 → 1.9.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/lib/faraday/error.rb +0 -6
- data/lib/faraday/version.rb +1 -1
- data/lib/faraday.rb +6 -2
- data/spec/faraday/request/instrumentation_spec.rb +5 -7
- data/spec/support/webmock_rack_app.rb +1 -2
- metadata +36 -34
- data/lib/faraday/file_part.rb +0 -128
- data/lib/faraday/param_part.rb +0 -53
- data/lib/faraday/request/multipart.rb +0 -106
- data/lib/faraday/request/retry.rb +0 -239
- data/spec/faraday/request/multipart_spec.rb +0 -302
- data/spec/faraday/request/retry_spec.rb +0 -242
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d04b0b5407943b47e2c55e93f2102cb1b0b268f2de5505eb5346410aec53ee8
|
4
|
+
data.tar.gz: 7793d3c324d35bb183192e8ed0f43d1036f8590907491b979a7e5da2d0a09266
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 308fd91172d26082a1de8401221507492a74a2e9a2ac8bad59e4e6f25f3d2b88f8c42258c12be65f0587ffb9d361e1c8f4c889d877dc3a401322283dc3106707
|
7
|
+
data.tar.gz: c5b84b518095290e8ddbbf660b37f798a54849e604cc6550bf6ba9e6b4792b6a1de3994c41d2df182ec0cdafcdab62c15bafc9dc147b658aabc9e5059f7a2a52
|
data/lib/faraday/error.rb
CHANGED
@@ -143,10 +143,4 @@ module Faraday
|
|
143
143
|
# Raised by FaradayMiddleware::ResponseMiddleware
|
144
144
|
class ParsingError < Error
|
145
145
|
end
|
146
|
-
|
147
|
-
# Exception used to control the Retry middleware.
|
148
|
-
#
|
149
|
-
# @see Faraday::Request::Retry
|
150
|
-
class RetriableResponse < Error
|
151
|
-
end
|
152
146
|
end
|
data/lib/faraday/version.rb
CHANGED
data/lib/faraday.rb
CHANGED
@@ -24,9 +24,13 @@ require 'faraday/adapter'
|
|
24
24
|
require 'faraday/request'
|
25
25
|
require 'faraday/response'
|
26
26
|
require 'faraday/error'
|
27
|
-
require 'faraday/
|
28
|
-
require 'faraday/param_part'
|
27
|
+
require 'faraday/request/url_encoded' # needed by multipart
|
29
28
|
|
29
|
+
# External Middleware gems
|
30
|
+
require 'faraday/multipart'
|
31
|
+
require 'faraday/retry'
|
32
|
+
|
33
|
+
# External Adapters gems
|
30
34
|
unless defined?(JRUBY_VERSION)
|
31
35
|
require 'faraday/em_http'
|
32
36
|
require 'faraday/em_synchrony'
|
@@ -30,13 +30,11 @@ RSpec.describe Faraday::Request::Instrumentation do
|
|
30
30
|
|
31
31
|
it { expect(options.name).to eq('request.faraday') }
|
32
32
|
it 'defaults to ActiveSupport::Notifications' do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
expect(res).to eq(ActiveSupport::Notifications)
|
39
|
-
end
|
33
|
+
res = options.instrumenter
|
34
|
+
rescue NameError => e
|
35
|
+
expect(e.to_s).to match('ActiveSupport')
|
36
|
+
else
|
37
|
+
expect(res).to eq(ActiveSupport::Notifications)
|
40
38
|
end
|
41
39
|
|
42
40
|
it 'instruments with default name' do
|
@@ -41,8 +41,7 @@ class WebmockRackApp
|
|
41
41
|
|
42
42
|
def req_headers(env)
|
43
43
|
http_headers = env.select { |k, _| k.start_with?('HTTP_') }
|
44
|
-
.
|
45
|
-
.to_h
|
44
|
+
.transform_keys { |k| k[5..] }
|
46
45
|
|
47
46
|
special_headers = Faraday::Adapter::Rack::SPECIAL_HEADERS
|
48
47
|
http_headers.merge(env.select { |k, _| special_headers.include?(k) })
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faraday
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- "@technoweenie"
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2022-01-06 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: faraday-em_http
|
@@ -58,18 +58,18 @@ dependencies:
|
|
58
58
|
name: faraday-httpclient
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
|
-
- - "
|
61
|
+
- - "<"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
63
|
+
version: '3'
|
64
64
|
type: :runtime
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
|
-
- - "
|
68
|
+
- - "<"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
70
|
+
version: '3'
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
|
-
name: faraday-
|
72
|
+
name: faraday-multipart
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
75
|
- - "~>"
|
@@ -82,34 +82,48 @@ dependencies:
|
|
82
82
|
- - "~>"
|
83
83
|
- !ruby/object:Gem::Version
|
84
84
|
version: '1.0'
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: faraday-net_http
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "<"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '3'
|
92
|
+
type: :runtime
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "<"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '3'
|
85
99
|
- !ruby/object:Gem::Dependency
|
86
100
|
name: faraday-net_http_persistent
|
87
101
|
requirement: !ruby/object:Gem::Requirement
|
88
102
|
requirements:
|
89
|
-
- - "
|
103
|
+
- - "<"
|
90
104
|
- !ruby/object:Gem::Version
|
91
|
-
version: '
|
105
|
+
version: '3'
|
92
106
|
type: :runtime
|
93
107
|
prerelease: false
|
94
108
|
version_requirements: !ruby/object:Gem::Requirement
|
95
109
|
requirements:
|
96
|
-
- - "
|
110
|
+
- - "<"
|
97
111
|
- !ruby/object:Gem::Version
|
98
|
-
version: '
|
112
|
+
version: '3'
|
99
113
|
- !ruby/object:Gem::Dependency
|
100
114
|
name: faraday-patron
|
101
115
|
requirement: !ruby/object:Gem::Requirement
|
102
116
|
requirements:
|
103
|
-
- - "
|
117
|
+
- - "<"
|
104
118
|
- !ruby/object:Gem::Version
|
105
|
-
version: '
|
119
|
+
version: '3'
|
106
120
|
type: :runtime
|
107
121
|
prerelease: false
|
108
122
|
version_requirements: !ruby/object:Gem::Requirement
|
109
123
|
requirements:
|
110
|
-
- - "
|
124
|
+
- - "<"
|
111
125
|
- !ruby/object:Gem::Version
|
112
|
-
version: '
|
126
|
+
version: '3'
|
113
127
|
- !ruby/object:Gem::Dependency
|
114
128
|
name: faraday-rack
|
115
129
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,25 +139,19 @@ dependencies:
|
|
125
139
|
- !ruby/object:Gem::Version
|
126
140
|
version: '1.0'
|
127
141
|
- !ruby/object:Gem::Dependency
|
128
|
-
name:
|
142
|
+
name: faraday-retry
|
129
143
|
requirement: !ruby/object:Gem::Requirement
|
130
144
|
requirements:
|
131
|
-
- - "
|
132
|
-
- !ruby/object:Gem::Version
|
133
|
-
version: '1.2'
|
134
|
-
- - "<"
|
145
|
+
- - "~>"
|
135
146
|
- !ruby/object:Gem::Version
|
136
|
-
version: '
|
147
|
+
version: '1.0'
|
137
148
|
type: :runtime
|
138
149
|
prerelease: false
|
139
150
|
version_requirements: !ruby/object:Gem::Requirement
|
140
151
|
requirements:
|
141
|
-
- - "
|
142
|
-
- !ruby/object:Gem::Version
|
143
|
-
version: '1.2'
|
144
|
-
- - "<"
|
152
|
+
- - "~>"
|
145
153
|
- !ruby/object:Gem::Version
|
146
|
-
version: '
|
154
|
+
version: '1.0'
|
147
155
|
- !ruby/object:Gem::Dependency
|
148
156
|
name: ruby2_keywords
|
149
157
|
requirement: !ruby/object:Gem::Requirement
|
@@ -181,7 +189,6 @@ files:
|
|
181
189
|
- lib/faraday/encoders/flat_params_encoder.rb
|
182
190
|
- lib/faraday/encoders/nested_params_encoder.rb
|
183
191
|
- lib/faraday/error.rb
|
184
|
-
- lib/faraday/file_part.rb
|
185
192
|
- lib/faraday/logging/formatter.rb
|
186
193
|
- lib/faraday/methods.rb
|
187
194
|
- lib/faraday/middleware.rb
|
@@ -192,15 +199,12 @@ files:
|
|
192
199
|
- lib/faraday/options/proxy_options.rb
|
193
200
|
- lib/faraday/options/request_options.rb
|
194
201
|
- lib/faraday/options/ssl_options.rb
|
195
|
-
- lib/faraday/param_part.rb
|
196
202
|
- lib/faraday/parameters.rb
|
197
203
|
- lib/faraday/rack_builder.rb
|
198
204
|
- lib/faraday/request.rb
|
199
205
|
- lib/faraday/request/authorization.rb
|
200
206
|
- lib/faraday/request/basic_authentication.rb
|
201
207
|
- lib/faraday/request/instrumentation.rb
|
202
|
-
- lib/faraday/request/multipart.rb
|
203
|
-
- lib/faraday/request/retry.rb
|
204
208
|
- lib/faraday/request/token_authentication.rb
|
205
209
|
- lib/faraday/request/url_encoded.rb
|
206
210
|
- lib/faraday/response.rb
|
@@ -235,8 +239,6 @@ files:
|
|
235
239
|
- spec/faraday/rack_builder_spec.rb
|
236
240
|
- spec/faraday/request/authorization_spec.rb
|
237
241
|
- spec/faraday/request/instrumentation_spec.rb
|
238
|
-
- spec/faraday/request/multipart_spec.rb
|
239
|
-
- spec/faraday/request/retry_spec.rb
|
240
242
|
- spec/faraday/request/url_encoded_spec.rb
|
241
243
|
- spec/faraday/request_spec.rb
|
242
244
|
- spec/faraday/response/logger_spec.rb
|
@@ -260,7 +262,7 @@ licenses:
|
|
260
262
|
- MIT
|
261
263
|
metadata:
|
262
264
|
homepage_uri: https://lostisland.github.io/faraday
|
263
|
-
changelog_uri: https://github.com/lostisland/faraday/releases/tag/v1.
|
265
|
+
changelog_uri: https://github.com/lostisland/faraday/releases/tag/v1.9.0
|
264
266
|
source_code_uri: https://github.com/lostisland/faraday
|
265
267
|
bug_tracker_uri: https://github.com/lostisland/faraday/issues
|
266
268
|
post_install_message:
|
@@ -272,7 +274,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
272
274
|
requirements:
|
273
275
|
- - ">="
|
274
276
|
- !ruby/object:Gem::Version
|
275
|
-
version: '2.
|
277
|
+
version: '2.6'
|
276
278
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
277
279
|
requirements:
|
278
280
|
- - ">="
|
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,106 +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?(::Faraday::Request::Multipart::DEFAULT_BOUNDARY_PREFIX)
|
12
|
-
DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(app = nil, options = {})
|
16
|
-
super(app)
|
17
|
-
@options = options
|
18
|
-
end
|
19
|
-
|
20
|
-
# Checks for files in the payload, otherwise leaves everything untouched.
|
21
|
-
#
|
22
|
-
# @param env [Faraday::Env]
|
23
|
-
def call(env)
|
24
|
-
match_content_type(env) do |params|
|
25
|
-
env.request.boundary ||= unique_boundary
|
26
|
-
env.request_headers[CONTENT_TYPE] +=
|
27
|
-
"; boundary=#{env.request.boundary}"
|
28
|
-
env.body = create_multipart(env, params)
|
29
|
-
end
|
30
|
-
@app.call env
|
31
|
-
end
|
32
|
-
|
33
|
-
# @param env [Faraday::Env]
|
34
|
-
def process_request?(env)
|
35
|
-
type = request_type(env)
|
36
|
-
env.body.respond_to?(:each_key) && !env.body.empty? && (
|
37
|
-
(type.empty? && has_multipart?(env.body)) ||
|
38
|
-
(type == self.class.mime_type)
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Returns true if obj is an enumerable with values that are multipart.
|
43
|
-
#
|
44
|
-
# @param obj [Object]
|
45
|
-
# @return [Boolean]
|
46
|
-
def has_multipart?(obj) # rubocop:disable Naming/PredicateName
|
47
|
-
if obj.respond_to?(:each)
|
48
|
-
(obj.respond_to?(:values) ? obj.values : obj).each do |val|
|
49
|
-
return true if val.respond_to?(:content_type) || has_multipart?(val)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
false
|
53
|
-
end
|
54
|
-
|
55
|
-
# @param env [Faraday::Env]
|
56
|
-
# @param params [Hash]
|
57
|
-
def create_multipart(env, params)
|
58
|
-
boundary = env.request.boundary
|
59
|
-
parts = process_params(params) do |key, value|
|
60
|
-
part(boundary, key, value)
|
61
|
-
end
|
62
|
-
parts << Faraday::Parts::EpiloguePart.new(boundary)
|
63
|
-
|
64
|
-
body = Faraday::CompositeReadIO.new(parts)
|
65
|
-
env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
|
66
|
-
body
|
67
|
-
end
|
68
|
-
|
69
|
-
def part(boundary, key, value)
|
70
|
-
if value.respond_to?(:to_part)
|
71
|
-
value.to_part(boundary, key)
|
72
|
-
else
|
73
|
-
Faraday::Parts::Part.new(boundary, key, value)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# @return [String]
|
78
|
-
def unique_boundary
|
79
|
-
"#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
|
80
|
-
end
|
81
|
-
|
82
|
-
# @param params [Hash]
|
83
|
-
# @param prefix [String]
|
84
|
-
# @param pieces [Array]
|
85
|
-
def process_params(params, prefix = nil, pieces = nil, &block)
|
86
|
-
params.inject(pieces || []) do |all, (key, value)|
|
87
|
-
if prefix
|
88
|
-
key = @options[:flat_encode] ? prefix.to_s : "#{prefix}[#{key}]"
|
89
|
-
end
|
90
|
-
|
91
|
-
case value
|
92
|
-
when Array
|
93
|
-
values = value.inject([]) { |a, v| a << [nil, v] }
|
94
|
-
process_params(values, key, all, &block)
|
95
|
-
when Hash
|
96
|
-
process_params(value, key, all, &block)
|
97
|
-
else
|
98
|
-
# rubocop:disable Performance/RedundantBlockCall
|
99
|
-
all << block.call(key, value)
|
100
|
-
# rubocop:enable Performance/RedundantBlockCall
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
@@ -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.125.
|
25
|
-
class Retry < Faraday::Middleware
|
26
|
-
DEFAULT_EXCEPTIONS = [
|
27
|
-
Errno::ETIMEDOUT, 'Timeout::Error',
|
28
|
-
Faraday::TimeoutError, Faraday::RetriableResponse
|
29
|
-
].freeze
|
30
|
-
IDEMPOTENT_METHODS = %i[delete get head options put].freeze
|
31
|
-
|
32
|
-
# Options contains the configurable parameters for the Retry middleware.
|
33
|
-
class Options < Faraday::Options.new(:max, :interval, :max_interval,
|
34
|
-
:interval_randomness,
|
35
|
-
:backoff_factor, :exceptions,
|
36
|
-
:methods, :retry_if, :retry_block,
|
37
|
-
:retry_statuses)
|
38
|
-
|
39
|
-
DEFAULT_CHECK = ->(_env, _exception) { false }
|
40
|
-
|
41
|
-
def self.from(value)
|
42
|
-
if value.is_a?(Integer)
|
43
|
-
new(value)
|
44
|
-
else
|
45
|
-
super(value)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def max
|
50
|
-
(self[:max] ||= 2).to_i
|
51
|
-
end
|
52
|
-
|
53
|
-
def interval
|
54
|
-
(self[:interval] ||= 0).to_f
|
55
|
-
end
|
56
|
-
|
57
|
-
def max_interval
|
58
|
-
(self[:max_interval] ||= Float::MAX).to_f
|
59
|
-
end
|
60
|
-
|
61
|
-
def interval_randomness
|
62
|
-
(self[:interval_randomness] ||= 0).to_f
|
63
|
-
end
|
64
|
-
|
65
|
-
def backoff_factor
|
66
|
-
(self[:backoff_factor] ||= 1).to_f
|
67
|
-
end
|
68
|
-
|
69
|
-
def exceptions
|
70
|
-
Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
|
71
|
-
end
|
72
|
-
|
73
|
-
def methods
|
74
|
-
Array(self[:methods] ||= IDEMPOTENT_METHODS)
|
75
|
-
end
|
76
|
-
|
77
|
-
def retry_if
|
78
|
-
self[:retry_if] ||= DEFAULT_CHECK
|
79
|
-
end
|
80
|
-
|
81
|
-
def retry_block
|
82
|
-
self[:retry_block] ||= proc {}
|
83
|
-
end
|
84
|
-
|
85
|
-
def retry_statuses
|
86
|
-
Array(self[:retry_statuses] ||= [])
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# @param app [#call]
|
91
|
-
# @param options [Hash]
|
92
|
-
# @option options [Integer] :max (2) Maximum number of retries
|
93
|
-
# @option options [Integer] :interval (0) Pause in seconds between retries
|
94
|
-
# @option options [Integer] :interval_randomness (0) The maximum random
|
95
|
-
# interval amount expressed as a float between
|
96
|
-
# 0 and 1 to use in addition to the interval.
|
97
|
-
# @option options [Integer] :max_interval (Float::MAX) An upper limit
|
98
|
-
# for the interval
|
99
|
-
# @option options [Integer] :backoff_factor (1) The amount to multiply
|
100
|
-
# each successive retry's interval amount by in order to provide backoff
|
101
|
-
# @option options [Array] :exceptions ([ Errno::ETIMEDOUT,
|
102
|
-
# 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse])
|
103
|
-
# The list of exceptions to handle. Exceptions can be given as
|
104
|
-
# Class, Module, or String.
|
105
|
-
# @option options [Array] :methods (the idempotent HTTP methods
|
106
|
-
# in IDEMPOTENT_METHODS) A list of HTTP methods to retry without
|
107
|
-
# calling retry_if. Pass an empty Array to call retry_if
|
108
|
-
# for all exceptions.
|
109
|
-
# @option options [Block] :retry_if (false) block that will receive
|
110
|
-
# the env object and the exception raised
|
111
|
-
# and should decide if the code should retry still the action or
|
112
|
-
# not independent of the retry count. This would be useful
|
113
|
-
# if the exception produced is non-recoverable or if the
|
114
|
-
# the HTTP method called is not idempotent.
|
115
|
-
# @option options [Block] :retry_block block that is executed before
|
116
|
-
# every retry. Request environment, middleware options, current number
|
117
|
-
# of retries and the exception is passed to the block as parameters.
|
118
|
-
# @option options [Array] :retry_statuses Array of Integer HTTP status
|
119
|
-
# codes or a single Integer value that determines whether to raise
|
120
|
-
# a Faraday::RetriableResponse exception based on the HTTP status code
|
121
|
-
# of an HTTP response.
|
122
|
-
def initialize(app, options = nil)
|
123
|
-
super(app)
|
124
|
-
@options = Options.from(options)
|
125
|
-
@errmatch = build_exception_matcher(@options.exceptions)
|
126
|
-
end
|
127
|
-
|
128
|
-
def calculate_sleep_amount(retries, env)
|
129
|
-
retry_after = calculate_retry_after(env)
|
130
|
-
retry_interval = calculate_retry_interval(retries)
|
131
|
-
|
132
|
-
return if retry_after && retry_after > @options.max_interval
|
133
|
-
|
134
|
-
if retry_after && retry_after >= retry_interval
|
135
|
-
retry_after
|
136
|
-
else
|
137
|
-
retry_interval
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
# @param env [Faraday::Env]
|
142
|
-
def call(env)
|
143
|
-
retries = @options.max
|
144
|
-
request_body = env[:body]
|
145
|
-
begin
|
146
|
-
# after failure env[:body] is set to the response body
|
147
|
-
env[:body] = request_body
|
148
|
-
@app.call(env).tap do |resp|
|
149
|
-
if @options.retry_statuses.include?(resp.status)
|
150
|
-
raise Faraday::RetriableResponse.new(nil, resp)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
rescue @errmatch => e
|
154
|
-
if retries.positive? && retry_request?(env, e)
|
155
|
-
retries -= 1
|
156
|
-
rewind_files(request_body)
|
157
|
-
@options.retry_block.call(env, @options, retries, e)
|
158
|
-
if (sleep_amount = calculate_sleep_amount(retries + 1, env))
|
159
|
-
sleep sleep_amount
|
160
|
-
retry
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
raise unless e.is_a?(Faraday::RetriableResponse)
|
165
|
-
|
166
|
-
e.response
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# An exception matcher for the rescue clause can usually be any object
|
171
|
-
# that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
|
172
|
-
#
|
173
|
-
# @param exceptions [Array]
|
174
|
-
# @api private
|
175
|
-
# @return [Module] an exception matcher
|
176
|
-
def build_exception_matcher(exceptions)
|
177
|
-
matcher = Module.new
|
178
|
-
(
|
179
|
-
class << matcher
|
180
|
-
self
|
181
|
-
end).class_eval do
|
182
|
-
define_method(:===) do |error|
|
183
|
-
exceptions.any? do |ex|
|
184
|
-
if ex.is_a? Module
|
185
|
-
error.is_a? ex
|
186
|
-
else
|
187
|
-
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,302 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Faraday::Request::Multipart do
|
4
|
-
let(:options) { {} }
|
5
|
-
let(:conn) do
|
6
|
-
Faraday.new do |b|
|
7
|
-
b.request :multipart, options
|
8
|
-
b.request :url_encoded
|
9
|
-
b.adapter :test do |stub|
|
10
|
-
stub.post('/echo') do |env|
|
11
|
-
posted_as = env[:request_headers]['Content-Type']
|
12
|
-
expect(env[:body]).to be_a_kind_of(Faraday::CompositeReadIO)
|
13
|
-
[200, { 'Content-Type' => posted_as }, env[:body].read]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
shared_examples 'a multipart request' do
|
20
|
-
it 'generates a unique boundary for each request' do
|
21
|
-
response1 = conn.post('/echo', payload)
|
22
|
-
response2 = conn.post('/echo', payload)
|
23
|
-
|
24
|
-
b1 = parse_multipart_boundary(response1.headers['Content-Type'])
|
25
|
-
b2 = parse_multipart_boundary(response2.headers['Content-Type'])
|
26
|
-
expect(b1).to_not eq(b2)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
context 'FilePart: when multipart objects in param' do
|
31
|
-
let(:payload) do
|
32
|
-
{
|
33
|
-
a: 1,
|
34
|
-
b: {
|
35
|
-
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
|
36
|
-
'Content-Disposition' => 'form-data; foo=1'),
|
37
|
-
d: 2
|
38
|
-
}
|
39
|
-
}
|
40
|
-
end
|
41
|
-
it_behaves_like 'a multipart request'
|
42
|
-
|
43
|
-
it 'forms a multipart request' do
|
44
|
-
response = conn.post('/echo', payload)
|
45
|
-
|
46
|
-
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
47
|
-
result = parse_multipart(boundary, response.body)
|
48
|
-
expect(result[:errors]).to be_empty
|
49
|
-
|
50
|
-
part_a, body_a = result.part('a')
|
51
|
-
expect(part_a).to_not be_nil
|
52
|
-
expect(part_a.filename).to be_nil
|
53
|
-
expect(body_a).to eq('1')
|
54
|
-
|
55
|
-
part_bc, body_bc = result.part('b[c]')
|
56
|
-
expect(part_bc).to_not be_nil
|
57
|
-
expect(part_bc.filename).to eq('multipart_spec.rb')
|
58
|
-
expect(part_bc.headers['content-disposition'])
|
59
|
-
.to eq(
|
60
|
-
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
61
|
-
)
|
62
|
-
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
63
|
-
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
64
|
-
expect(body_bc).to eq(File.read(__FILE__))
|
65
|
-
|
66
|
-
part_bd, body_bd = result.part('b[d]')
|
67
|
-
expect(part_bd).to_not be_nil
|
68
|
-
expect(part_bd.filename).to be_nil
|
69
|
-
expect(body_bd).to eq('2')
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
context 'FilePart: when providing json and IO content in the same payload' do
|
74
|
-
let(:io) { StringIO.new('io-content') }
|
75
|
-
let(:json) do
|
76
|
-
{
|
77
|
-
b: 1,
|
78
|
-
c: 2
|
79
|
-
}.to_json
|
80
|
-
end
|
81
|
-
|
82
|
-
let(:payload) do
|
83
|
-
{
|
84
|
-
json: Faraday::ParamPart.new(json, 'application/json'),
|
85
|
-
io: Faraday::FilePart.new(io, 'application/pdf')
|
86
|
-
}
|
87
|
-
end
|
88
|
-
|
89
|
-
it_behaves_like 'a multipart request'
|
90
|
-
|
91
|
-
it 'forms a multipart request' do
|
92
|
-
response = conn.post('/echo', payload)
|
93
|
-
|
94
|
-
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
95
|
-
result = parse_multipart(boundary, response.body)
|
96
|
-
expect(result[:errors]).to be_empty
|
97
|
-
|
98
|
-
part_json, body_json = result.part('json')
|
99
|
-
expect(part_json).to_not be_nil
|
100
|
-
expect(part_json.mime).to eq('application/json')
|
101
|
-
expect(part_json.filename).to be_nil
|
102
|
-
expect(body_json).to eq(json)
|
103
|
-
|
104
|
-
part_io, body_io = result.part('io')
|
105
|
-
expect(part_io).to_not be_nil
|
106
|
-
expect(part_io.mime).to eq('application/pdf')
|
107
|
-
expect(part_io.filename).to eq('local.path')
|
108
|
-
expect(body_io).to eq(io.string)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
context 'FilePart: when multipart objects in array param' do
|
113
|
-
let(:payload) do
|
114
|
-
{
|
115
|
-
a: 1,
|
116
|
-
b: [{
|
117
|
-
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
|
118
|
-
d: 2
|
119
|
-
}]
|
120
|
-
}
|
121
|
-
end
|
122
|
-
|
123
|
-
it_behaves_like 'a multipart request'
|
124
|
-
|
125
|
-
it 'forms a multipart request' do
|
126
|
-
response = conn.post('/echo', payload)
|
127
|
-
|
128
|
-
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
129
|
-
result = parse_multipart(boundary, response.body)
|
130
|
-
expect(result[:errors]).to be_empty
|
131
|
-
|
132
|
-
part_a, body_a = result.part('a')
|
133
|
-
expect(part_a).to_not be_nil
|
134
|
-
expect(part_a.filename).to be_nil
|
135
|
-
expect(body_a).to eq('1')
|
136
|
-
|
137
|
-
part_bc, body_bc = result.part('b[][c]')
|
138
|
-
expect(part_bc).to_not be_nil
|
139
|
-
expect(part_bc.filename).to eq('multipart_spec.rb')
|
140
|
-
expect(part_bc.headers['content-disposition'])
|
141
|
-
.to eq(
|
142
|
-
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
143
|
-
)
|
144
|
-
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
145
|
-
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
146
|
-
expect(body_bc).to eq(File.read(__FILE__))
|
147
|
-
|
148
|
-
part_bd, body_bd = result.part('b[][d]')
|
149
|
-
expect(part_bd).to_not be_nil
|
150
|
-
expect(part_bd.filename).to be_nil
|
151
|
-
expect(body_bd).to eq('2')
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
context 'UploadIO: when multipart objects in param' do
|
156
|
-
let(:payload) do
|
157
|
-
{
|
158
|
-
a: 1,
|
159
|
-
b: {
|
160
|
-
c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby', nil,
|
161
|
-
'Content-Disposition' => 'form-data; foo=1'),
|
162
|
-
d: 2
|
163
|
-
}
|
164
|
-
}
|
165
|
-
end
|
166
|
-
it_behaves_like 'a multipart request'
|
167
|
-
|
168
|
-
it 'forms a multipart request' do
|
169
|
-
response = conn.post('/echo', payload)
|
170
|
-
|
171
|
-
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
172
|
-
result = parse_multipart(boundary, response.body)
|
173
|
-
expect(result[:errors]).to be_empty
|
174
|
-
|
175
|
-
part_a, body_a = result.part('a')
|
176
|
-
expect(part_a).to_not be_nil
|
177
|
-
expect(part_a.filename).to be_nil
|
178
|
-
expect(body_a).to eq('1')
|
179
|
-
|
180
|
-
part_bc, body_bc = result.part('b[c]')
|
181
|
-
expect(part_bc).to_not be_nil
|
182
|
-
expect(part_bc.filename).to eq('multipart_spec.rb')
|
183
|
-
expect(part_bc.headers['content-disposition'])
|
184
|
-
.to eq(
|
185
|
-
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
186
|
-
)
|
187
|
-
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
188
|
-
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
189
|
-
expect(body_bc).to eq(File.read(__FILE__))
|
190
|
-
|
191
|
-
part_bd, body_bd = result.part('b[d]')
|
192
|
-
expect(part_bd).to_not be_nil
|
193
|
-
expect(part_bd.filename).to be_nil
|
194
|
-
expect(body_bd).to eq('2')
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
context 'UploadIO: when providing json and IO content in the same payload' do
|
199
|
-
let(:io) { StringIO.new('io-content') }
|
200
|
-
let(:json) do
|
201
|
-
{
|
202
|
-
b: 1,
|
203
|
-
c: 2
|
204
|
-
}.to_json
|
205
|
-
end
|
206
|
-
|
207
|
-
let(:payload) do
|
208
|
-
{
|
209
|
-
json: Faraday::ParamPart.new(json, 'application/json'),
|
210
|
-
io: Faraday::UploadIO.new(io, 'application/pdf')
|
211
|
-
}
|
212
|
-
end
|
213
|
-
|
214
|
-
it_behaves_like 'a multipart request'
|
215
|
-
|
216
|
-
it 'forms a multipart request' do
|
217
|
-
response = conn.post('/echo', payload)
|
218
|
-
|
219
|
-
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
220
|
-
result = parse_multipart(boundary, response.body)
|
221
|
-
expect(result[:errors]).to be_empty
|
222
|
-
|
223
|
-
part_json, body_json = result.part('json')
|
224
|
-
expect(part_json).to_not be_nil
|
225
|
-
expect(part_json.mime).to eq('application/json')
|
226
|
-
expect(part_json.filename).to be_nil
|
227
|
-
expect(body_json).to eq(json)
|
228
|
-
|
229
|
-
part_io, body_io = result.part('io')
|
230
|
-
expect(part_io).to_not be_nil
|
231
|
-
expect(part_io.mime).to eq('application/pdf')
|
232
|
-
expect(part_io.filename).to eq('local.path')
|
233
|
-
expect(body_io).to eq(io.string)
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
context 'UploadIO: when multipart objects in array param' do
|
238
|
-
let(:payload) do
|
239
|
-
{
|
240
|
-
a: 1,
|
241
|
-
b: [{
|
242
|
-
c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby'),
|
243
|
-
d: 2
|
244
|
-
}]
|
245
|
-
}
|
246
|
-
end
|
247
|
-
|
248
|
-
it_behaves_like 'a multipart request'
|
249
|
-
|
250
|
-
it 'forms a multipart request' do
|
251
|
-
response = conn.post('/echo', payload)
|
252
|
-
|
253
|
-
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
254
|
-
result = parse_multipart(boundary, response.body)
|
255
|
-
expect(result[:errors]).to be_empty
|
256
|
-
|
257
|
-
part_a, body_a = result.part('a')
|
258
|
-
expect(part_a).to_not be_nil
|
259
|
-
expect(part_a.filename).to be_nil
|
260
|
-
expect(body_a).to eq('1')
|
261
|
-
|
262
|
-
part_bc, body_bc = result.part('b[][c]')
|
263
|
-
expect(part_bc).to_not be_nil
|
264
|
-
expect(part_bc.filename).to eq('multipart_spec.rb')
|
265
|
-
expect(part_bc.headers['content-disposition'])
|
266
|
-
.to eq(
|
267
|
-
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
268
|
-
)
|
269
|
-
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
270
|
-
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
271
|
-
expect(body_bc).to eq(File.read(__FILE__))
|
272
|
-
|
273
|
-
part_bd, body_bd = result.part('b[][d]')
|
274
|
-
expect(part_bd).to_not be_nil
|
275
|
-
expect(part_bd.filename).to be_nil
|
276
|
-
expect(body_bd).to eq('2')
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
context 'when passing flat_encode=true option' do
|
281
|
-
let(:options) { { flat_encode: true } }
|
282
|
-
let(:io) { StringIO.new('io-content') }
|
283
|
-
let(:payload) do
|
284
|
-
{
|
285
|
-
a: 1,
|
286
|
-
b: [
|
287
|
-
Faraday::UploadIO.new(io, 'application/pdf'),
|
288
|
-
Faraday::UploadIO.new(io, 'application/pdf')
|
289
|
-
]
|
290
|
-
}
|
291
|
-
end
|
292
|
-
|
293
|
-
it_behaves_like 'a multipart request'
|
294
|
-
|
295
|
-
it 'encode params using flat encoder' do
|
296
|
-
response = conn.post('/echo', payload)
|
297
|
-
|
298
|
-
expect(response.body).to include('name="b"')
|
299
|
-
expect(response.body).not_to include('name="b[]"')
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
@@ -1,242 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Faraday::Request::Retry do
|
4
|
-
let(:calls) { [] }
|
5
|
-
let(:times_called) { calls.size }
|
6
|
-
let(:options) { [] }
|
7
|
-
let(:conn) do
|
8
|
-
Faraday.new do |b|
|
9
|
-
b.request :retry, *options
|
10
|
-
|
11
|
-
b.adapter :test do |stub|
|
12
|
-
%w[get post].each do |method|
|
13
|
-
stub.send(method, '/unstable') do |env|
|
14
|
-
calls << env.dup
|
15
|
-
env[:body] = nil # simulate blanking out response body
|
16
|
-
callback.call
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
context 'when an unexpected error happens' do
|
24
|
-
let(:callback) { -> { raise 'boom!' } }
|
25
|
-
|
26
|
-
before { expect { conn.get('/unstable') }.to raise_error(RuntimeError) }
|
27
|
-
|
28
|
-
it { expect(times_called).to eq(1) }
|
29
|
-
|
30
|
-
context 'and this is passed as a custom exception' do
|
31
|
-
let(:options) { [{ exceptions: StandardError }] }
|
32
|
-
|
33
|
-
it { expect(times_called).to eq(3) }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
context 'when an expected error happens' do
|
38
|
-
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
39
|
-
|
40
|
-
before do
|
41
|
-
@started = Time.now
|
42
|
-
expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
43
|
-
end
|
44
|
-
|
45
|
-
it { expect(times_called).to eq(3) }
|
46
|
-
|
47
|
-
context 'and legacy max_retry set to 1' do
|
48
|
-
let(:options) { [1] }
|
49
|
-
|
50
|
-
it { expect(times_called).to eq(2) }
|
51
|
-
end
|
52
|
-
|
53
|
-
context 'and legacy max_retry set to -9' do
|
54
|
-
let(:options) { [-9] }
|
55
|
-
|
56
|
-
it { expect(times_called).to eq(1) }
|
57
|
-
end
|
58
|
-
|
59
|
-
context 'and new max_retry set to 3' do
|
60
|
-
let(:options) { [{ max: 3 }] }
|
61
|
-
|
62
|
-
it { expect(times_called).to eq(4) }
|
63
|
-
end
|
64
|
-
|
65
|
-
context 'and new max_retry set to -9' do
|
66
|
-
let(:options) { [{ max: -9 }] }
|
67
|
-
|
68
|
-
it { expect(times_called).to eq(1) }
|
69
|
-
end
|
70
|
-
|
71
|
-
context 'and both max_retry and interval are set' do
|
72
|
-
let(:options) { [{ max: 2, interval: 0.1 }] }
|
73
|
-
|
74
|
-
it { expect(Time.now - @started).to be_within(0.04).of(0.2) }
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
context 'when no exception raised' do
|
79
|
-
let(:options) { [{ max: 1, retry_statuses: 429 }] }
|
80
|
-
|
81
|
-
before { conn.get('/unstable') }
|
82
|
-
|
83
|
-
context 'and response code is in retry_statuses' do
|
84
|
-
let(:callback) { -> { [429, {}, ''] } }
|
85
|
-
|
86
|
-
it { expect(times_called).to eq(2) }
|
87
|
-
end
|
88
|
-
|
89
|
-
context 'and response code is not in retry_statuses' do
|
90
|
-
let(:callback) { -> { [503, {}, ''] } }
|
91
|
-
|
92
|
-
it { expect(times_called).to eq(1) }
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
describe '#calculate_retry_interval' do
|
97
|
-
context 'with exponential backoff' do
|
98
|
-
let(:options) { { max: 5, interval: 0.1, backoff_factor: 2 } }
|
99
|
-
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
100
|
-
|
101
|
-
it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
|
102
|
-
it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
|
103
|
-
it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.4) }
|
104
|
-
end
|
105
|
-
|
106
|
-
context 'with exponential backoff and max_interval' do
|
107
|
-
let(:options) { { max: 5, interval: 0.1, backoff_factor: 2, max_interval: 0.3 } }
|
108
|
-
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
109
|
-
|
110
|
-
it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
|
111
|
-
it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
|
112
|
-
it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.3) }
|
113
|
-
it { expect(middleware.send(:calculate_retry_interval, 2)).to eq(0.3) }
|
114
|
-
end
|
115
|
-
|
116
|
-
context 'with exponential backoff and interval_randomness' do
|
117
|
-
let(:options) { { max: 2, interval: 0.1, interval_randomness: 0.05 } }
|
118
|
-
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
119
|
-
|
120
|
-
it { expect(middleware.send(:calculate_retry_interval, 2)).to be_between(0.1, 0.105) }
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
context 'when method is not idempotent' do
|
125
|
-
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
126
|
-
|
127
|
-
before { expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT) }
|
128
|
-
|
129
|
-
it { expect(times_called).to eq(1) }
|
130
|
-
end
|
131
|
-
|
132
|
-
describe 'retry_if option' do
|
133
|
-
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
134
|
-
let(:options) { [{ retry_if: @check }] }
|
135
|
-
|
136
|
-
it 'retries if retry_if block always returns true' do
|
137
|
-
body = { foo: :bar }
|
138
|
-
@check = ->(_, _) { true }
|
139
|
-
expect { conn.post('/unstable', body) }.to raise_error(Errno::ETIMEDOUT)
|
140
|
-
expect(times_called).to eq(3)
|
141
|
-
expect(calls.all? { |env| env[:body] == body }).to be_truthy
|
142
|
-
end
|
143
|
-
|
144
|
-
it 'does not retry if retry_if block returns false checking env' do
|
145
|
-
@check = ->(env, _) { env[:method] != :post }
|
146
|
-
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
147
|
-
expect(times_called).to eq(1)
|
148
|
-
end
|
149
|
-
|
150
|
-
it 'does not retry if retry_if block returns false checking exception' do
|
151
|
-
@check = ->(_, exception) { !exception.is_a?(Errno::ETIMEDOUT) }
|
152
|
-
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
153
|
-
expect(times_called).to eq(1)
|
154
|
-
end
|
155
|
-
|
156
|
-
it 'FilePart: should rewind files on retry' do
|
157
|
-
io = StringIO.new('Test data')
|
158
|
-
filepart = Faraday::FilePart.new(io, 'application/octet/stream')
|
159
|
-
|
160
|
-
rewound = 0
|
161
|
-
rewind = -> { rewound += 1 }
|
162
|
-
|
163
|
-
@check = ->(_, _) { true }
|
164
|
-
allow(filepart).to receive(:rewind, &rewind)
|
165
|
-
expect { conn.post('/unstable', file: filepart) }.to raise_error(Errno::ETIMEDOUT)
|
166
|
-
expect(times_called).to eq(3)
|
167
|
-
expect(rewound).to eq(2)
|
168
|
-
end
|
169
|
-
|
170
|
-
it 'UploadIO: should rewind files on retry' do
|
171
|
-
io = StringIO.new('Test data')
|
172
|
-
upload_io = Faraday::UploadIO.new(io, 'application/octet/stream')
|
173
|
-
|
174
|
-
rewound = 0
|
175
|
-
rewind = -> { rewound += 1 }
|
176
|
-
|
177
|
-
@check = ->(_, _) { true }
|
178
|
-
allow(upload_io).to receive(:rewind, &rewind)
|
179
|
-
expect { conn.post('/unstable', file: upload_io) }.to raise_error(Errno::ETIMEDOUT)
|
180
|
-
expect(times_called).to eq(3)
|
181
|
-
expect(rewound).to eq(2)
|
182
|
-
end
|
183
|
-
|
184
|
-
context 'when explicitly specifying methods to retry' do
|
185
|
-
let(:options) { [{ retry_if: @check, methods: [:post] }] }
|
186
|
-
|
187
|
-
it 'does not call retry_if for specified methods' do
|
188
|
-
@check = ->(_, _) { raise 'this should have never been called' }
|
189
|
-
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
190
|
-
expect(times_called).to eq(3)
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
context 'with empty list of methods to retry' do
|
195
|
-
let(:options) { [{ retry_if: @check, methods: [] }] }
|
196
|
-
|
197
|
-
it 'calls retry_if for all methods' do
|
198
|
-
@check = ->(_, _) { calls.size < 2 }
|
199
|
-
expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
200
|
-
expect(times_called).to eq(2)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
describe 'retry_after header support' do
|
206
|
-
let(:callback) { -> { [504, headers, ''] } }
|
207
|
-
let(:elapsed) { Time.now - @started }
|
208
|
-
|
209
|
-
before do
|
210
|
-
@started = Time.now
|
211
|
-
conn.get('/unstable')
|
212
|
-
end
|
213
|
-
|
214
|
-
context 'when retry_after bigger than interval' do
|
215
|
-
let(:headers) { { 'Retry-After' => '0.5' } }
|
216
|
-
let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
|
217
|
-
|
218
|
-
it { expect(elapsed).to be > 0.5 }
|
219
|
-
end
|
220
|
-
|
221
|
-
context 'when retry_after smaller than interval' do
|
222
|
-
let(:headers) { { 'Retry-After' => '0.1' } }
|
223
|
-
let(:options) { [{ max: 1, interval: 0.2, retry_statuses: 504 }] }
|
224
|
-
|
225
|
-
it { expect(elapsed).to be > 0.2 }
|
226
|
-
end
|
227
|
-
|
228
|
-
context 'when retry_after is a timestamp' do
|
229
|
-
let(:headers) { { 'Retry-After' => (Time.now.utc + 2).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
|
230
|
-
let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
|
231
|
-
|
232
|
-
it { expect(elapsed).to be > 1 }
|
233
|
-
end
|
234
|
-
|
235
|
-
context 'when retry_after is bigger than max_interval' do
|
236
|
-
let(:headers) { { 'Retry-After' => (Time.now.utc + 20).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
|
237
|
-
let(:options) { [{ max: 2, interval: 0.1, max_interval: 5, retry_statuses: 504 }] }
|
238
|
-
|
239
|
-
it { expect(times_called).to eq(1) }
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|