http 2.2.2 → 3.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +46 -13
- data/.travis.yml +17 -12
- data/CHANGES.md +25 -1
- data/Gemfile +11 -4
- data/Guardfile +2 -0
- data/README.md +4 -5
- data/Rakefile +14 -13
- data/http.gemspec +3 -1
- data/lib/http.rb +1 -0
- data/lib/http/chainable.rb +15 -14
- data/lib/http/client.rb +27 -24
- data/lib/http/connection.rb +6 -4
- data/lib/http/content_type.rb +1 -0
- data/lib/http/errors.rb +3 -2
- data/lib/http/feature.rb +2 -1
- data/lib/http/features/auto_deflate.rb +77 -20
- data/lib/http/features/auto_inflate.rb +2 -1
- data/lib/http/headers.rb +3 -2
- data/lib/http/headers/known.rb +23 -22
- data/lib/http/headers/mixin.rb +1 -0
- data/lib/http/mime_type.rb +1 -0
- data/lib/http/mime_type/adapter.rb +2 -1
- data/lib/http/mime_type/json.rb +2 -1
- data/lib/http/options.rb +15 -12
- data/lib/http/redirector.rb +4 -3
- data/lib/http/request.rb +25 -10
- data/lib/http/request/body.rb +67 -0
- data/lib/http/request/writer.rb +32 -37
- data/lib/http/response.rb +17 -2
- data/lib/http/response/body.rb +16 -12
- data/lib/http/response/parser.rb +1 -0
- data/lib/http/response/status.rb +1 -0
- data/lib/http/response/status/reasons.rb +1 -0
- data/lib/http/timeout/global.rb +1 -0
- data/lib/http/timeout/null.rb +2 -1
- data/lib/http/timeout/per_operation.rb +19 -6
- data/lib/http/uri.rb +8 -2
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/client_spec.rb +104 -4
- data/spec/lib/http/content_type_spec.rb +1 -0
- data/spec/lib/http/features/auto_deflate_spec.rb +32 -64
- data/spec/lib/http/features/auto_inflate_spec.rb +1 -0
- data/spec/lib/http/headers/mixin_spec.rb +1 -0
- data/spec/lib/http/headers_spec.rb +36 -35
- data/spec/lib/http/options/body_spec.rb +1 -0
- data/spec/lib/http/options/features_spec.rb +1 -0
- data/spec/lib/http/options/form_spec.rb +1 -0
- data/spec/lib/http/options/headers_spec.rb +2 -1
- data/spec/lib/http/options/json_spec.rb +1 -0
- data/spec/lib/http/options/new_spec.rb +2 -1
- data/spec/lib/http/options/proxy_spec.rb +1 -0
- data/spec/lib/http/options_spec.rb +1 -0
- data/spec/lib/http/redirector_spec.rb +1 -0
- data/spec/lib/http/request/body_spec.rb +138 -0
- data/spec/lib/http/request/writer_spec.rb +44 -74
- data/spec/lib/http/request_spec.rb +14 -0
- data/spec/lib/http/response/body_spec.rb +20 -4
- data/spec/lib/http/response/status_spec.rb +27 -26
- data/spec/lib/http/response_spec.rb +10 -0
- data/spec/lib/http/uri_spec.rb +11 -0
- data/spec/lib/http_spec.rb +18 -6
- data/spec/regression_specs.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/black_hole.rb +9 -2
- data/spec/support/capture_warning.rb +1 -0
- data/spec/support/dummy_server.rb +2 -1
- data/spec/support/dummy_server/servlet.rb +1 -1
- data/spec/support/fakeio.rb +21 -0
- data/spec/support/http_handling_shared.rb +1 -0
- data/spec/support/proxy_server.rb +1 -0
- data/spec/support/servers/config.rb +1 -0
- data/spec/support/servers/runner.rb +1 -0
- data/spec/support/ssl_helper.rb +3 -2
- metadata +20 -9
data/lib/http/response/parser.rb
CHANGED
data/lib/http/response/status.rb
CHANGED
data/lib/http/timeout/global.rb
CHANGED
data/lib/http/timeout/null.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "forwardable"
|
3
4
|
require "io/wait"
|
4
5
|
|
@@ -11,7 +12,7 @@ module HTTP
|
|
11
12
|
|
12
13
|
attr_reader :options, :socket
|
13
14
|
|
14
|
-
def initialize(options = {})
|
15
|
+
def initialize(options = {}) # rubocop:disable Style/OptionHash
|
15
16
|
@options = options
|
16
17
|
end
|
17
18
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "timeout"
|
3
4
|
|
4
5
|
require "http/timeout/null"
|
@@ -59,29 +60,41 @@ module HTTP
|
|
59
60
|
else
|
60
61
|
# Read data from the socket
|
61
62
|
def readpartial(size)
|
63
|
+
timeout = false
|
62
64
|
loop do
|
63
65
|
result = @socket.read_nonblock(size, :exception => false)
|
64
66
|
|
65
67
|
return :eof if result.nil?
|
66
68
|
return result if result != :wait_readable
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
raise TimeoutError, "Read timed out after #{read_timeout} seconds" if timeout
|
71
|
+
# marking the socket for timeout. Why is this not being raised immediately?
|
72
|
+
# it seems there is some race-condition on the network level between calling
|
73
|
+
# #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting
|
74
|
+
# for reads, and when waiting for x seconds, it returns nil suddenly without completing
|
75
|
+
# the x seconds. In a normal case this would be a timeout on wait/read, but it can
|
76
|
+
# also mean that the socket has been closed by the server. Therefore we "mark" the
|
77
|
+
# socket for timeout and try to read more bytes. If it returns :eof, it's all good, no
|
78
|
+
# timeout. Else, the first timeout was a proper timeout.
|
79
|
+
# This hack has to be done because io/wait#wait_readable doesn't provide a value for when
|
80
|
+
# the socket is closed by the server, and HTTP::Parser doesn't provide the limit for the chunks.
|
81
|
+
timeout = true unless @socket.to_io.wait_readable(read_timeout)
|
71
82
|
end
|
72
83
|
end
|
73
84
|
|
74
85
|
# Write data to the socket
|
75
86
|
def write(data)
|
87
|
+
timeout = false
|
76
88
|
loop do
|
77
89
|
result = @socket.write_nonblock(data, :exception => false)
|
78
90
|
return result unless result == :wait_writable
|
79
91
|
|
80
|
-
|
81
|
-
|
82
|
-
|
92
|
+
raise TimeoutError, "Write timed out after #{write_timeout} seconds" if timeout
|
93
|
+
|
94
|
+
timeout = true unless @socket.to_io.wait_writable(write_timeout)
|
83
95
|
end
|
84
96
|
end
|
97
|
+
|
85
98
|
end
|
86
99
|
end
|
87
100
|
end
|
data/lib/http/uri.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "addressable/uri"
|
3
4
|
|
4
5
|
module HTTP
|
@@ -19,10 +20,10 @@ module HTTP
|
|
19
20
|
def_delegators :@uri, :omit, :join, :normalize
|
20
21
|
|
21
22
|
# @private
|
22
|
-
HTTP_SCHEME = "http"
|
23
|
+
HTTP_SCHEME = "http"
|
23
24
|
|
24
25
|
# @private
|
25
|
-
HTTPS_SCHEME = "https"
|
26
|
+
HTTPS_SCHEME = "https"
|
26
27
|
|
27
28
|
# Parse the given URI string, returning an HTTP::URI object
|
28
29
|
#
|
@@ -114,6 +115,11 @@ module HTTP
|
|
114
115
|
HTTPS_SCHEME == scheme
|
115
116
|
end
|
116
117
|
|
118
|
+
# @return [Object] duplicated URI
|
119
|
+
def dup
|
120
|
+
self.class.new @uri.dup
|
121
|
+
end
|
122
|
+
|
117
123
|
# Convert an HTTP::URI to a String
|
118
124
|
#
|
119
125
|
# @return [String] URI serialized as a String
|
data/lib/http/version.rb
CHANGED
@@ -111,7 +111,7 @@ RSpec.describe HTTP::Client do
|
|
111
111
|
|
112
112
|
it "accepts params within the provided URL" do
|
113
113
|
expect(HTTP::Request).to receive(:new) do |opts|
|
114
|
-
expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w
|
114
|
+
expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w[bar])
|
115
115
|
end
|
116
116
|
|
117
117
|
client.get("http://example.com/?foo=bar")
|
@@ -119,7 +119,7 @@ RSpec.describe HTTP::Client do
|
|
119
119
|
|
120
120
|
it "combines GET params from the URI with the passed in params" do
|
121
121
|
expect(HTTP::Request).to receive(:new) do |opts|
|
122
|
-
expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w
|
122
|
+
expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w[bar], "baz" => %w[quux])
|
123
123
|
end
|
124
124
|
|
125
125
|
client.get("http://example.com/?foo=bar", :params => {:baz => "quux"})
|
@@ -143,7 +143,7 @@ RSpec.describe HTTP::Client do
|
|
143
143
|
|
144
144
|
it "does not corrupts index-less arrays" do
|
145
145
|
expect(HTTP::Request).to receive(:new) do |opts|
|
146
|
-
expect(CGI.parse(opts[:uri].query)).to eq "a[]" => %w
|
146
|
+
expect(CGI.parse(opts[:uri].query)).to eq "a[]" => %w[b c], "d" => %w[e]
|
147
147
|
end
|
148
148
|
|
149
149
|
client.get("http://example.com/?a[]=b&a[]=c", :params => {:d => "e"})
|
@@ -158,6 +158,32 @@ RSpec.describe HTTP::Client do
|
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
161
|
+
describe "passing multipart form data" do
|
162
|
+
it "creates url encoded form data object" do
|
163
|
+
client = HTTP::Client.new
|
164
|
+
allow(client).to receive(:perform)
|
165
|
+
|
166
|
+
expect(HTTP::Request).to receive(:new) do |opts|
|
167
|
+
expect(opts[:body]).to be_a(HTTP::FormData::Urlencoded)
|
168
|
+
expect(opts[:body].to_s).to eq "foo=bar"
|
169
|
+
end
|
170
|
+
|
171
|
+
client.get("http://example.com/", :form => {:foo => "bar"})
|
172
|
+
end
|
173
|
+
|
174
|
+
it "creates multipart form data object" do
|
175
|
+
client = HTTP::Client.new
|
176
|
+
allow(client).to receive(:perform)
|
177
|
+
|
178
|
+
expect(HTTP::Request).to receive(:new) do |opts|
|
179
|
+
expect(opts[:body]).to be_a(HTTP::FormData::Multipart)
|
180
|
+
expect(opts[:body].to_s).to include("content")
|
181
|
+
end
|
182
|
+
|
183
|
+
client.get("http://example.com/", :form => {:foo => HTTP::FormData::Part.new("content")})
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
161
187
|
describe "passing json" do
|
162
188
|
it "encodes given object" do
|
163
189
|
client = HTTP::Client.new
|
@@ -197,6 +223,27 @@ RSpec.describe HTTP::Client do
|
|
197
223
|
client.request(:get, "http://example.com/")
|
198
224
|
end
|
199
225
|
end
|
226
|
+
|
227
|
+
context "when :auto_deflate was specified" do
|
228
|
+
let(:headers) { {"Content-Length" => "12"} }
|
229
|
+
let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}} }
|
230
|
+
|
231
|
+
it "deletes Content-Length header" do
|
232
|
+
expect(client).to receive(:perform) do |req, _|
|
233
|
+
expect(req["Content-Length"]).to eq nil
|
234
|
+
end
|
235
|
+
|
236
|
+
client.request(:get, "http://example.com/")
|
237
|
+
end
|
238
|
+
|
239
|
+
it "sets Content-Encoding header" do
|
240
|
+
expect(client).to receive(:perform) do |req, _|
|
241
|
+
expect(req["Content-Encoding"]).to eq "gzip"
|
242
|
+
end
|
243
|
+
|
244
|
+
client.request(:get, "http://example.com/")
|
245
|
+
end
|
246
|
+
end
|
200
247
|
end
|
201
248
|
|
202
249
|
include_context "HTTP handling" do
|
@@ -280,7 +327,7 @@ RSpec.describe HTTP::Client do
|
|
280
327
|
|
281
328
|
allow(socket_spy).to receive(:close) { nil }
|
282
329
|
allow(socket_spy).to receive(:closed?) { true }
|
283
|
-
allow(socket_spy).to receive(:readpartial) { chunks
|
330
|
+
allow(socket_spy).to receive(:readpartial) { chunks.shift || :eof }
|
284
331
|
allow(socket_spy).to receive(:write) { chunks[0].length }
|
285
332
|
|
286
333
|
allow(TCPSocket).to receive(:open) { socket_spy }
|
@@ -291,5 +338,58 @@ RSpec.describe HTTP::Client do
|
|
291
338
|
expect(body).to eq "<!doctype html>"
|
292
339
|
end
|
293
340
|
end
|
341
|
+
|
342
|
+
context "when uses chunked transfer encoding" do
|
343
|
+
let(:chunks) do
|
344
|
+
[
|
345
|
+
<<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n") << body
|
346
|
+
| HTTP/1.1 200 OK
|
347
|
+
| Content-Type: application/json
|
348
|
+
| Transfer-Encoding: chunked
|
349
|
+
| Connection: close
|
350
|
+
|
|
351
|
+
RESPONSE
|
352
|
+
]
|
353
|
+
end
|
354
|
+
let(:body) do
|
355
|
+
<<-BODY.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
|
356
|
+
| 9
|
357
|
+
| {"state":
|
358
|
+
| 5
|
359
|
+
| "ok"}
|
360
|
+
| 0
|
361
|
+
|
|
362
|
+
BODY
|
363
|
+
end
|
364
|
+
|
365
|
+
before do
|
366
|
+
socket_spy = double
|
367
|
+
|
368
|
+
allow(socket_spy).to receive(:close) { nil }
|
369
|
+
allow(socket_spy).to receive(:closed?) { true }
|
370
|
+
allow(socket_spy).to receive(:readpartial) { chunks.shift || :eof }
|
371
|
+
allow(socket_spy).to receive(:write) { chunks[0].length }
|
372
|
+
|
373
|
+
allow(TCPSocket).to receive(:open) { socket_spy }
|
374
|
+
end
|
375
|
+
|
376
|
+
it "properly reads body" do
|
377
|
+
body = client.get(dummy.endpoint).to_s
|
378
|
+
expect(body).to eq '{"state":"ok"}'
|
379
|
+
end
|
380
|
+
|
381
|
+
context "with broken body (too early closed connection)" do
|
382
|
+
let(:body) do
|
383
|
+
<<-BODY.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
|
384
|
+
| 9
|
385
|
+
| {"state":
|
386
|
+
BODY
|
387
|
+
end
|
388
|
+
|
389
|
+
it "raises HTTP::ConnectionError" do
|
390
|
+
expect { client.get(dummy.endpoint).to_s }.to raise_error(HTTP::ConnectionError)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
294
394
|
end
|
295
395
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
RSpec.describe HTTP::Features::AutoDeflate do
|
3
4
|
subject { HTTP::Features::AutoDeflate.new }
|
4
5
|
|
@@ -25,84 +26,51 @@ RSpec.describe HTTP::Features::AutoDeflate do
|
|
25
26
|
expect(subject.method).to eq("gzip")
|
26
27
|
end
|
27
28
|
|
28
|
-
describe "#
|
29
|
-
let(:
|
30
|
-
|
31
|
-
context "when body is nil" do
|
32
|
-
let(:body) { nil }
|
33
|
-
|
34
|
-
it "returns nil" do
|
35
|
-
expect(subject.deflate(headers, body)).to be_nil
|
36
|
-
end
|
37
|
-
|
38
|
-
it "does not remove Content-Length header" do
|
39
|
-
subject.deflate(headers, body)
|
40
|
-
expect(headers["Content-Length"]).to eq "10"
|
41
|
-
end
|
29
|
+
describe "#deflated_body" do
|
30
|
+
let(:body) { %w[bees cows] }
|
31
|
+
let(:deflated_body) { subject.deflated_body(body) }
|
42
32
|
|
43
|
-
|
44
|
-
|
45
|
-
expect(headers.include?("Content-Encoding")).to eq false
|
46
|
-
end
|
47
|
-
end
|
33
|
+
context "when method is gzip" do
|
34
|
+
subject { HTTP::Features::AutoDeflate.new(:method => :gzip) }
|
48
35
|
|
49
|
-
|
50
|
-
|
36
|
+
it "returns object which yields gzipped content of the given body" do
|
37
|
+
io = StringIO.new
|
38
|
+
io.set_encoding(Encoding::BINARY)
|
39
|
+
gzip = Zlib::GzipWriter.new(io)
|
40
|
+
gzip.write("beescows")
|
41
|
+
gzip.close
|
42
|
+
gzipped = io.string
|
51
43
|
|
52
|
-
|
53
|
-
expect(subject.deflate(headers, body).object_id).to eq(body.object_id)
|
44
|
+
expect(deflated_body.each.to_a.join).to eq gzipped
|
54
45
|
end
|
55
46
|
|
56
|
-
it "
|
57
|
-
|
58
|
-
|
59
|
-
|
47
|
+
it "caches compressed content when size is called" do
|
48
|
+
io = StringIO.new
|
49
|
+
io.set_encoding(Encoding::BINARY)
|
50
|
+
gzip = Zlib::GzipWriter.new(io)
|
51
|
+
gzip.write("beescows")
|
52
|
+
gzip.close
|
53
|
+
gzipped = io.string
|
60
54
|
|
61
|
-
|
62
|
-
|
63
|
-
expect(headers.include?("Content-Encoding")).to eq false
|
55
|
+
expect(deflated_body.size).to eq gzipped.bytesize
|
56
|
+
expect(deflated_body.each.to_a.join).to eq gzipped
|
64
57
|
end
|
65
58
|
end
|
66
59
|
|
67
|
-
context "when
|
68
|
-
|
69
|
-
|
70
|
-
it "encodes body" do
|
71
|
-
encoded = subject.deflate(headers, body)
|
72
|
-
decoded = Zlib::GzipReader.new(StringIO.new(encoded)).read
|
73
|
-
|
74
|
-
expect(decoded).to eq(body)
|
75
|
-
end
|
60
|
+
context "when method is deflate" do
|
61
|
+
subject { HTTP::Features::AutoDeflate.new(:method => :deflate) }
|
76
62
|
|
77
|
-
it "
|
78
|
-
|
79
|
-
expect(headers.include?("Content-Length")).to eq false
|
80
|
-
end
|
63
|
+
it "returns object which yields deflated content of the given body" do
|
64
|
+
deflated = Zlib::Deflate.deflate("beescows")
|
81
65
|
|
82
|
-
|
83
|
-
subject.deflate(headers, body)
|
84
|
-
expect(headers["Content-Encoding"]).to eq "gzip"
|
66
|
+
expect(deflated_body.each.to_a.join).to eq deflated
|
85
67
|
end
|
86
68
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
it "encodes body" do
|
91
|
-
encoded = subject.deflate(headers, body)
|
92
|
-
decoded = Zlib::Inflate.inflate(encoded)
|
93
|
-
|
94
|
-
expect(decoded).to eq(body)
|
95
|
-
end
|
96
|
-
|
97
|
-
it "removes Content-Length header" do
|
98
|
-
subject.deflate(headers, body)
|
99
|
-
expect(headers.include?("Content-Length")).to eq false
|
100
|
-
end
|
69
|
+
it "caches compressed content when size is called" do
|
70
|
+
deflated = Zlib::Deflate.deflate("beescows")
|
101
71
|
|
102
|
-
|
103
|
-
|
104
|
-
expect(headers["Content-Encoding"]).to eq "deflate"
|
105
|
-
end
|
72
|
+
expect(deflated_body.size).to eq deflated.bytesize
|
73
|
+
expect(deflated_body.each.to_a.join).to eq deflated
|
106
74
|
end
|
107
75
|
end
|
108
76
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
RSpec.describe HTTP::Headers do
|
3
4
|
subject(:headers) { described_class.new }
|
4
5
|
|
@@ -25,18 +26,18 @@ RSpec.describe HTTP::Headers do
|
|
25
26
|
|
26
27
|
it "allows set multiple values" do
|
27
28
|
headers.set :set_cookie, "hoo=ray"
|
28
|
-
headers.set :set_cookie, %w
|
29
|
-
expect(headers["Set-Cookie"]).to eq %w
|
29
|
+
headers.set :set_cookie, %w[hoo=ray woo=hoo]
|
30
|
+
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
|
30
31
|
end
|
31
32
|
|
32
33
|
it "fails with empty header name" do
|
33
34
|
expect { headers.set "", "foo bar" }.
|
34
|
-
to raise_error HTTP::
|
35
|
+
to raise_error HTTP::HeaderError
|
35
36
|
end
|
36
37
|
|
37
38
|
it "fails with invalid header name" do
|
38
39
|
expect { headers.set "foo bar", "baz" }.
|
39
|
-
to raise_error HTTP::
|
40
|
+
to raise_error HTTP::HeaderError
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -59,8 +60,8 @@ RSpec.describe HTTP::Headers do
|
|
59
60
|
|
60
61
|
it "allows set multiple values" do
|
61
62
|
headers[:set_cookie] = "hoo=ray"
|
62
|
-
headers[:set_cookie] = %w
|
63
|
-
expect(headers["Set-Cookie"]).to eq %w
|
63
|
+
headers[:set_cookie] = %w[hoo=ray woo=hoo]
|
64
|
+
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
@@ -79,12 +80,12 @@ RSpec.describe HTTP::Headers do
|
|
79
80
|
|
80
81
|
it "fails with empty header name" do
|
81
82
|
expect { headers.delete "" }.
|
82
|
-
to raise_error HTTP::
|
83
|
+
to raise_error HTTP::HeaderError
|
83
84
|
end
|
84
85
|
|
85
86
|
it "fails with invalid header name" do
|
86
87
|
expect { headers.delete "foo bar" }.
|
87
|
-
to raise_error HTTP::
|
88
|
+
to raise_error HTTP::HeaderError
|
88
89
|
end
|
89
90
|
end
|
90
91
|
|
@@ -102,23 +103,23 @@ RSpec.describe HTTP::Headers do
|
|
102
103
|
it "appends new value if header exists" do
|
103
104
|
headers.add :set_cookie, "hoo=ray"
|
104
105
|
headers.add :set_cookie, "woo=hoo"
|
105
|
-
expect(headers["Set-Cookie"]).to eq %w
|
106
|
+
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
|
106
107
|
end
|
107
108
|
|
108
109
|
it "allows append multiple values" do
|
109
110
|
headers.add :set_cookie, "hoo=ray"
|
110
|
-
headers.add :set_cookie, %w
|
111
|
-
expect(headers["Set-Cookie"]).to eq %w
|
111
|
+
headers.add :set_cookie, %w[woo=hoo yup=pie]
|
112
|
+
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo yup=pie]
|
112
113
|
end
|
113
114
|
|
114
115
|
it "fails with empty header name" do
|
115
116
|
expect { headers.add("", "foobar") }.
|
116
|
-
to raise_error HTTP::
|
117
|
+
to raise_error HTTP::HeaderError
|
117
118
|
end
|
118
119
|
|
119
120
|
it "fails with invalid header name" do
|
120
121
|
expect { headers.add "foo bar", "baz" }.
|
121
|
-
to raise_error HTTP::
|
122
|
+
to raise_error HTTP::HeaderError
|
122
123
|
end
|
123
124
|
end
|
124
125
|
|
@@ -126,11 +127,11 @@ RSpec.describe HTTP::Headers do
|
|
126
127
|
before { headers.set("Content-Type", "application/json") }
|
127
128
|
|
128
129
|
it "returns array of associated values" do
|
129
|
-
expect(headers.get("Content-Type")).to eq %w
|
130
|
+
expect(headers.get("Content-Type")).to eq %w[application/json]
|
130
131
|
end
|
131
132
|
|
132
133
|
it "normalizes header name" do
|
133
|
-
expect(headers.get(:content_type)).to eq %w
|
134
|
+
expect(headers.get(:content_type)).to eq %w[application/json]
|
134
135
|
end
|
135
136
|
|
136
137
|
context "when header does not exists" do
|
@@ -141,12 +142,12 @@ RSpec.describe HTTP::Headers do
|
|
141
142
|
|
142
143
|
it "fails with empty header name" do
|
143
144
|
expect { headers.get("") }.
|
144
|
-
to raise_error HTTP::
|
145
|
+
to raise_error HTTP::HeaderError
|
145
146
|
end
|
146
147
|
|
147
148
|
it "fails with invalid header name" do
|
148
149
|
expect { headers.get("foo bar") }.
|
149
|
-
to raise_error HTTP::
|
150
|
+
to raise_error HTTP::HeaderError
|
150
151
|
end
|
151
152
|
end
|
152
153
|
|
@@ -180,7 +181,7 @@ RSpec.describe HTTP::Headers do
|
|
180
181
|
end
|
181
182
|
|
182
183
|
it "returns array of associated values" do
|
183
|
-
expect(headers[:set_cookie]).to eq %w
|
184
|
+
expect(headers[:set_cookie]).to eq %w[hoo=ray woo=hoo]
|
184
185
|
end
|
185
186
|
end
|
186
187
|
end
|
@@ -217,7 +218,7 @@ RSpec.describe HTTP::Headers do
|
|
217
218
|
end
|
218
219
|
|
219
220
|
it "returns Hash with normalized keys" do
|
220
|
-
expect(headers.to_h.keys).to match_array %w
|
221
|
+
expect(headers.to_h.keys).to match_array %w[Content-Type Set-Cookie]
|
221
222
|
end
|
222
223
|
|
223
224
|
context "for a header with single value" do
|
@@ -228,7 +229,7 @@ RSpec.describe HTTP::Headers do
|
|
228
229
|
|
229
230
|
context "for a header with multiple values" do
|
230
231
|
it "provides an array of values" do
|
231
|
-
expect(headers.to_h["Set-Cookie"]).to eq %w
|
232
|
+
expect(headers.to_h["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
|
232
233
|
end
|
233
234
|
end
|
234
235
|
end
|
@@ -246,15 +247,15 @@ RSpec.describe HTTP::Headers do
|
|
246
247
|
|
247
248
|
it "returns Array of key/value pairs with normalized keys" do
|
248
249
|
expect(headers.to_a).to eq [
|
249
|
-
%w
|
250
|
-
%w
|
251
|
-
%w
|
250
|
+
%w[Content-Type application/json],
|
251
|
+
%w[Set-Cookie hoo=ray],
|
252
|
+
%w[Set-Cookie woo=hoo]
|
252
253
|
]
|
253
254
|
end
|
254
255
|
end
|
255
256
|
|
256
257
|
describe "#inspect" do
|
257
|
-
before { headers.set :set_cookie, %w
|
258
|
+
before { headers.set :set_cookie, %w[hoo=ray woo=hoo] }
|
258
259
|
subject { headers.inspect }
|
259
260
|
|
260
261
|
it { is_expected.to eq '#<HTTP::Headers {"Set-Cookie"=>["hoo=ray", "woo=hoo"]}>' }
|
@@ -289,9 +290,9 @@ RSpec.describe HTTP::Headers do
|
|
289
290
|
|
290
291
|
it "yields headers in the same order they were added" do
|
291
292
|
expect { |b| headers.each(&b) }.to yield_successive_args(
|
292
|
-
%w
|
293
|
-
%w
|
294
|
-
%w
|
293
|
+
%w[Set-Cookie hoo=ray],
|
294
|
+
%w[Content-Type application/json],
|
295
|
+
%w[Set-Cookie woo=hoo]
|
295
296
|
)
|
296
297
|
end
|
297
298
|
|
@@ -351,7 +352,7 @@ RSpec.describe HTTP::Headers do
|
|
351
352
|
|
352
353
|
it "allows comparison with Array of key/value pairs" do
|
353
354
|
left.add :accept, "text/plain"
|
354
|
-
expect(left).to eq [%w
|
355
|
+
expect(left).to eq [%w[Accept text/plain]]
|
355
356
|
end
|
356
357
|
|
357
358
|
it "sensitive to headers order" do
|
@@ -402,7 +403,7 @@ RSpec.describe HTTP::Headers do
|
|
402
403
|
before do
|
403
404
|
headers.set :host, "example.com"
|
404
405
|
headers.set :accept, "application/json"
|
405
|
-
headers.merge! :accept => "plain/text", :cookie => %w
|
406
|
+
headers.merge! :accept => "plain/text", :cookie => %w[hoo=ray woo=hoo]
|
406
407
|
end
|
407
408
|
|
408
409
|
it "leaves headers not presented in other as is" do
|
@@ -414,7 +415,7 @@ RSpec.describe HTTP::Headers do
|
|
414
415
|
end
|
415
416
|
|
416
417
|
it "appends other headers, not presented in base" do
|
417
|
-
expect(headers[:cookie]).to eq %w
|
418
|
+
expect(headers[:cookie]).to eq %w[hoo=ray woo=hoo]
|
418
419
|
end
|
419
420
|
end
|
420
421
|
|
@@ -425,7 +426,7 @@ RSpec.describe HTTP::Headers do
|
|
425
426
|
end
|
426
427
|
|
427
428
|
subject(:merged) do
|
428
|
-
headers.merge :accept => "plain/text", :cookie => %w
|
429
|
+
headers.merge :accept => "plain/text", :cookie => %w[hoo=ray woo=hoo]
|
429
430
|
end
|
430
431
|
|
431
432
|
it { is_expected.to be_a described_class }
|
@@ -444,7 +445,7 @@ RSpec.describe HTTP::Headers do
|
|
444
445
|
end
|
445
446
|
|
446
447
|
it "appends other headers, not presented in base" do
|
447
|
-
expect(merged[:cookie]).to eq %w
|
448
|
+
expect(merged[:cookie]).to eq %w[hoo=ray woo=hoo]
|
448
449
|
end
|
449
450
|
end
|
450
451
|
|
@@ -462,7 +463,7 @@ RSpec.describe HTTP::Headers do
|
|
462
463
|
end
|
463
464
|
|
464
465
|
it "accepts any object that respond to #to_a" do
|
465
|
-
hashie = double :to_a => [%w
|
466
|
+
hashie = double :to_a => [%w[accept json]]
|
466
467
|
expect(described_class.coerce(hashie)["accept"]).to eq "json"
|
467
468
|
end
|
468
469
|
|
@@ -477,8 +478,8 @@ RSpec.describe HTTP::Headers do
|
|
477
478
|
expect(described_class.coerce(headers).to_a).
|
478
479
|
to match_array(
|
479
480
|
[
|
480
|
-
%w
|
481
|
-
%w
|
481
|
+
%w[Set-Cookie hoo=ray],
|
482
|
+
%w[Set-Cookie woo=hoo]
|
482
483
|
]
|
483
484
|
)
|
484
485
|
end
|