http.rb 0.21.0 → 0.22.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 +86 -36
- data/Rakefile +4 -4
- data/http.rb.gemspec +8 -7
- data/lib/HTTP/VERSION.rb +1 -1
- data/lib/HTTP/request.rb +1 -1
- data/test/HTTP/RETRY_test.rb +281 -0
- data/test/HTTP/delete_test.rb +265 -0
- data/test/HTTP/get_test.rb +304 -0
- data/{spec/HTTP/head_spec.rb → test/HTTP/head_test.rb} +27 -24
- data/test/HTTP/options_test.rb +246 -0
- data/test/HTTP/patch_test.rb +408 -0
- data/test/HTTP/post_test.rb +448 -0
- data/test/HTTP/put_test.rb +408 -0
- data/test/HTTP/trace_test.rb +246 -0
- data/{spec/spec_helper.rb → test/helper.rb} +15 -2
- metadata +28 -14
- data/spec/HTTP/RETRY_spec.rb +0 -251
- data/spec/HTTP/delete_spec.rb +0 -268
- data/spec/HTTP/get_spec.rb +0 -295
- data/spec/HTTP/options_spec.rb +0 -247
- data/spec/HTTP/patch_spec.rb +0 -406
- data/spec/HTTP/post_spec.rb +0 -447
- data/spec/HTTP/put_spec.rb +0 -406
- data/spec/HTTP/trace_spec.rb +0 -247
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5535114ce1c4c4612aaef5e7eb88777a4807ab671d2f474d38175e3398d37358
|
|
4
|
+
data.tar.gz: e72a5ce3ea83170a4bafc2d6179912d20ff88c8d945d9f8d06f40dae387b6961
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf30ba6e2171e8a005b398cbb467f8437d0653b15f066f610e87bf5d301464695bcd751dbf4b064ded581761c1c2abad25e71418d24d2c4a3001706f1e41f8dc
|
|
7
|
+
data.tar.gz: f62e33d69f7e107c43d717f33bd9130538f0b4ff96a8f337145dfe11fb3ee3dac25ee83fec6777d301fa0eadeb0282760057a58da4736cd2adee0eba47c6552d
|
data/CHANGELOG
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
## 20260522
|
|
4
|
+
|
|
5
|
+
0.22.0: Convert specs from RSpec to Minitest.
|
|
6
|
+
|
|
7
|
+
1. ~ spec/ → test/: All spec files moved to test/ and rewritten in Minitest spec style with `let`, double-quoted descriptions, and `_(...).must_*` expectations. On the TODO since at least 0.17.0; completed before 1.0 to ship into stability on the test framework that's here to stay.
|
|
8
|
+
2. ~ spec/spec_helper.rb → test/helper.rb: Rewrite for Minitest; preserve the webmock http_rb_adapter collision workaround. + MockResponse Struct used as a header-stand-in in HTTP.retry_after specs in place of instance_double(Net::HTTPResponse).
|
|
9
|
+
3. ~ Rakefile: Use Rake::TestTask instead of RSpec::Core::RakeTask.
|
|
10
|
+
4. ~ http.rb.gemspec: -rspec; +minitest; +minitest-mock (minitest 6 extracted Mock/stub into a separate gem); files glob /spec/test/.
|
|
11
|
+
5. ~ HTTP.request: /response.header['location']/response['location']/ — Net::HTTPResponse#header has been deprecated for years; the documented replacement is #[]. Surfaced by Minitest's default reporter showing stderr warnings inline where RSpec was hiding them.
|
|
12
|
+
6. ~ TODO: Annotate "Convert specs from RSpec to Minitest" as (Done as of 0.22.0).
|
|
13
|
+
7. ~ HTTP::VERSION: /0.21.0/0.22.0/
|
|
14
|
+
8. ~ CHANGELOG: + 0.22.0 entry
|
|
15
|
+
9. ~ CHANGELOG: Reformat all entries with Markdown-style headings — `## YYYYMMDD` date headings, version line no longer `#`-prefixed.
|
|
16
|
+
10. ~ http.rb.gemspec: Pin minitest to `~> 6.0` to lock the major and avoid bundler quietly resolving to 5.x off a stale lockfile.
|
|
17
|
+
|
|
18
|
+
## 20260522
|
|
19
|
+
|
|
20
|
+
0.21.0: Add specs for options, trace, and patch verbs.
|
|
21
|
+
|
|
5
22
|
1. + spec/HTTP/options_spec.rb: Specs for HTTP.options. These were owed since 0.18.0 when the verb was added via the metaprogramming refactor.
|
|
6
23
|
2. + spec/HTTP/trace_spec.rb: Specs for HTTP.trace. Owed since 0.18.0.
|
|
7
24
|
3. + spec/HTTP/patch_spec.rb: Specs for HTTP.patch. Owed since 0.18.0.
|
|
8
25
|
4. ~ HTTP::VERSION: /0.20.0/0.21.0/
|
|
9
26
|
5. ~ CHANGELOG: + 0.21.0 entry
|
|
10
27
|
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
## 20260522
|
|
29
|
+
|
|
30
|
+
0.20.0: Add opt-in retry logic with exponential backoff.
|
|
31
|
+
|
|
13
32
|
1. + lib/HTTP/RETRY.rb: Retry helpers (with_retries, backoff_delay, retry_after) and constants (HTTP::RETRY::EXCEPTIONS, STATUS_CODES, VERBS).
|
|
14
33
|
2. ~ HTTP.request: Retry on transient network exceptions and retry-worthy HTTP status codes (429, 502, 503, 504) when enabled. Exponential backoff with jitter. Honours Retry-After when present. Disabled by default; opt in via the retries option. Configurable via new options: retries (default 0), retry_delay (default 1.0), retry_status_codes, retry_exceptions, retry_verbs. Only idempotent verbs (get, head, options, put, delete, trace) retry by default; opt in to POST/PATCH retries via retry_verbs.
|
|
15
34
|
3. + spec/HTTP/RETRY_spec.rb: Specs for retry behaviour and the helpers.
|
|
@@ -18,8 +37,10 @@
|
|
|
18
37
|
6. ~ HTTP::VERSION: /0.19.0/0.20.0/
|
|
19
38
|
7. ~ CHANGELOG: + 0.20.0 entry
|
|
20
39
|
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
## 20260522
|
|
41
|
+
|
|
42
|
+
0.19.0: Change default verify_mode to VERIFY_PEER.
|
|
43
|
+
|
|
23
44
|
1. ~ HTTP.request: Default verify_mode changed from OpenSSL::SSL::VERIFY_NONE to OpenSSL::SSL::VERIFY_PEER. Callers needing the old behaviour can pass verify_mode: OpenSSL::SSL::VERIFY_NONE explicitly through the options hash.
|
|
24
45
|
2. ~ spec/HTTP/get_spec.rb: + specs for default verify_mode and explicit VERIFY_NONE override; /verify_mode: 0/verify_mode: OpenSSL::SSL::VERIFY_PEER/ in redirect specs.
|
|
25
46
|
3. ~ spec/HTTP/post_spec.rb: /verify_mode: 0/verify_mode: OpenSSL::SSL::VERIFY_PEER/ in redirect specs.
|
|
@@ -29,8 +50,10 @@
|
|
|
29
50
|
7. ~ HTTP::VERSION: /0.18.3/0.19.0/
|
|
30
51
|
8. ~ CHANGELOG: + 0.19.0 entry
|
|
31
52
|
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
## 20260521
|
|
54
|
+
|
|
55
|
+
0.18.3: Fix verb preservation on 307/308 redirects.
|
|
56
|
+
|
|
34
57
|
1. ~ HTTP.request: Use original verb when following 307 or 308 redirects, per RFC 7231 §6.4.7 and RFC 7538. 301/302/303 keep legacy GET-on-redirect behaviour.
|
|
35
58
|
2. ~ spec/HTTP/post_spec.rb: + specs for 307/308 verb preservation and 307 body preservation.
|
|
36
59
|
3. ~ spec/HTTP/put_spec.rb: + spec for 307 verb preservation.
|
|
@@ -39,8 +62,10 @@
|
|
|
39
62
|
6. ~ HTTP::VERSION: /0.18.2/0.18.3/
|
|
40
63
|
7. ~ CHANGELOG: + 0.18.3 entry
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
## 20260520
|
|
66
|
+
|
|
67
|
+
0.18.2: Fix relative redirect URL construction.
|
|
68
|
+
|
|
44
69
|
1. ~ HTTP.request: Use URI#merge for redirect URL construction. Preserves original scheme, elides default ports, and resolves relative paths per RFC 3986.
|
|
45
70
|
2. ~ spec/HTTP/get_spec.rb: Update relative-redirect stubs to elide default port; + context for HTTPS relative redirect.
|
|
46
71
|
3. ~ spec/HTTP/post_spec.rb: Update relative-redirect stubs to elide default port.
|
|
@@ -49,8 +74,10 @@
|
|
|
49
74
|
6. ~ HTTP::VERSION: /0.18.1/0.18.2/
|
|
50
75
|
7. ~ CHANGELOG: + 0.18.2 entry
|
|
51
76
|
|
|
52
|
-
|
|
53
|
-
|
|
77
|
+
## 20260508
|
|
78
|
+
|
|
79
|
+
0.18.1: Remove incorrectly added WebDAV verbs.
|
|
80
|
+
|
|
54
81
|
1. - lib/Net/HTTP/Report.rb
|
|
55
82
|
2. ~ HTTP::VERBS: - require_relative '../Net/HTTP/Report'
|
|
56
83
|
3. ~ HTTP::VERBS::WITH_BODY: - propfind, proppatch, mkcol, copy, move, lock, unlock, report
|
|
@@ -59,8 +86,9 @@
|
|
|
59
86
|
6. ~ HTTP::VERSION: /0.18.0/0.18.1/
|
|
60
87
|
7. ~ CHANGELOG: + 0.18.1 entry
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
## 20260507
|
|
90
|
+
0.18.0: Add all missing HTTP verbs; use meta-programming to define verb methods.
|
|
91
|
+
|
|
64
92
|
1. + HTTP/verbs.rb; including:
|
|
65
93
|
+ HTTP::VERBS::WITHOUT_BODY: get, delete, head, options, trace
|
|
66
94
|
+ HTTP::VERBS::WITH_BODY: post, put, patch, propfind, proppatch, mkcol, copy, move, lock, unlock, report
|
|
@@ -87,8 +115,10 @@
|
|
|
87
115
|
22. ~ .gitignore: Using a common one with lots of entries.
|
|
88
116
|
23. ~ README.md: /http.rb/http/; Trimmed the Description; Usage has more code blocks, making the comments Markdown sub-sections.
|
|
89
117
|
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
## 20260325
|
|
119
|
+
|
|
120
|
+
0.17.0: Extract HTTP.request method; consolidate set_headers.
|
|
121
|
+
|
|
92
122
|
1. + HTTP.request: Extract common plumbing (connection, SSL, auth, redirect, response) from verb methods.
|
|
93
123
|
2. ~ HTTP.get: Delegate to HTTP.request.
|
|
94
124
|
3. ~ HTTP.delete: Delegate to HTTP.request.
|
|
@@ -104,8 +134,10 @@
|
|
|
104
134
|
13. ~ CHANGELOG.txt: + 0.17.0 entry
|
|
105
135
|
14. ~ http.rb.gemspec: Change date.
|
|
106
136
|
|
|
107
|
-
|
|
108
|
-
|
|
137
|
+
## 20250908
|
|
138
|
+
|
|
139
|
+
0.16.1: Allow any case for the content-type key.
|
|
140
|
+
|
|
109
141
|
1. ~ HTTP.post: Check for any case for content-type key.
|
|
110
142
|
2. ~ HTTP.put: Check for any case for content-type key.
|
|
111
143
|
3. ~ spec/HTTP/post_spec.rb: + specs for different content-type key cases.
|
|
@@ -115,8 +147,10 @@
|
|
|
115
147
|
7. ~ CHANGELOG.txt: + 0.16.1 entry
|
|
116
148
|
8. ~ http.rb.gemspec: Change date.
|
|
117
149
|
|
|
118
|
-
|
|
119
|
-
|
|
150
|
+
## 20250809
|
|
151
|
+
|
|
152
|
+
0.16.0: Allow passing of a raw payload for POST/PUT.
|
|
153
|
+
|
|
120
154
|
1. ~ HTTP.post: Check for Content-Type and whether a string.
|
|
121
155
|
2. ~ HTTP.put: Check for Content-Type and whether a string.
|
|
122
156
|
3. ~ spec/HTTP/post_spec.rb: + raw form data example
|
|
@@ -127,8 +161,10 @@
|
|
|
127
161
|
8. ~ CHANGELOG.txt: + 0.16.0 entry; Small edit on 0.15.1.
|
|
128
162
|
9. ~ http.rb.gemspec: Change date.
|
|
129
163
|
|
|
130
|
-
|
|
131
|
-
|
|
164
|
+
## 20250721
|
|
165
|
+
|
|
166
|
+
0.15.1: Fix PUT; require delete and put in the load file.
|
|
167
|
+
|
|
132
168
|
1. + require 'HTTP/put'
|
|
133
169
|
2. + require 'HTTP/delete'
|
|
134
170
|
3. ~ HTTP/put.rb: Params in the body!
|
|
@@ -138,8 +174,10 @@
|
|
|
138
174
|
5. ~ CHANGELOG.txt
|
|
139
175
|
6. ~ http.rb.gemspec: Change date.
|
|
140
176
|
|
|
141
|
-
|
|
142
|
-
|
|
177
|
+
## 20250716
|
|
178
|
+
|
|
179
|
+
0.15.0: + PUT
|
|
180
|
+
|
|
143
181
|
1. + HTTP.put
|
|
144
182
|
2. + Net::HTTP::Put#set_headers
|
|
145
183
|
3. + spec/HTTP/put_spec.rb
|
|
@@ -147,8 +185,10 @@
|
|
|
147
185
|
5. ~ CHANGELOG.txt
|
|
148
186
|
6. ~ http.rb.gemspec: Change date.
|
|
149
187
|
|
|
150
|
-
|
|
151
|
-
|
|
188
|
+
## 20250711
|
|
189
|
+
|
|
190
|
+
0.14.0: + DELETE
|
|
191
|
+
|
|
152
192
|
1. + HTTP.delete
|
|
153
193
|
2. + Net::HTTP::Delete#set_headers
|
|
154
194
|
3. + spec/HTTP/delete_spec.rb
|
|
@@ -158,26 +198,34 @@
|
|
|
158
198
|
7. ~ CHANGELOG.txt
|
|
159
199
|
8. ~ http.rb.gemspec: Change date.
|
|
160
200
|
|
|
161
|
-
|
|
162
|
-
|
|
201
|
+
## 20250501
|
|
202
|
+
|
|
203
|
+
0.13.3: Handle when there's a redirect to a URL with arguments, so as to not add an additional '?' at the end.
|
|
204
|
+
|
|
163
205
|
1. ~ HTTP.get: Check if the args hash is empty.
|
|
164
206
|
2. ~ HTTP::VERSION: /0.13.2/0.13.3/
|
|
165
207
|
3. ~ http.rb.gemspec: Change date.
|
|
166
208
|
|
|
167
|
-
|
|
168
|
-
|
|
209
|
+
## 202503030
|
|
210
|
+
|
|
211
|
+
0.13.2: Change repo name to match gem name (/HTTP/http.rb/); + Use HTTP::VERSION; /require/require_relative/
|
|
212
|
+
|
|
169
213
|
1. ~ README.md: /HTTP/http.rb/, /HTTP.rb/http.rb/
|
|
170
214
|
2. + HTTP::VERSION
|
|
171
215
|
3. ~ http.rb.gemspec: Use HTTP::VERSION.
|
|
172
216
|
4. ~ HTTP.get: /require/require_relative/
|
|
173
217
|
5. ~ HTTP.post: /require/require_relative/
|
|
174
218
|
|
|
175
|
-
|
|
176
|
-
|
|
219
|
+
## 20250304
|
|
220
|
+
|
|
221
|
+
0.13.1: /HTTP.rb.gemspec/http.rb.gemspec/
|
|
222
|
+
|
|
177
223
|
1. /HTTP.rb.gemspec/http.rb.gemspec/ (Wonder no more!)
|
|
178
224
|
|
|
179
|
-
|
|
180
|
-
|
|
225
|
+
## 20250304
|
|
226
|
+
|
|
227
|
+
0.13.0: Extend Net::HTTPResponse to allow use of predicate methods for statuses and optionally prevent redirections.
|
|
228
|
+
|
|
181
229
|
1. + lib/Net/HTTPResponse/StatusPredicates.rb
|
|
182
230
|
2. ~ HTTP/get.rb: + require 'Net/HTTPResponse/StatusPredicates'
|
|
183
231
|
3. ~ HTTP/post.rb: + require 'Net/HTTPResponse/StatusPredicates'
|
|
@@ -191,8 +239,10 @@
|
|
|
191
239
|
11. /http.rb.gemspec/HTTP.rb.gemspec/ (I wonder if that's going to cause issues for rubygems.org...)
|
|
192
240
|
12. ~ CHANGELOG.txt
|
|
193
241
|
|
|
194
|
-
|
|
195
|
-
|
|
242
|
+
## 20250207
|
|
243
|
+
|
|
244
|
+
0.12.1: Correctly handle POST'ing JSON data.
|
|
245
|
+
|
|
196
246
|
1. ~ HTTP.post(): Check if the Content-Type is 'application/json' and assign the body JSON data otherwise assign the supplied hash to the form data.
|
|
197
247
|
2. ~ spec/HTTP/post_spec.rb: + spec for when a request is being made with JSON data
|
|
198
248
|
3. ~ spec/HTTP/post_spec.rb: Assign a headers variable to make the spec more readable.
|
data/Rakefile
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Rakefile
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'rake/testtask'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
t.
|
|
5
|
+
Rake::TestTask.new do |t|
|
|
6
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
task default: :
|
|
9
|
+
task default: :test
|
data/http.rb.gemspec
CHANGED
|
@@ -31,13 +31,14 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
'Rakefile',
|
|
32
32
|
'README.md',
|
|
33
33
|
Dir['lib/**/*.rb'],
|
|
34
|
-
Dir['
|
|
34
|
+
Dir['test/**/*.rb'],
|
|
35
35
|
].flatten
|
|
36
36
|
|
|
37
|
-
spec.development_dependencies =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
spec.development_dependencies = [
|
|
38
|
+
['minitest', '~> 6.0'],
|
|
39
|
+
'minitest-mock',
|
|
40
|
+
'pry',
|
|
41
|
+
'rake',
|
|
42
|
+
'webmock',
|
|
43
|
+
]
|
|
43
44
|
end
|
data/lib/HTTP/VERSION.rb
CHANGED
data/lib/HTTP/request.rb
CHANGED
|
@@ -36,7 +36,7 @@ module HTTP
|
|
|
36
36
|
elsif no_redirect
|
|
37
37
|
return response
|
|
38
38
|
end
|
|
39
|
-
redirect_uri = uri.merge(response
|
|
39
|
+
redirect_uri = uri.merge(response['location'])
|
|
40
40
|
if response.code =~ /^30[78]$/
|
|
41
41
|
data = VERBS::WITH_BODY.include?(verb) ? request_object.body : {}
|
|
42
42
|
response = send(verb, redirect_uri.to_s, data, headers, options, &block)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# test/HTTP/RETRY_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../helper'
|
|
4
|
+
|
|
5
|
+
describe "retry behaviour" do
|
|
6
|
+
let(:uri){'http://example.com/path'}
|
|
7
|
+
|
|
8
|
+
describe "defaults" do
|
|
9
|
+
it "does not retry by default" do
|
|
10
|
+
stub_request(:get, uri).to_return(status: 503)
|
|
11
|
+
response = HTTP.get(uri)
|
|
12
|
+
_(response.code.to_i).must_equal(503)
|
|
13
|
+
assert_requested(:get, uri, times: 1)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "does not retry on a transient exception by default" do
|
|
17
|
+
stub_request(:get, uri).to_raise(Errno::ECONNRESET)
|
|
18
|
+
_(->{HTTP.get(uri)}).must_raise(Errno::ECONNRESET)
|
|
19
|
+
assert_requested(:get, uri, times: 1)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "retry on transient exception" do
|
|
24
|
+
it "retries and succeeds when the failure is transient" do
|
|
25
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
26
|
+
stub_request(:get, uri).
|
|
27
|
+
to_raise(Errno::ECONNRESET).then.
|
|
28
|
+
to_raise(Errno::ECONNRESET).then.
|
|
29
|
+
to_return(status: 200, body: '')
|
|
30
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
31
|
+
_(response.success?).must_equal(true)
|
|
32
|
+
assert_requested(:get, uri, times: 3)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "re-raises the exception after retries are exhausted" do
|
|
37
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
38
|
+
stub_request(:get, uri).to_raise(Errno::ECONNRESET)
|
|
39
|
+
_(->{HTTP.get(uri, {}, {}, {retries: 2})}).must_raise(Errno::ECONNRESET)
|
|
40
|
+
assert_requested(:get, uri, times: 3)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "retries on SocketError (DNS failure)" do
|
|
45
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
46
|
+
stub_request(:get, uri).
|
|
47
|
+
to_raise(SocketError).then.
|
|
48
|
+
to_return(status: 200, body: '')
|
|
49
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
50
|
+
_(response.success?).must_equal(true)
|
|
51
|
+
assert_requested(:get, uri, times: 2)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "does not retry on a non-listed exception" do
|
|
56
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
57
|
+
stub_request(:get, uri).to_raise(OpenSSL::SSL::SSLError)
|
|
58
|
+
_(->{HTTP.get(uri, {}, {}, {retries: 3})}).must_raise(OpenSSL::SSL::SSLError)
|
|
59
|
+
assert_requested(:get, uri, times: 1)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "retry on status code" do
|
|
65
|
+
it "retries on 503 then succeeds" do
|
|
66
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
67
|
+
stub_request(:get, uri).
|
|
68
|
+
to_return({status: 503}, {status: 503}, {status: 200, body: ''})
|
|
69
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
70
|
+
_(response.success?).must_equal(true)
|
|
71
|
+
assert_requested(:get, uri, times: 3)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "retries on 502" do
|
|
76
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
77
|
+
stub_request(:get, uri).to_return({status: 502}, {status: 200, body: ''})
|
|
78
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
79
|
+
_(response.success?).must_equal(true)
|
|
80
|
+
assert_requested(:get, uri, times: 2)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "retries on 504" do
|
|
85
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
86
|
+
stub_request(:get, uri).to_return({status: 504}, {status: 200, body: ''})
|
|
87
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
88
|
+
_(response.success?).must_equal(true)
|
|
89
|
+
assert_requested(:get, uri, times: 2)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "does not retry on 500 by default" do
|
|
94
|
+
stub_request(:get, uri).to_return(status: 500)
|
|
95
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
96
|
+
_(response.code.to_i).must_equal(500)
|
|
97
|
+
assert_requested(:get, uri, times: 1)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "does not retry on 404" do
|
|
101
|
+
stub_request(:get, uri).to_return(status: 404)
|
|
102
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
103
|
+
_(response.code.to_i).must_equal(404)
|
|
104
|
+
assert_requested(:get, uri, times: 1)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "returns the last response when retries are exhausted" do
|
|
108
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
109
|
+
stub_request(:get, uri).to_return(status: 503)
|
|
110
|
+
response = HTTP.get(uri, {}, {}, {retries: 2})
|
|
111
|
+
_(response.code.to_i).must_equal(503)
|
|
112
|
+
assert_requested(:get, uri, times: 3)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
describe "Retry-After header" do
|
|
118
|
+
it "honours integer Retry-After on 429" do
|
|
119
|
+
received_seconds = nil
|
|
120
|
+
HTTP::RETRY.stub(:sleep, ->(n){received_seconds = n}) do
|
|
121
|
+
stub_request(:get, uri).
|
|
122
|
+
to_return({status: 429, headers: {'Retry-After' => '2'}}, {status: 200, body: ''})
|
|
123
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
124
|
+
_(response.success?).must_equal(true)
|
|
125
|
+
_(received_seconds).must_equal(2)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "honours integer Retry-After on 503" do
|
|
130
|
+
received_seconds = nil
|
|
131
|
+
HTTP::RETRY.stub(:sleep, ->(n){received_seconds = n}) do
|
|
132
|
+
stub_request(:get, uri).
|
|
133
|
+
to_return({status: 503, headers: {'Retry-After' => '5'}}, {status: 200, body: ''})
|
|
134
|
+
response = HTTP.get(uri, {}, {}, {retries: 3})
|
|
135
|
+
_(response.success?).must_equal(true)
|
|
136
|
+
_(received_seconds).must_equal(5)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe "configuration" do
|
|
142
|
+
it "treats retries: 0 as no retries" do
|
|
143
|
+
stub_request(:get, uri).to_return(status: 503)
|
|
144
|
+
response = HTTP.get(uri, {}, {}, {retries: 0})
|
|
145
|
+
_(response.code.to_i).must_equal(503)
|
|
146
|
+
assert_requested(:get, uri, times: 1)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "respects a custom retry_status_codes list" do
|
|
150
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
151
|
+
stub_request(:get, uri).to_return({status: 500}, {status: 200, body: ''})
|
|
152
|
+
response = HTTP.get(uri, {}, {}, {retries: 3, retry_status_codes: [500]})
|
|
153
|
+
_(response.success?).must_equal(true)
|
|
154
|
+
assert_requested(:get, uri, times: 2)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "respects a custom retry_exceptions list" do
|
|
159
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
160
|
+
stub_request(:get, uri).
|
|
161
|
+
to_raise(OpenSSL::SSL::SSLError).then.
|
|
162
|
+
to_return(status: 200, body: '')
|
|
163
|
+
response = HTTP.get(uri, {}, {}, {retries: 3, retry_exceptions: [OpenSSL::SSL::SSLError]})
|
|
164
|
+
_(response.success?).must_equal(true)
|
|
165
|
+
assert_requested(:get, uri, times: 2)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "does not pass retry options through to Net::HTTP" do
|
|
170
|
+
stub_request(:get, uri).to_return(status: 200, body: '')
|
|
171
|
+
net_http_object = Net::HTTP.new(URI.parse(uri).host, URI.parse(uri).port)
|
|
172
|
+
received_opts = nil
|
|
173
|
+
net_http_object.define_singleton_method(:options=){|opts| received_opts = opts}
|
|
174
|
+
Net::HTTP.stub(:new, net_http_object) do
|
|
175
|
+
HTTP.get(uri, {}, {}, {
|
|
176
|
+
retries: 3,
|
|
177
|
+
retry_delay: 0.1,
|
|
178
|
+
retry_status_codes: [500],
|
|
179
|
+
retry_exceptions: [Errno::ECONNRESET],
|
|
180
|
+
retry_verbs: %i{get}
|
|
181
|
+
})
|
|
182
|
+
end
|
|
183
|
+
_(received_opts).wont_include(:retries)
|
|
184
|
+
_(received_opts).wont_include(:retry_delay)
|
|
185
|
+
_(received_opts).wont_include(:retry_status_codes)
|
|
186
|
+
_(received_opts).wont_include(:retry_exceptions)
|
|
187
|
+
_(received_opts).wont_include(:retry_verbs)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe "backoff timing" do
|
|
192
|
+
it "increases the delay between successive retries" do
|
|
193
|
+
delays = []
|
|
194
|
+
HTTP::RETRY.stub(:sleep, ->(d){delays << d}) do
|
|
195
|
+
stub_request(:get, uri).to_return(status: 503)
|
|
196
|
+
HTTP.get(uri, {}, {}, {retries: 3, retry_delay: 1.0})
|
|
197
|
+
end
|
|
198
|
+
_(delays.length).must_equal(3)
|
|
199
|
+
_(delays[1]).must_be(:>, delays[0] * 0.8)
|
|
200
|
+
_(delays[2]).must_be(:>, delays[1] * 0.8)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
describe "verb-based retry default" do
|
|
205
|
+
it "does not retry POST by default even when retries are enabled" do
|
|
206
|
+
stub_request(:post, uri).to_return(status: 503)
|
|
207
|
+
HTTP.post(uri, {}, {}, {retries: 3})
|
|
208
|
+
assert_requested(:post, uri, times: 1)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "does not retry PATCH by default" do
|
|
212
|
+
stub_request(:patch, uri).to_return(status: 503)
|
|
213
|
+
HTTP.patch(uri, {}, {}, {retries: 3})
|
|
214
|
+
assert_requested(:patch, uri, times: 1)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "retries PUT by default (idempotent)" do
|
|
218
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
219
|
+
stub_request(:put, uri).to_return({status: 503}, {status: 200, body: ''})
|
|
220
|
+
response = HTTP.put(uri, {}, {}, {retries: 3})
|
|
221
|
+
_(response.success?).must_equal(true)
|
|
222
|
+
assert_requested(:put, uri, times: 2)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it "retries DELETE by default (idempotent)" do
|
|
227
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
228
|
+
stub_request(:delete, uri).to_return({status: 503}, {status: 200, body: ''})
|
|
229
|
+
response = HTTP.delete(uri, {}, {}, {retries: 3})
|
|
230
|
+
_(response.success?).must_equal(true)
|
|
231
|
+
assert_requested(:delete, uri, times: 2)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "retries POST when opted in via retry_verbs" do
|
|
236
|
+
HTTP::RETRY.stub(:sleep, nil) do
|
|
237
|
+
stub_request(:post, uri).to_return({status: 503}, {status: 200, body: ''})
|
|
238
|
+
response = HTTP.post(uri, {}, {}, {retries: 3, retry_verbs: %i{get post}})
|
|
239
|
+
_(response.success?).must_equal(true)
|
|
240
|
+
assert_requested(:post, uri, times: 2)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
describe HTTP, ".retry_after" do
|
|
247
|
+
it "returns integer seconds for a delta-seconds Retry-After header" do
|
|
248
|
+
response = MockResponse.new(headers_hash: {'Retry-After' => '5'})
|
|
249
|
+
_(HTTP.retry_after(response)).must_equal(5)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it "parses an HTTP-date Retry-After header" do
|
|
253
|
+
base = Time.utc(2026, 5, 22, 12, 0, 0)
|
|
254
|
+
retry_at_header = (base + 5).httpdate
|
|
255
|
+
response = MockResponse.new(headers_hash: {'Retry-After' => retry_at_header})
|
|
256
|
+
Time.stub(:now, base) do
|
|
257
|
+
_(HTTP.retry_after(response)).must_be_close_to(5.0, 0.001)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it "returns nil when Retry-After is absent" do
|
|
262
|
+
response = MockResponse.new(headers_hash: {})
|
|
263
|
+
_(HTTP.retry_after(response)).must_be_nil
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "returns nil when Retry-After is malformed" do
|
|
267
|
+
response = MockResponse.new(headers_hash: {'Retry-After' => 'not a date'})
|
|
268
|
+
_(HTTP.retry_after(response)).must_be_nil
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
describe HTTP, ".backoff_delay" do
|
|
273
|
+
it "grows exponentially with attempt number" do
|
|
274
|
+
base = 1.0
|
|
275
|
+
delays = (1..4).map{|attempt| HTTP.backoff_delay(base, attempt)}
|
|
276
|
+
_(delays[0]).must_be_close_to(1.0, 0.2)
|
|
277
|
+
_(delays[1]).must_be_close_to(2.0, 0.4)
|
|
278
|
+
_(delays[2]).must_be_close_to(4.0, 0.8)
|
|
279
|
+
_(delays[3]).must_be_close_to(8.0, 1.6)
|
|
280
|
+
end
|
|
281
|
+
end
|