http 5.0.0.pre3 → 5.0.3
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/.github/workflows/ci.yml +65 -0
- data/.gitignore +6 -10
- data/.rspec +0 -4
- data/.rubocop/layout.yml +8 -0
- data/.rubocop/style.yml +32 -0
- data/.rubocop.yml +7 -124
- data/.rubocop_todo.yml +192 -0
- data/CHANGES.md +123 -1
- data/Gemfile +18 -11
- data/LICENSE.txt +1 -1
- data/README.md +47 -87
- data/Rakefile +2 -10
- data/http.gemspec +3 -3
- data/lib/http/chainable.rb +15 -14
- data/lib/http/client.rb +26 -15
- data/lib/http/connection.rb +7 -3
- data/lib/http/content_type.rb +10 -5
- data/lib/http/feature.rb +1 -1
- data/lib/http/features/auto_inflate.rb +0 -2
- data/lib/http/features/instrumentation.rb +1 -1
- data/lib/http/features/logging.rb +19 -21
- data/lib/http/headers.rb +3 -3
- data/lib/http/mime_type/adapter.rb +2 -0
- data/lib/http/options.rb +2 -2
- data/lib/http/redirector.rb +1 -1
- data/lib/http/request/writer.rb +6 -2
- data/lib/http/request.rb +22 -5
- data/lib/http/response/body.rb +5 -4
- data/lib/http/response/inflater.rb +1 -1
- data/lib/http/response/parser.rb +72 -64
- data/lib/http/response/status.rb +2 -2
- data/lib/http/response.rb +24 -6
- data/lib/http/timeout/global.rb +18 -30
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +31 -55
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +109 -41
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -1
- data/spec/lib/http/features/instrumentation_spec.rb +21 -16
- data/spec/lib/http/features/logging_spec.rb +2 -5
- data/spec/lib/http/headers_spec.rb +3 -3
- data/spec/lib/http/redirector_spec.rb +44 -0
- data/spec/lib/http/request/body_spec.rb +3 -3
- data/spec/lib/http/request/writer_spec.rb +12 -1
- data/spec/lib/http/response/body_spec.rb +5 -5
- data/spec/lib/http/response/parser_spec.rb +5 -5
- data/spec/lib/http/response_spec.rb +62 -10
- data/spec/lib/http_spec.rb +20 -2
- data/spec/spec_helper.rb +21 -21
- data/spec/support/black_hole.rb +1 -1
- data/spec/support/dummy_server/servlet.rb +14 -2
- data/spec/support/dummy_server.rb +1 -1
- data/spec/support/fuubar.rb +21 -0
- data/spec/support/simplecov.rb +19 -0
- metadata +23 -18
- data/.coveralls.yml +0 -1
- data/.travis.yml +0 -38
@@ -4,50 +4,55 @@
|
|
4
4
|
require "support/http_handling_shared"
|
5
5
|
require "support/dummy_server"
|
6
6
|
require "support/ssl_helper"
|
7
|
+
require "logger"
|
7
8
|
|
8
9
|
RSpec.describe HTTP::Client do
|
9
10
|
run_server(:dummy) { DummyServer.new }
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def stubs
|
18
|
-
@stubs ||= {}
|
19
|
-
end
|
12
|
+
before do
|
13
|
+
stubbed_client = Class.new(HTTP::Client) do
|
14
|
+
def perform(request, options)
|
15
|
+
stubbed = stubs[HTTP::URI::NORMALIZER.call(request.uri).to_s]
|
16
|
+
stubbed ? stubbed.call(request) : super(request, options)
|
17
|
+
end
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
o[HTTP::URI.parse k] = v
|
19
|
+
def stubs
|
20
|
+
@stubs ||= {}
|
24
21
|
end
|
25
22
|
|
26
|
-
|
23
|
+
def stub(stubs)
|
24
|
+
@stubs = stubs.transform_keys do |k|
|
25
|
+
HTTP::URI::NORMALIZER.call(k).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
27
30
|
end
|
28
|
-
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
def redirect_response(location, status = 302)
|
33
|
+
lambda do |request|
|
34
|
+
HTTP::Response.new(
|
35
|
+
:status => status,
|
36
|
+
:version => "1.1",
|
37
|
+
:headers => {"Location" => location},
|
38
|
+
:body => "",
|
39
|
+
:request => request
|
40
|
+
)
|
41
|
+
end
|
39
42
|
end
|
40
|
-
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
def simple_response(body, status = 200)
|
45
|
+
lambda do |request|
|
46
|
+
HTTP::Response.new(
|
47
|
+
:status => status,
|
48
|
+
:version => "1.1",
|
49
|
+
:body => body,
|
50
|
+
:request => request
|
51
|
+
)
|
52
|
+
end
|
50
53
|
end
|
54
|
+
|
55
|
+
stub_const("StubbedClient", stubbed_client)
|
51
56
|
end
|
52
57
|
|
53
58
|
describe "following redirects" do
|
@@ -105,13 +110,45 @@ RSpec.describe HTTP::Client do
|
|
105
110
|
end
|
106
111
|
|
107
112
|
it "works like a charm in real world" do
|
108
|
-
|
109
|
-
|
110
|
-
expect(client.get(url).to_s).to include "support for non-ascii URIs"
|
113
|
+
expect(HTTP.follow.get("https://bit.ly/2UaBT4R").parse(:json)).
|
114
|
+
to include("url" => "https://httpbin.org/anything/könig")
|
111
115
|
end
|
112
116
|
end
|
113
117
|
end
|
114
118
|
|
119
|
+
describe "following redirects with logging" do
|
120
|
+
let(:logger) do
|
121
|
+
logger = Logger.new(logdev)
|
122
|
+
logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
|
123
|
+
logger.level = Logger::INFO
|
124
|
+
logger
|
125
|
+
end
|
126
|
+
|
127
|
+
let(:logdev) { StringIO.new }
|
128
|
+
|
129
|
+
it "logs all requests" do
|
130
|
+
client = StubbedClient.new(:follow => true, :features => { :logging => { :logger => logger } }).stub(
|
131
|
+
"http://example.com/" => redirect_response("/1"),
|
132
|
+
"http://example.com/1" => redirect_response("/2"),
|
133
|
+
"http://example.com/2" => redirect_response("/3"),
|
134
|
+
"http://example.com/3" => simple_response("OK")
|
135
|
+
)
|
136
|
+
|
137
|
+
expect { client.get("http://example.com/") }.not_to raise_error
|
138
|
+
|
139
|
+
expect(logdev.string).to eq <<~OUTPUT
|
140
|
+
** INFO **
|
141
|
+
> GET http://example.com/
|
142
|
+
** INFO **
|
143
|
+
> GET http://example.com/1
|
144
|
+
** INFO **
|
145
|
+
> GET http://example.com/2
|
146
|
+
** INFO **
|
147
|
+
> GET http://example.com/3
|
148
|
+
OUTPUT
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
115
152
|
describe "parsing params" do
|
116
153
|
let(:client) { HTTP::Client.new }
|
117
154
|
before { allow(client).to receive :perform }
|
@@ -197,6 +234,22 @@ RSpec.describe HTTP::Client do
|
|
197
234
|
|
198
235
|
client.get("http://example.com/", :form => {:foo => HTTP::FormData::Part.new("content")})
|
199
236
|
end
|
237
|
+
|
238
|
+
context "when passing an HTTP::FormData object directly" do
|
239
|
+
it "creates url encoded form data object" do
|
240
|
+
client = HTTP::Client.new
|
241
|
+
form_data = HTTP::FormData::Multipart.new({ :foo => "bar" })
|
242
|
+
|
243
|
+
allow(client).to receive(:perform)
|
244
|
+
|
245
|
+
expect(HTTP::Request).to receive(:new) do |opts|
|
246
|
+
expect(opts[:body]).to be form_data
|
247
|
+
expect(opts[:body].to_s).to match(/^Content-Disposition: form-data; name="foo"\r\n\r\nbar\r\n/m)
|
248
|
+
end
|
249
|
+
|
250
|
+
client.get("http://example.com/", :form => form_data)
|
251
|
+
end
|
252
|
+
end
|
200
253
|
end
|
201
254
|
|
202
255
|
describe "passing json" do
|
@@ -220,9 +273,9 @@ RSpec.describe HTTP::Client do
|
|
220
273
|
end
|
221
274
|
|
222
275
|
it "works like a charm in real world" do
|
223
|
-
url
|
224
|
-
|
225
|
-
expect(
|
276
|
+
url = "https://httpbin.org/anything/ö無"
|
277
|
+
|
278
|
+
expect(HTTP.follow.get(url).parse(:json)).to include("url" => url)
|
226
279
|
end
|
227
280
|
end
|
228
281
|
|
@@ -291,6 +344,7 @@ RSpec.describe HTTP::Client do
|
|
291
344
|
end
|
292
345
|
end
|
293
346
|
end
|
347
|
+
|
294
348
|
it "is given a chance to wrap the Request" do
|
295
349
|
feature_instance = feature_class.new
|
296
350
|
|
@@ -299,7 +353,7 @@ RSpec.describe HTTP::Client do
|
|
299
353
|
|
300
354
|
expect(response.code).to eq(200)
|
301
355
|
expect(feature_instance.captured_request.verb).to eq(:get)
|
302
|
-
expect(feature_instance.captured_request.uri.to_s).to eq(dummy.endpoint
|
356
|
+
expect(feature_instance.captured_request.uri.to_s).to eq("#{dummy.endpoint}/")
|
303
357
|
end
|
304
358
|
|
305
359
|
it "is given a chance to wrap the Response" do
|
@@ -325,6 +379,19 @@ RSpec.describe HTTP::Client do
|
|
325
379
|
expect(feature_instance.captured_request.verb).to eq(:post)
|
326
380
|
expect(feature_instance.captured_request.uri.to_s).to eq(sleep_url)
|
327
381
|
end
|
382
|
+
|
383
|
+
it "is given a chance to handle a connection timeout error" do
|
384
|
+
allow(TCPSocket).to receive(:open) { sleep 1 }
|
385
|
+
sleep_url = "#{dummy.endpoint}/sleep"
|
386
|
+
feature_instance = feature_class.new
|
387
|
+
|
388
|
+
expect do
|
389
|
+
client.use(:test_feature => feature_instance).
|
390
|
+
timeout(0.001).
|
391
|
+
request(:post, sleep_url)
|
392
|
+
end.to raise_error(HTTP::TimeoutError)
|
393
|
+
expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
|
394
|
+
end
|
328
395
|
end
|
329
396
|
end
|
330
397
|
|
@@ -335,7 +402,8 @@ RSpec.describe HTTP::Client do
|
|
335
402
|
let(:client) { described_class.new(options.merge(extra_options)) }
|
336
403
|
end
|
337
404
|
|
338
|
-
|
405
|
+
# TODO: https://github.com/httprb/http/issues/627
|
406
|
+
xdescribe "working with SSL" do
|
339
407
|
run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
|
340
408
|
|
341
409
|
let(:extra_options) { {} }
|
@@ -476,7 +544,7 @@ RSpec.describe HTTP::Client do
|
|
476
544
|
BODY
|
477
545
|
end
|
478
546
|
|
479
|
-
|
547
|
+
xit "raises HTTP::ConnectionError" do
|
480
548
|
expect { client.get(dummy.endpoint).to_s }.to raise_error(HTTP::ConnectionError)
|
481
549
|
end
|
482
550
|
end
|
@@ -74,7 +74,6 @@ RSpec.describe HTTP::Features::AutoInflate do
|
|
74
74
|
:status => 200,
|
75
75
|
:headers => {:content_encoding => "gzip"},
|
76
76
|
:connection => connection,
|
77
|
-
:uri => "https://example.com",
|
78
77
|
:request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
|
79
78
|
)
|
80
79
|
end
|
@@ -2,8 +2,29 @@
|
|
2
2
|
|
3
3
|
RSpec.describe HTTP::Features::Instrumentation do
|
4
4
|
subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) }
|
5
|
+
|
5
6
|
let(:instrumenter) { TestInstrumenter.new }
|
6
7
|
|
8
|
+
before do
|
9
|
+
test_instrumenter = Class.new(HTTP::Features::Instrumentation::NullInstrumenter) do
|
10
|
+
attr_reader :output
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@output = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def start(_name, payload)
|
17
|
+
output[:start] = payload
|
18
|
+
end
|
19
|
+
|
20
|
+
def finish(_name, payload)
|
21
|
+
output[:finish] = payload
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
stub_const("TestInstrumenter", test_instrumenter)
|
26
|
+
end
|
27
|
+
|
7
28
|
describe "logging the request" do
|
8
29
|
let(:request) do
|
9
30
|
HTTP::Request.new(
|
@@ -25,7 +46,6 @@ RSpec.describe HTTP::Features::Instrumentation do
|
|
25
46
|
let(:response) do
|
26
47
|
HTTP::Response.new(
|
27
48
|
:version => "1.1",
|
28
|
-
:uri => "https://example.com",
|
29
49
|
:status => 200,
|
30
50
|
:headers => {:content_type => "application/json"},
|
31
51
|
:body => '{"success": true}',
|
@@ -39,19 +59,4 @@ RSpec.describe HTTP::Features::Instrumentation do
|
|
39
59
|
expect(instrumenter.output[:finish]).to eq(:response => response)
|
40
60
|
end
|
41
61
|
end
|
42
|
-
|
43
|
-
class TestInstrumenter < HTTP::Features::Instrumentation::NullInstrumenter
|
44
|
-
attr_reader :output
|
45
|
-
def initialize
|
46
|
-
@output = {}
|
47
|
-
end
|
48
|
-
|
49
|
-
def start(_name, payload)
|
50
|
-
output[:start] = payload
|
51
|
-
end
|
52
|
-
|
53
|
-
def finish(_name, payload)
|
54
|
-
output[:finish] = payload
|
55
|
-
end
|
56
|
-
end
|
57
62
|
end
|
@@ -4,10 +4,8 @@ require "logger"
|
|
4
4
|
|
5
5
|
RSpec.describe HTTP::Features::Logging do
|
6
6
|
subject(:feature) do
|
7
|
-
logger
|
8
|
-
logger.formatter = ->(severity, _, _, message)
|
9
|
-
format("** %s **\n%s\n", severity, message)
|
10
|
-
end
|
7
|
+
logger = Logger.new(logdev)
|
8
|
+
logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
|
11
9
|
|
12
10
|
described_class.new(:logger => logger)
|
13
11
|
end
|
@@ -44,7 +42,6 @@ RSpec.describe HTTP::Features::Logging do
|
|
44
42
|
let(:response) do
|
45
43
|
HTTP::Response.new(
|
46
44
|
:version => "1.1",
|
47
|
-
:uri => "https://example.com",
|
48
45
|
:status => 200,
|
49
46
|
:headers => {:content_type => "application/json"},
|
50
47
|
:body => '{"success": true}',
|
@@ -321,17 +321,17 @@ RSpec.describe HTTP::Headers do
|
|
321
321
|
|
322
322
|
it "yields header keys specified as symbols in normalized form" do
|
323
323
|
keys = headers.each.map(&:first)
|
324
|
-
expect(keys).to eq([
|
324
|
+
expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie])
|
325
325
|
end
|
326
326
|
|
327
327
|
it "yields headers specified as strings without conversion" do
|
328
328
|
headers.add "X_kEy", "value"
|
329
329
|
keys = headers.each.map(&:first)
|
330
|
-
expect(keys).to eq([
|
330
|
+
expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie X_kEy])
|
331
331
|
end
|
332
332
|
|
333
333
|
it "returns self instance if block given" do
|
334
|
-
expect(headers.each { |*| }).to be headers
|
334
|
+
expect(headers.each { |*| }).to be headers # rubocop:disable Lint/EmptyBlock
|
335
335
|
end
|
336
336
|
|
337
337
|
it "returns Enumerator if no block given" do
|
@@ -396,5 +396,49 @@ RSpec.describe HTTP::Redirector do
|
|
396
396
|
end
|
397
397
|
end
|
398
398
|
end
|
399
|
+
|
400
|
+
describe "changing verbs during redirects" do
|
401
|
+
let(:options) { {:strict => false} }
|
402
|
+
let(:post_body) { HTTP::Request::Body.new("i might be way longer in real life") }
|
403
|
+
let(:cookie) { "dont eat my cookies" }
|
404
|
+
|
405
|
+
def a_dangerous_request(verb)
|
406
|
+
HTTP::Request.new(
|
407
|
+
:verb => verb, :uri => "http://example.com",
|
408
|
+
:body => post_body, :headers => {
|
409
|
+
"Content-Type" => "meme",
|
410
|
+
"Cookie" => cookie
|
411
|
+
}
|
412
|
+
)
|
413
|
+
end
|
414
|
+
|
415
|
+
def empty_body
|
416
|
+
HTTP::Request::Body.new(nil)
|
417
|
+
end
|
418
|
+
|
419
|
+
it "follows without body/content type if it has to change verb" do
|
420
|
+
req = a_dangerous_request(:post)
|
421
|
+
res = redirect_response 302, "http://example.com/1"
|
422
|
+
|
423
|
+
redirector.perform(req, res) do |prev_req, _|
|
424
|
+
expect(prev_req.body).to eq(empty_body)
|
425
|
+
expect(prev_req.headers["Cookie"]).to eq(cookie)
|
426
|
+
expect(prev_req.headers["Content-Type"]).to eq(nil)
|
427
|
+
simple_response 200
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
it "leaves body/content-type intact if it does not have to change verb" do
|
432
|
+
req = a_dangerous_request(:post)
|
433
|
+
res = redirect_response 307, "http://example.com/1"
|
434
|
+
|
435
|
+
redirector.perform(req, res) do |prev_req, _|
|
436
|
+
expect(prev_req.body).to eq(post_body)
|
437
|
+
expect(prev_req.headers["Cookie"]).to eq(cookie)
|
438
|
+
expect(prev_req.headers["Content-Type"]).to eq("meme")
|
439
|
+
simple_response 200
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
399
443
|
end
|
400
444
|
end
|
@@ -118,10 +118,10 @@ RSpec.describe HTTP::Request::Body do
|
|
118
118
|
end
|
119
119
|
|
120
120
|
context "when body is a non-Enumerable IO" do
|
121
|
-
let(:body) { FakeIO.new("a" * 16 * 1024 + "b" * 10 * 1024) }
|
121
|
+
let(:body) { FakeIO.new(("a" * 16 * 1024) + ("b" * 10 * 1024)) }
|
122
122
|
|
123
123
|
it "yields chunks of content" do
|
124
|
-
expect(chunks.inject("", :+)).to eq "a" * 16 * 1024 + "b" * 10 * 1024
|
124
|
+
expect(chunks.inject("", :+)).to eq ("a" * 16 * 1024) + ("b" * 10 * 1024)
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
@@ -148,7 +148,7 @@ RSpec.describe HTTP::Request::Body do
|
|
148
148
|
end
|
149
149
|
|
150
150
|
context "when body is an Enumerable IO" do
|
151
|
-
let(:data) { "a" * 16 * 1024 + "b" * 10 * 1024 }
|
151
|
+
let(:data) { ("a" * 16 * 1024) + ("b" * 10 * 1024) }
|
152
152
|
let(:body) { StringIO.new data }
|
153
153
|
|
154
154
|
it "yields chunks of content" do
|
@@ -47,9 +47,20 @@ RSpec.describe HTTP::Request::Writer do
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
context "when body is
|
50
|
+
context "when body is not set" do
|
51
51
|
let(:body) { HTTP::Request::Body.new(nil) }
|
52
52
|
|
53
|
+
it "doesn't write anything to the socket and doesn't set Content-Length" do
|
54
|
+
writer.stream
|
55
|
+
expect(io.string).to eq [
|
56
|
+
"#{headerstart}\r\n\r\n"
|
57
|
+
].join
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when body is empty" do
|
62
|
+
let(:body) { HTTP::Request::Body.new("") }
|
63
|
+
|
53
64
|
it "doesn't write anything to the socket and sets Content-Length" do
|
54
65
|
writer.stream
|
55
66
|
expect(io.string).to eq [
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
RSpec.describe HTTP::Response::Body do
|
4
4
|
let(:connection) { double(:sequence_id => 0) }
|
5
|
-
let(:chunks) { [
|
5
|
+
let(:chunks) { ["Hello, ", "World!"] }
|
6
6
|
|
7
7
|
before do
|
8
8
|
allow(connection).to receive(:readpartial) { chunks.shift }
|
@@ -16,7 +16,7 @@ RSpec.describe HTTP::Response::Body do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
context "when body empty" do
|
19
|
-
let(:chunks) { [
|
19
|
+
let(:chunks) { [""] }
|
20
20
|
|
21
21
|
it "returns responds to empty? with true" do
|
22
22
|
expect(subject).to be_empty
|
@@ -45,12 +45,12 @@ RSpec.describe HTTP::Response::Body do
|
|
45
45
|
it "returns content in specified encoding" do
|
46
46
|
body = described_class.new(connection)
|
47
47
|
expect(connection).to receive(:readpartial).
|
48
|
-
and_return(String.new("content"
|
48
|
+
and_return(String.new("content", :encoding => Encoding::UTF_8))
|
49
49
|
expect(body.readpartial.encoding).to eq Encoding::BINARY
|
50
50
|
|
51
51
|
body = described_class.new(connection, :encoding => Encoding::UTF_8)
|
52
52
|
expect(connection).to receive(:readpartial).
|
53
|
-
and_return(String.new("content"
|
53
|
+
and_return(String.new("content", :encoding => Encoding::BINARY))
|
54
54
|
expect(body.readpartial.encoding).to eq Encoding::UTF_8
|
55
55
|
end
|
56
56
|
end
|
@@ -59,7 +59,7 @@ RSpec.describe HTTP::Response::Body do
|
|
59
59
|
let(:chunks) do
|
60
60
|
body = Zlib::Deflate.deflate("Hi, HTTP here ☺")
|
61
61
|
len = body.length
|
62
|
-
[
|
62
|
+
[body[0, len / 2], body[(len / 2)..-1]]
|
63
63
|
end
|
64
64
|
subject(:body) do
|
65
65
|
inflater = HTTP::Response::Inflater.new(connection)
|
@@ -32,7 +32,7 @@ RSpec.describe HTTP::Response::Parser do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
context "response in many parts" do
|
35
|
-
let(:parts) { raw_response.
|
35
|
+
let(:parts) { raw_response.chars }
|
36
36
|
|
37
37
|
it "parses headers" do
|
38
38
|
expect(subject.headers.to_h).to eq(expected_headers)
|
@@ -46,9 +46,9 @@ RSpec.describe HTTP::Response::Parser do
|
|
46
46
|
context "when got 100 Continue response" do
|
47
47
|
let :raw_response do
|
48
48
|
"HTTP/1.1 100 Continue\r\n\r\n" \
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
"HTTP/1.1 200 OK\r\n" \
|
50
|
+
"Content-Length: 12\r\n\r\n" \
|
51
|
+
"Hello World!"
|
52
52
|
end
|
53
53
|
|
54
54
|
context "when response is feeded in one part" do
|
@@ -62,7 +62,7 @@ RSpec.describe HTTP::Response::Parser do
|
|
62
62
|
end
|
63
63
|
|
64
64
|
context "when response is feeded in many parts" do
|
65
|
-
let(:parts) { raw_response.
|
65
|
+
let(:parts) { raw_response.chars }
|
66
66
|
|
67
67
|
it "skips to next non-info response" do
|
68
68
|
expect(subject.status_code).to eq(200)
|
@@ -4,6 +4,7 @@ RSpec.describe HTTP::Response do
|
|
4
4
|
let(:body) { "Hello world!" }
|
5
5
|
let(:uri) { "http://example.com/" }
|
6
6
|
let(:headers) { {} }
|
7
|
+
let(:request) { HTTP::Request.new(:verb => :get, :uri => uri) }
|
7
8
|
|
8
9
|
subject(:response) do
|
9
10
|
HTTP::Response.new(
|
@@ -11,8 +12,7 @@ RSpec.describe HTTP::Response do
|
|
11
12
|
:version => "1.1",
|
12
13
|
:headers => headers,
|
13
14
|
:body => body,
|
14
|
-
:
|
15
|
-
:request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
|
15
|
+
:request => request
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
@@ -87,19 +87,32 @@ RSpec.describe HTTP::Response do
|
|
87
87
|
end
|
88
88
|
|
89
89
|
describe "#parse" do
|
90
|
-
let(:headers) { {"Content-Type" =>
|
90
|
+
let(:headers) { {"Content-Type" => content_type} }
|
91
91
|
let(:body) { '{"foo":"bar"}' }
|
92
92
|
|
93
|
-
|
94
|
-
|
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
|
95
98
|
end
|
96
99
|
|
97
|
-
|
98
|
-
|
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
|
99
105
|
end
|
100
106
|
|
101
|
-
|
102
|
-
|
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
|
103
116
|
end
|
104
117
|
end
|
105
118
|
|
@@ -154,7 +167,7 @@ RSpec.describe HTTP::Response do
|
|
154
167
|
:version => "1.1",
|
155
168
|
:status => 200,
|
156
169
|
:connection => connection,
|
157
|
-
:request =>
|
170
|
+
:request => request
|
158
171
|
)
|
159
172
|
end
|
160
173
|
|
@@ -171,4 +184,43 @@ RSpec.describe HTTP::Response do
|
|
171
184
|
end
|
172
185
|
it { is_expected.not_to be_chunked }
|
173
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
|
174
226
|
end
|
data/spec/lib/http_spec.rb
CHANGED
@@ -95,7 +95,8 @@ RSpec.describe HTTP do
|
|
95
95
|
expect(response.to_s).to match(/<!doctype html>/)
|
96
96
|
end
|
97
97
|
|
98
|
-
|
98
|
+
# TODO: htt:s://github.com/httprb/http/issues/627
|
99
|
+
xcontext "ssl" do
|
99
100
|
it "responds with the endpoint's body" do
|
100
101
|
response = ssl_client.via(proxy.addr, proxy.port).get dummy_ssl.endpoint
|
101
102
|
expect(response.to_s).to match(/<!doctype html>/)
|
@@ -131,7 +132,8 @@ RSpec.describe HTTP do
|
|
131
132
|
expect(response.status).to eq(407)
|
132
133
|
end
|
133
134
|
|
134
|
-
|
135
|
+
# TODO: htt:s://github.com/httprb/http/issues/627
|
136
|
+
xcontext "ssl" do
|
135
137
|
it "responds with the endpoint's body" do
|
136
138
|
response = ssl_client.via(proxy.addr, proxy.port, "username", "password").get dummy_ssl.endpoint
|
137
139
|
expect(response.to_s).to match(/<!doctype html>/)
|
@@ -438,6 +440,22 @@ RSpec.describe HTTP do
|
|
438
440
|
|
439
441
|
expect(response.to_s).to eq("#{body}-deflated")
|
440
442
|
end
|
443
|
+
|
444
|
+
it "returns empty body for no content response where Content-Encoding is gzip" do
|
445
|
+
client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "gzip")
|
446
|
+
body = "Hello!"
|
447
|
+
response = client.post("#{dummy.endpoint}/no-content-204", :body => body)
|
448
|
+
|
449
|
+
expect(response.to_s).to eq("")
|
450
|
+
end
|
451
|
+
|
452
|
+
it "returns empty body for no content response where Content-Encoding is deflate" do
|
453
|
+
client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "deflate")
|
454
|
+
body = "Hello!"
|
455
|
+
response = client.post("#{dummy.endpoint}/no-content-204", :body => body)
|
456
|
+
|
457
|
+
expect(response.to_s).to eq("")
|
458
|
+
end
|
441
459
|
end
|
442
460
|
|
443
461
|
context "with :normalize_uri" do
|