http 2.2.2 → 3.0.0.pre
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/.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
|