http 5.3.1 → 6.0.1
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/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/http.rbs +1619 -0
- metadata +36 -171
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGELOG.md +0 -67
- data/CHANGES_OLD.md +0 -1002
- data/CONTRIBUTING.md +0 -26
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/SECURITY.md +0 -17
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/capture_warning.rb +0 -10
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fakeio.rb +0 -21
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/simplecov.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
RSpec.describe HTTP::Response::Parser do
|
|
4
|
-
subject(:parser) { described_class.new }
|
|
5
|
-
let(:raw_response) do
|
|
6
|
-
"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: application/json\r\nMyHeader: val\r\nEmptyHeader: \r\n\r\n{}"
|
|
7
|
-
end
|
|
8
|
-
let(:expected_headers) do
|
|
9
|
-
{
|
|
10
|
-
"Content-Length" => "2",
|
|
11
|
-
"Content-Type" => "application/json",
|
|
12
|
-
"MyHeader" => "val",
|
|
13
|
-
"EmptyHeader" => ""
|
|
14
|
-
}
|
|
15
|
-
end
|
|
16
|
-
let(:expected_body) { "{}" }
|
|
17
|
-
|
|
18
|
-
before do
|
|
19
|
-
parts.each { |part| subject.add(part) }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
context "whole response in one part" do
|
|
23
|
-
let(:parts) { [raw_response] }
|
|
24
|
-
|
|
25
|
-
it "parses headers" do
|
|
26
|
-
expect(subject.headers.to_h).to eq(expected_headers)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "parses body" do
|
|
30
|
-
expect(subject.read(expected_body.size)).to eq(expected_body)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
context "response in many parts" do
|
|
35
|
-
let(:parts) { raw_response.chars }
|
|
36
|
-
|
|
37
|
-
it "parses headers" do
|
|
38
|
-
expect(subject.headers.to_h).to eq(expected_headers)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it "parses body" do
|
|
42
|
-
expect(subject.read(expected_body.size)).to eq(expected_body)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
context "when got 100 Continue response" do
|
|
47
|
-
let :raw_response do
|
|
48
|
-
"HTTP/1.1 100 Continue\r\n\r\n" \
|
|
49
|
-
"HTTP/1.1 200 OK\r\n" \
|
|
50
|
-
"Content-Length: 12\r\n\r\n" \
|
|
51
|
-
"Hello World!"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
context "when response is feeded in one part" do
|
|
55
|
-
let(:parts) { [raw_response] }
|
|
56
|
-
|
|
57
|
-
it "skips to next non-info response" do
|
|
58
|
-
expect(subject.status_code).to eq(200)
|
|
59
|
-
expect(subject.headers).to eq("Content-Length" => "12")
|
|
60
|
-
expect(subject.read(12)).to eq("Hello World!")
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
context "when response is feeded in many parts" do
|
|
65
|
-
let(:parts) { raw_response.chars }
|
|
66
|
-
|
|
67
|
-
it "skips to next non-info response" do
|
|
68
|
-
expect(subject.status_code).to eq(200)
|
|
69
|
-
expect(subject.headers).to eq("Content-Length" => "12")
|
|
70
|
-
expect(subject.read(12)).to eq("Hello World!")
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
RSpec.describe HTTP::Response::Status do
|
|
4
|
-
describe ".new" do
|
|
5
|
-
it "fails if given value does not respond to #to_i" do
|
|
6
|
-
expect { described_class.new double }.to raise_error TypeError
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
it "accepts any object that responds to #to_i" do
|
|
10
|
-
expect { described_class.new double :to_i => 200 }.to_not raise_error
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
describe "#code" do
|
|
15
|
-
subject { described_class.new("200.0").code }
|
|
16
|
-
it { is_expected.to eq 200 }
|
|
17
|
-
it { is_expected.to be_a Integer }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
describe "#reason" do
|
|
21
|
-
subject { described_class.new(code).reason }
|
|
22
|
-
|
|
23
|
-
context "with unknown code" do
|
|
24
|
-
let(:code) { 1024 }
|
|
25
|
-
it { is_expected.to be_nil }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
described_class::REASONS.each do |code, reason|
|
|
29
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
30
|
-
context 'with well-known code: #{code}' do
|
|
31
|
-
let(:code) { #{code} }
|
|
32
|
-
it { is_expected.to eq #{reason.inspect} }
|
|
33
|
-
it { is_expected.to be_frozen }
|
|
34
|
-
end
|
|
35
|
-
RUBY
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
context "with 1xx codes" do
|
|
40
|
-
subject { (100...200).map { |code| described_class.new code } }
|
|
41
|
-
|
|
42
|
-
it "is #informational?" do
|
|
43
|
-
expect(subject).to all(satisfy(&:informational?))
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it "is not #success?" do
|
|
47
|
-
expect(subject).to all(satisfy { |status| !status.success? })
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
it "is not #redirect?" do
|
|
51
|
-
expect(subject).to all(satisfy { |status| !status.redirect? })
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "is not #client_error?" do
|
|
55
|
-
expect(subject).to all(satisfy { |status| !status.client_error? })
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
it "is not #server_error?" do
|
|
59
|
-
expect(subject).to all(satisfy { |status| !status.server_error? })
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
context "with 2xx codes" do
|
|
64
|
-
subject { (200...300).map { |code| described_class.new code } }
|
|
65
|
-
|
|
66
|
-
it "is not #informational?" do
|
|
67
|
-
expect(subject).to all(satisfy { |status| !status.informational? })
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
it "is #success?" do
|
|
71
|
-
expect(subject).to all(satisfy(&:success?))
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
it "is not #redirect?" do
|
|
75
|
-
expect(subject).to all(satisfy { |status| !status.redirect? })
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "is not #client_error?" do
|
|
79
|
-
expect(subject).to all(satisfy { |status| !status.client_error? })
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it "is not #server_error?" do
|
|
83
|
-
expect(subject).to all(satisfy { |status| !status.server_error? })
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
context "with 3xx codes" do
|
|
88
|
-
subject { (300...400).map { |code| described_class.new code } }
|
|
89
|
-
|
|
90
|
-
it "is not #informational?" do
|
|
91
|
-
expect(subject).to all(satisfy { |status| !status.informational? })
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
it "is not #success?" do
|
|
95
|
-
expect(subject).to all(satisfy { |status| !status.success? })
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
it "is #redirect?" do
|
|
99
|
-
expect(subject).to all(satisfy(&:redirect?))
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
it "is not #client_error?" do
|
|
103
|
-
expect(subject).to all(satisfy { |status| !status.client_error? })
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
it "is not #server_error?" do
|
|
107
|
-
expect(subject).to all(satisfy { |status| !status.server_error? })
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
context "with 4xx codes" do
|
|
112
|
-
subject { (400...500).map { |code| described_class.new code } }
|
|
113
|
-
|
|
114
|
-
it "is not #informational?" do
|
|
115
|
-
expect(subject).to all(satisfy { |status| !status.informational? })
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
it "is not #success?" do
|
|
119
|
-
expect(subject).to all(satisfy { |status| !status.success? })
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
it "is not #redirect?" do
|
|
123
|
-
expect(subject).to all(satisfy { |status| !status.redirect? })
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it "is #client_error?" do
|
|
127
|
-
expect(subject).to all(satisfy(&:client_error?))
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it "is not #server_error?" do
|
|
131
|
-
expect(subject).to all(satisfy { |status| !status.server_error? })
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
context "with 5xx codes" do
|
|
136
|
-
subject { (500...600).map { |code| described_class.new code } }
|
|
137
|
-
|
|
138
|
-
it "is not #informational?" do
|
|
139
|
-
expect(subject).to all(satisfy { |status| !status.informational? })
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
it "is not #success?" do
|
|
143
|
-
expect(subject).to all(satisfy { |status| !status.success? })
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
it "is not #redirect?" do
|
|
147
|
-
expect(subject).to all(satisfy { |status| !status.redirect? })
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
it "is not #client_error?" do
|
|
151
|
-
expect(subject).to all(satisfy { |status| !status.client_error? })
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
it "is #server_error?" do
|
|
155
|
-
expect(subject).to all(satisfy(&:server_error?))
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
describe "#to_sym" do
|
|
160
|
-
subject { described_class.new(code).to_sym }
|
|
161
|
-
|
|
162
|
-
context "with unknown code" do
|
|
163
|
-
let(:code) { 1024 }
|
|
164
|
-
it { is_expected.to be_nil }
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
described_class::SYMBOLS.each do |code, symbol|
|
|
168
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
169
|
-
context 'with well-known code: #{code}' do
|
|
170
|
-
let(:code) { #{code} }
|
|
171
|
-
it { is_expected.to be #{symbol.inspect} }
|
|
172
|
-
end
|
|
173
|
-
RUBY
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
describe "#inspect" do
|
|
178
|
-
it "returns quoted code and reason phrase" do
|
|
179
|
-
status = described_class.new 200
|
|
180
|
-
expect(status.inspect).to eq "#<HTTP::Response::Status 200 OK>"
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# testing edge cases only
|
|
185
|
-
describe "::SYMBOLS" do
|
|
186
|
-
subject { described_class::SYMBOLS }
|
|
187
|
-
|
|
188
|
-
# "OK"
|
|
189
|
-
its([200]) { is_expected.to be :ok }
|
|
190
|
-
|
|
191
|
-
# "Bad Request"
|
|
192
|
-
its([400]) { is_expected.to be :bad_request }
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
described_class::SYMBOLS.each do |code, symbol|
|
|
196
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
197
|
-
describe '##{symbol}?' do
|
|
198
|
-
subject { status.#{symbol}? }
|
|
199
|
-
|
|
200
|
-
context 'when code is #{code}' do
|
|
201
|
-
let(:status) { described_class.new #{code} }
|
|
202
|
-
it { is_expected.to be true }
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
context 'when code is higher than #{code}' do
|
|
206
|
-
let(:status) { described_class.new #{code + 1} }
|
|
207
|
-
it { is_expected.to be false }
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
context 'when code is lower than #{code}' do
|
|
211
|
-
let(:status) { described_class.new #{code - 1} }
|
|
212
|
-
it { is_expected.to be false }
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
RUBY
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
describe ".coerce" do
|
|
219
|
-
context "with String" do
|
|
220
|
-
it "coerces reasons" do
|
|
221
|
-
expect(described_class.coerce("Bad request")).to eq described_class.new 400
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
it "fails when reason is unknown" do
|
|
225
|
-
expect { described_class.coerce "foobar" }.to raise_error HTTP::Error
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
context "with Symbol" do
|
|
230
|
-
it "coerces symbolized reasons" do
|
|
231
|
-
expect(described_class.coerce(:bad_request)).to eq described_class.new 400
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
it "fails when symbolized reason is unknown" do
|
|
235
|
-
expect { described_class.coerce(:foobar) }.to raise_error HTTP::Error
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
context "with Numeric" do
|
|
240
|
-
it "coerces as Fixnum code" do
|
|
241
|
-
expect(described_class.coerce(200.1)).to eq described_class.new 200
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
it "fails if coercion failed" do
|
|
246
|
-
expect { described_class.coerce(true) }.to raise_error HTTP::Error
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
it "is aliased as `.[]`" do
|
|
250
|
-
expect(described_class.method(:coerce)).to eq described_class.method :[]
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
end
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
RSpec.describe HTTP::Response do
|
|
4
|
-
let(:body) { "Hello world!" }
|
|
5
|
-
let(:uri) { "http://example.com/" }
|
|
6
|
-
let(:headers) { {} }
|
|
7
|
-
let(:request) { HTTP::Request.new(:verb => :get, :uri => uri) }
|
|
8
|
-
|
|
9
|
-
subject(:response) do
|
|
10
|
-
HTTP::Response.new(
|
|
11
|
-
:status => 200,
|
|
12
|
-
:version => "1.1",
|
|
13
|
-
:headers => headers,
|
|
14
|
-
:body => body,
|
|
15
|
-
:request => request
|
|
16
|
-
)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "includes HTTP::Headers::Mixin" do
|
|
20
|
-
expect(described_class).to include HTTP::Headers::Mixin
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
describe "to_a" do
|
|
24
|
-
let(:body) { "Hello world" }
|
|
25
|
-
let(:content_type) { "text/plain" }
|
|
26
|
-
let(:headers) { {"Content-Type" => content_type} }
|
|
27
|
-
|
|
28
|
-
it "returns a Rack-like array" do
|
|
29
|
-
expect(subject.to_a).to eq([200, headers, body])
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
describe "#content_length" do
|
|
34
|
-
subject { response.content_length }
|
|
35
|
-
|
|
36
|
-
context "without Content-Length header" do
|
|
37
|
-
it { is_expected.to be_nil }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
context "with Content-Length: 5" do
|
|
41
|
-
let(:headers) { {"Content-Length" => "5"} }
|
|
42
|
-
it { is_expected.to eq 5 }
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
context "with invalid Content-Length" do
|
|
46
|
-
let(:headers) { {"Content-Length" => "foo"} }
|
|
47
|
-
it { is_expected.to be_nil }
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
describe "mime_type" do
|
|
52
|
-
subject { response.mime_type }
|
|
53
|
-
|
|
54
|
-
context "without Content-Type header" do
|
|
55
|
-
let(:headers) { {} }
|
|
56
|
-
it { is_expected.to be_nil }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
context "with Content-Type: text/html" do
|
|
60
|
-
let(:headers) { {"Content-Type" => "text/html"} }
|
|
61
|
-
it { is_expected.to eq "text/html" }
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
context "with Content-Type: text/html; charset=utf-8" do
|
|
65
|
-
let(:headers) { {"Content-Type" => "text/html; charset=utf-8"} }
|
|
66
|
-
it { is_expected.to eq "text/html" }
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
describe "charset" do
|
|
71
|
-
subject { response.charset }
|
|
72
|
-
|
|
73
|
-
context "without Content-Type header" do
|
|
74
|
-
let(:headers) { {} }
|
|
75
|
-
it { is_expected.to be_nil }
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
context "with Content-Type: text/html" do
|
|
79
|
-
let(:headers) { {"Content-Type" => "text/html"} }
|
|
80
|
-
it { is_expected.to be_nil }
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
context "with Content-Type: text/html; charset=utf-8" do
|
|
84
|
-
let(:headers) { {"Content-Type" => "text/html; charset=utf-8"} }
|
|
85
|
-
it { is_expected.to eq "utf-8" }
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
describe "#parse" do
|
|
90
|
-
let(:headers) { {"Content-Type" => content_type} }
|
|
91
|
-
let(:body) { '{"foo":"bar"}' }
|
|
92
|
-
|
|
93
|
-
context "with known content type" do
|
|
94
|
-
let(:content_type) { "application/json" }
|
|
95
|
-
it "returns parsed body" do
|
|
96
|
-
expect(response.parse).to eq "foo" => "bar"
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
context "with unknown content type" do
|
|
101
|
-
let(:content_type) { "application/deadbeef" }
|
|
102
|
-
it "raises HTTP::Error" do
|
|
103
|
-
expect { response.parse }.to raise_error HTTP::Error
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
context "with explicitly given mime type" do
|
|
108
|
-
let(:content_type) { "application/deadbeef" }
|
|
109
|
-
it "ignores mime_type of response" do
|
|
110
|
-
expect(response.parse("application/json")).to eq "foo" => "bar"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
it "supports mime type aliases" do
|
|
114
|
-
expect(response.parse(:json)).to eq "foo" => "bar"
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
describe "#flush" do
|
|
120
|
-
let(:body) { double :to_s => "" }
|
|
121
|
-
|
|
122
|
-
it "returns response self-reference" do
|
|
123
|
-
expect(response.flush).to be response
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it "flushes body" do
|
|
127
|
-
expect(body).to receive :to_s
|
|
128
|
-
response.flush
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
describe "#inspect" do
|
|
133
|
-
subject { response.inspect }
|
|
134
|
-
|
|
135
|
-
let(:headers) { {:content_type => "text/plain"} }
|
|
136
|
-
let(:body) { double :to_s => "foobar" }
|
|
137
|
-
|
|
138
|
-
it { is_expected.to eq '#<HTTP::Response/1.1 200 OK {"Content-Type"=>"text/plain"}>' }
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
describe "#cookies" do
|
|
142
|
-
let(:cookies) { ["a=1", "b=2; domain=example.com", "c=3; domain=bad.org"] }
|
|
143
|
-
let(:headers) { {"Set-Cookie" => cookies} }
|
|
144
|
-
|
|
145
|
-
subject(:jar) { response.cookies }
|
|
146
|
-
|
|
147
|
-
it { is_expected.to be_an HTTP::CookieJar }
|
|
148
|
-
|
|
149
|
-
it "contains cookies without domain restriction" do
|
|
150
|
-
expect(jar.count { |c| "a" == c.name }).to eq 1
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
it "contains cookies limited to domain of request uri" do
|
|
154
|
-
expect(jar.count { |c| "b" == c.name }).to eq 1
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
it "does not contains cookies limited to non-requeted uri" do
|
|
158
|
-
expect(jar.count { |c| "c" == c.name }).to eq 0
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
describe "#connection" do
|
|
163
|
-
let(:connection) { double }
|
|
164
|
-
|
|
165
|
-
subject(:response) do
|
|
166
|
-
HTTP::Response.new(
|
|
167
|
-
:version => "1.1",
|
|
168
|
-
:status => 200,
|
|
169
|
-
:connection => connection,
|
|
170
|
-
:request => request
|
|
171
|
-
)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
it "returns the connection object used to instantiate the response" do
|
|
175
|
-
expect(response.connection).to eq connection
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
describe "#chunked?" do
|
|
180
|
-
subject { response }
|
|
181
|
-
context "when encoding is set to chunked" do
|
|
182
|
-
let(:headers) { {"Transfer-Encoding" => "chunked"} }
|
|
183
|
-
it { is_expected.to be_chunked }
|
|
184
|
-
end
|
|
185
|
-
it { is_expected.not_to be_chunked }
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
describe "backwards compatibilty with :uri" do
|
|
189
|
-
context "with no :verb" do
|
|
190
|
-
subject(:response) do
|
|
191
|
-
HTTP::Response.new(
|
|
192
|
-
:status => 200,
|
|
193
|
-
:version => "1.1",
|
|
194
|
-
:headers => headers,
|
|
195
|
-
:body => body,
|
|
196
|
-
:uri => uri
|
|
197
|
-
)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
it "defaults the uri to :uri" do
|
|
201
|
-
expect(response.request.uri.to_s).to eq uri
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
it "defaults to the verb to :get" do
|
|
205
|
-
expect(response.request.verb).to eq :get
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
context "with both a :request and :uri" do
|
|
210
|
-
subject(:response) do
|
|
211
|
-
HTTP::Response.new(
|
|
212
|
-
:status => 200,
|
|
213
|
-
:version => "1.1",
|
|
214
|
-
:headers => headers,
|
|
215
|
-
:body => body,
|
|
216
|
-
:uri => uri,
|
|
217
|
-
:request => request
|
|
218
|
-
)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
it "raises ArgumentError" do
|
|
222
|
-
expect { response }.to raise_error(ArgumentError)
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
describe "#body" do
|
|
228
|
-
let(:connection) { double(:sequence_id => 0) }
|
|
229
|
-
let(:chunks) { ["Hello, ", "World!"] }
|
|
230
|
-
|
|
231
|
-
subject(:response) do
|
|
232
|
-
HTTP::Response.new(
|
|
233
|
-
:status => 200,
|
|
234
|
-
:version => "1.1",
|
|
235
|
-
:headers => headers,
|
|
236
|
-
:request => request,
|
|
237
|
-
:connection => connection
|
|
238
|
-
)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
before do
|
|
242
|
-
allow(connection).to receive(:readpartial) { chunks.shift }
|
|
243
|
-
allow(connection).to receive(:body_completed?) { chunks.empty? }
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
context "with no Content-Type" do
|
|
247
|
-
let(:headers) { {} }
|
|
248
|
-
|
|
249
|
-
it "returns a body with default binary encoding" do
|
|
250
|
-
expect(response.body.to_s.encoding).to eq Encoding::BINARY
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
context "with Content-Type: application/json" do
|
|
255
|
-
let(:headers) { {"Content-Type" => "application/json"} }
|
|
256
|
-
|
|
257
|
-
it "returns a body with a default UTF_8 encoding" do
|
|
258
|
-
expect(response.body.to_s.encoding).to eq Encoding::UTF_8
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
end
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
RSpec.describe HTTP::Retriable::DelayCalculator do
|
|
4
|
-
let(:response) do
|
|
5
|
-
HTTP::Response.new(
|
|
6
|
-
status: 200,
|
|
7
|
-
version: "1.1",
|
|
8
|
-
headers: {},
|
|
9
|
-
body: "Hello world!",
|
|
10
|
-
request: HTTP::Request.new(verb: :get, uri: "http://example.com")
|
|
11
|
-
)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def call_delay(iterations, **options)
|
|
15
|
-
described_class.new(options).call(iterations, response)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def call_retry_header(value, **options)
|
|
19
|
-
response.headers["Retry-After"] = value
|
|
20
|
-
described_class.new(options).call(rand(1...100), response)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "prevents negative sleep time" do
|
|
24
|
-
expect(call_delay(20, delay: -20)).to eq 0
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
it "backs off exponentially" do
|
|
28
|
-
expect(call_delay(1)).to be_between 0, 1
|
|
29
|
-
expect(call_delay(2)).to be_between 1, 2
|
|
30
|
-
expect(call_delay(3)).to be_between 3, 4
|
|
31
|
-
expect(call_delay(4)).to be_between 7, 8
|
|
32
|
-
expect(call_delay(5)).to be_between 15, 16
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "can have a maximum wait time" do
|
|
36
|
-
expect(call_delay(1, max_delay: 5)).to be_between 0, 1
|
|
37
|
-
expect(call_delay(5, max_delay: 5)).to eq 5
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it "respects Retry-After headers as integer" do
|
|
41
|
-
delay_time = rand(6...2500)
|
|
42
|
-
header_value = delay_time.to_s
|
|
43
|
-
expect(call_retry_header(header_value)).to eq delay_time
|
|
44
|
-
expect(call_retry_header(header_value, max_delay: 5)).to eq 5
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "respects Retry-After headers as rfc2822 timestamp" do
|
|
48
|
-
delay_time = rand(6...2500)
|
|
49
|
-
header_value = (Time.now.gmtime + delay_time).to_datetime.rfc2822.sub("+0000", "GMT")
|
|
50
|
-
expect(call_retry_header(header_value)).to be_within(1).of(delay_time)
|
|
51
|
-
expect(call_retry_header(header_value, max_delay: 5)).to eq 5
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "respects Retry-After headers as rfc2822 timestamp in the past" do
|
|
55
|
-
delay_time = rand(6...2500)
|
|
56
|
-
header_value = (Time.now.gmtime - delay_time).to_datetime.rfc2822.sub("+0000", "GMT")
|
|
57
|
-
expect(call_retry_header(header_value)).to eq 0
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it "does not error on invalid Retry-After header" do
|
|
61
|
-
[ # invalid strings
|
|
62
|
-
"This is a string with a number 5 in it",
|
|
63
|
-
"8 Eight is the first digit in this string",
|
|
64
|
-
"This is a string with a #{Time.now.gmtime.to_datetime.rfc2822} timestamp in it"
|
|
65
|
-
].each do |header_value|
|
|
66
|
-
expect(call_retry_header(header_value)).to eq 0
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|