http 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe HTTP::Features::Instrumentation do
4
+ subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) }
5
+ let(:instrumenter) { TestInstrumenter.new }
6
+
7
+ describe "logging the request" do
8
+ let(:request) do
9
+ HTTP::Request.new(
10
+ :verb => :post,
11
+ :uri => "https://example.com/",
12
+ :headers => {:accept => "application/json"},
13
+ :body => '{"hello": "world!"}'
14
+ )
15
+ end
16
+
17
+ it "should log the request" do
18
+ feature.wrap_request(request)
19
+
20
+ expect(instrumenter.output[:start]).to eq(:request => request)
21
+ end
22
+ end
23
+
24
+ describe "logging the response" do
25
+ let(:response) do
26
+ HTTP::Response.new(
27
+ :version => "1.1",
28
+ :uri => "https://example.com",
29
+ :status => 200,
30
+ :headers => {:content_type => "application/json"},
31
+ :body => '{"success": true}'
32
+ )
33
+ end
34
+
35
+ it "should log the response" do
36
+ feature.wrap_response(response)
37
+
38
+ expect(instrumenter.output[:finish]).to eq(:response => response)
39
+ end
40
+ end
41
+
42
+ class TestInstrumenter < HTTP::Features::Instrumentation::NullInstrumenter
43
+ attr_reader :output
44
+ def initialize
45
+ @output = {}
46
+ end
47
+
48
+ def start(_name, payload)
49
+ output[:start] = payload
50
+ end
51
+
52
+ def finish(_name, payload)
53
+ output[:finish] = payload
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe HTTP::Features::Logging do
4
+ subject(:feature) { HTTP::Features::Logging.new(:logger => logger) }
5
+ let(:logger) { TestLogger.new }
6
+
7
+ describe "logging the request" do
8
+ let(:request) do
9
+ HTTP::Request.new(
10
+ :verb => :post,
11
+ :uri => "https://example.com/",
12
+ :headers => {:accept => "application/json"},
13
+ :body => '{"hello": "world!"}'
14
+ )
15
+ end
16
+
17
+ it "should log the request" do
18
+ feature.wrap_request(request)
19
+
20
+ expect(logger.output).to eq(
21
+ [
22
+ "> POST https://example.com/",
23
+ <<~REQ.strip
24
+ Accept: application/json
25
+ Host: example.com
26
+ User-Agent: http.rb/4.0.0.dev
27
+
28
+ {"hello": "world!"}
29
+ REQ
30
+ ]
31
+ )
32
+ end
33
+ end
34
+
35
+ describe "logging the response" do
36
+ let(:response) do
37
+ HTTP::Response.new(
38
+ :version => "1.1",
39
+ :uri => "https://example.com",
40
+ :status => 200,
41
+ :headers => {:content_type => "application/json"},
42
+ :body => '{"success": true}'
43
+ )
44
+ end
45
+
46
+ it "should log the response" do
47
+ feature.wrap_response(response)
48
+
49
+ expect(logger.output).to eq(
50
+ [
51
+ "< 200 OK",
52
+ <<~REQ.strip
53
+ Content-Type: application/json
54
+
55
+ {"success": true}
56
+ REQ
57
+ ]
58
+ )
59
+ end
60
+ end
61
+
62
+ class TestLogger
63
+ attr_reader :output
64
+ def initialize
65
+ @output = []
66
+ end
67
+
68
+ %w[fatal error warn info debug].each do |level|
69
+ define_method(level.to_sym) do |*args, &block|
70
+ @output << (block ? block.call : args)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -157,4 +157,33 @@ RSpec.describe HTTP::Request::Body do
157
157
  end
158
158
  end
159
159
  end
160
+
161
+ describe "#==" do
162
+ context "when sources are equivalent" do
163
+ let(:body1) { HTTP::Request::Body.new("content") }
164
+ let(:body2) { HTTP::Request::Body.new("content") }
165
+
166
+ it "returns true" do
167
+ expect(body1).to eq body2
168
+ end
169
+ end
170
+
171
+ context "when sources are not equivalent" do
172
+ let(:body1) { HTTP::Request::Body.new("content") }
173
+ let(:body2) { HTTP::Request::Body.new(nil) }
174
+
175
+ it "returns false" do
176
+ expect(body1).not_to eq body2
177
+ end
178
+ end
179
+
180
+ context "when objects are not of the same class" do
181
+ let(:body1) { HTTP::Request::Body.new("content") }
182
+ let(:body2) { "content" }
183
+
184
+ it "returns false" do
185
+ expect(body1).not_to eq body2
186
+ end
187
+ end
188
+ end
160
189
  end
@@ -74,5 +74,25 @@ RSpec.describe HTTP::Request::Writer do
74
74
  ].join
75
75
  end
76
76
  end
77
+
78
+ context "when server won't accept any more data" do
79
+ before do
80
+ expect(io).to receive(:write).and_raise(Errno::EPIPE)
81
+ end
82
+
83
+ it "aborts silently" do
84
+ writer.stream
85
+ end
86
+ end
87
+
88
+ context "when writing to socket raises an exception" do
89
+ before do
90
+ expect(io).to receive(:write).and_raise(Errno::ECONNRESET)
91
+ end
92
+
93
+ it "raises a ConnectionError" do
94
+ expect { writer.stream }.to raise_error(HTTP::ConnectionError)
95
+ end
96
+ end
77
97
  end
78
98
  end
@@ -55,38 +55,17 @@ RSpec.describe HTTP do
55
55
  end
56
56
 
57
57
  context "with a large request body" do
58
- %w[global null per_operation].each do |timeout|
59
- context "with a #{timeout} timeout" do
60
- [16_000, 16_500, 17_000, 34_000, 68_000].each do |size|
61
- [0, rand(0..100), rand(100..1000)].each do |fuzzer|
62
- context "with a #{size} body and #{fuzzer} of fuzzing" do
63
- let(:client) { HTTP.timeout(timeout, :read => 2, :write => 2, :connect => 2) }
64
-
65
- let(:characters) { ("A".."Z").to_a }
66
- let(:request_body) do
67
- Array.new(size + fuzzer) { |i| characters[i % characters.length] }.join
68
- end
69
-
70
- it "returns a large body" do
71
- response = client.post("#{dummy.endpoint}/echo-body", :body => request_body)
72
-
73
- expect(response.body.to_s).to eq(request_body)
74
- expect(response.headers["Content-Length"].to_i).to eq(request_body.bytesize)
75
- end
76
-
77
- context "when bytesize != length" do
78
- let(:characters) { ("A".."Z").to_a.push("“") }
79
-
80
- it "returns a large body" do
81
- body = {:data => request_body}
82
- response = client.post("#{dummy.endpoint}/echo-body", :json => body)
83
-
84
- expect(CGI.unescape(response.body.to_s)).to eq(body.to_json)
85
- expect(response.headers["Content-Length"].to_i).to eq(body.to_json.bytesize)
86
- end
87
- end
88
- end
89
- end
58
+ let(:request_body) { "“" * 1_000_000 } # use multi-byte character
59
+
60
+ [:null, 6, {:read => 2, :write => 2, :connect => 2}].each do |timeout|
61
+ context "with `.timeout(#{timeout.inspect})`" do
62
+ let(:client) { HTTP.timeout(timeout) }
63
+
64
+ it "writes the whole body" do
65
+ response = client.post "#{dummy.endpoint}/echo-body", :body => request_body
66
+
67
+ expect(response.body.to_s).to eq(request_body.b)
68
+ expect(response.headers["Content-Length"].to_i).to eq request_body.bytesize
90
69
  end
91
70
  end
92
71
  end
@@ -194,7 +173,7 @@ RSpec.describe HTTP do
194
173
 
195
174
  context "with encoding option" do
196
175
  it "respects option" do
197
- response = HTTP.get "#{dummy.endpoint}/iso-8859-1", "encoding" => Encoding::BINARY
176
+ response = HTTP.get "#{dummy.endpoint}/iso-8859-1", :encoding => Encoding::BINARY
198
177
  expect(response.to_s.encoding).to eq(Encoding::BINARY)
199
178
  end
200
179
  end
@@ -202,7 +181,7 @@ RSpec.describe HTTP do
202
181
 
203
182
  context "passing a string encoding type" do
204
183
  it "finds encoding" do
205
- response = HTTP.get dummy.endpoint, "encoding" => "ascii"
184
+ response = HTTP.get dummy.endpoint, :encoding => "ascii"
206
185
  expect(response.to_s.encoding).to eq(Encoding::ASCII)
207
186
  end
208
187
  end
@@ -255,15 +234,15 @@ RSpec.describe HTTP do
255
234
 
256
235
  describe ".basic_auth" do
257
236
  it "fails when options is not a Hash" do
258
- expect { HTTP.basic_auth "[FOOBAR]" }.to raise_error
237
+ expect { HTTP.basic_auth "[FOOBAR]" }.to raise_error(NoMethodError)
259
238
  end
260
239
 
261
240
  it "fails when :pass is not given" do
262
- expect { HTTP.basic_auth :user => "[USER]" }.to raise_error
241
+ expect { HTTP.basic_auth :user => "[USER]" }.to raise_error(KeyError)
263
242
  end
264
243
 
265
244
  it "fails when :user is not given" do
266
- expect { HTTP.basic_auth :pass => "[PASS]" }.to raise_error
245
+ expect { HTTP.basic_auth :pass => "[PASS]" }.to raise_error(KeyError)
267
246
  end
268
247
 
269
248
  it "sets Authorization header with proper BasicAuth value" do
@@ -305,22 +284,8 @@ RSpec.describe HTTP do
305
284
  end
306
285
 
307
286
  describe ".timeout" do
308
- context "without timeout type" do
309
- subject(:client) { HTTP.timeout :read => 123 }
310
-
311
- it "sets timeout_class to PerOperation" do
312
- expect(client.default_options.timeout_class).
313
- to be HTTP::Timeout::PerOperation
314
- end
315
-
316
- it "sets given timeout options" do
317
- expect(client.default_options.timeout_options).
318
- to eq :read_timeout => 123
319
- end
320
- end
321
-
322
- context "with :null type" do
323
- subject(:client) { HTTP.timeout :null, :read => 123 }
287
+ context "specifying a null timeout" do
288
+ subject(:client) { HTTP.timeout :null }
324
289
 
325
290
  it "sets timeout_class to Null" do
326
291
  expect(client.default_options.timeout_class).
@@ -328,8 +293,8 @@ RSpec.describe HTTP do
328
293
  end
329
294
  end
330
295
 
331
- context "with :per_operation type" do
332
- subject(:client) { HTTP.timeout :per_operation, :read => 123 }
296
+ context "specifying per operation timeouts" do
297
+ subject(:client) { HTTP.timeout :read => 123 }
333
298
 
334
299
  it "sets timeout_class to PerOperation" do
335
300
  expect(client.default_options.timeout_class).
@@ -342,24 +307,19 @@ RSpec.describe HTTP do
342
307
  end
343
308
  end
344
309
 
345
- context "with :global type" do
346
- subject(:client) { HTTP.timeout :global, :read => 123 }
310
+ context "specifying a global timeout" do
311
+ subject(:client) { HTTP.timeout 123 }
347
312
 
348
313
  it "sets timeout_class to Global" do
349
314
  expect(client.default_options.timeout_class).
350
315
  to be HTTP::Timeout::Global
351
316
  end
352
317
 
353
- it "sets given timeout options" do
318
+ it "sets given timeout option" do
354
319
  expect(client.default_options.timeout_options).
355
- to eq :read_timeout => 123
320
+ to eq :global_timeout => 123
356
321
  end
357
322
  end
358
-
359
- it "fails with unknown timeout type" do
360
- expect { HTTP.timeout(:foobar, :read => 123) }.
361
- to raise_error(ArgumentError, /foobar/)
362
- end
363
323
  end
364
324
 
365
325
  describe ".cookies" do
@@ -476,7 +436,7 @@ RSpec.describe HTTP do
476
436
  expect { HTTP.get "http://thishostshouldnotexists.com" }.
477
437
  to raise_error HTTP::ConnectionError
478
438
 
479
- expect { HTTP.get "http://127.0.0.1:000" }.
439
+ expect { HTTP.get "http://127.0.0.1:111" }.
480
440
  to raise_error HTTP::ConnectionError
481
441
  end
482
442
  end
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.shared_context "HTTP handling" do
4
- describe "timeouts" do
5
- let(:conn_timeout) { 1 }
6
- let(:read_timeout) { 1 }
7
- let(:write_timeout) { 1 }
4
+ context "without timeouts" do
5
+ let(:options) { {:timeout_class => HTTP::Timeout::Null, :timeout_options => {}} }
6
+
7
+ it "works" do
8
+ expect(client.get(server.endpoint).body.to_s).to eq("<!doctype html>")
9
+ end
10
+ end
11
+
12
+ context "with a per operation timeout" do
13
+ let(:response) { client.get(server.endpoint).body.to_s }
8
14
 
9
15
  let(:options) do
10
16
  {
11
- :timeout_class => timeout_class,
17
+ :timeout_class => HTTP::Timeout::PerOperation,
12
18
  :timeout_options => {
13
19
  :connect_timeout => conn_timeout,
14
20
  :read_timeout => read_timeout,
@@ -16,87 +22,77 @@ RSpec.shared_context "HTTP handling" do
16
22
  }
17
23
  }
18
24
  end
25
+ let(:conn_timeout) { 1 }
26
+ let(:read_timeout) { 1 }
27
+ let(:write_timeout) { 1 }
19
28
 
20
- context "without timeouts" do
21
- let(:timeout_class) { HTTP::Timeout::Null }
22
- let(:conn_timeout) { 0 }
23
- let(:read_timeout) { 0 }
24
- let(:write_timeout) { 0 }
25
-
26
- it "works" do
27
- expect(client.get(server.endpoint).body.to_s).to eq("<!doctype html>")
28
- end
29
+ it "works" do
30
+ expect(response).to eq("<!doctype html>")
29
31
  end
30
32
 
31
- context "with a per operation timeout" do
32
- let(:timeout_class) { HTTP::Timeout::PerOperation }
33
-
34
- let(:response) { client.get(server.endpoint).body.to_s }
35
-
36
- it "works" do
37
- expect(response).to eq("<!doctype html>")
38
- end
33
+ context "connection" do
34
+ context "of 1" do
35
+ let(:conn_timeout) { 1 }
39
36
 
40
- context "connection" do
41
- context "of 1" do
42
- let(:conn_timeout) { 1 }
43
-
44
- it "does not time out" do
45
- expect { response }.to_not raise_error
46
- end
37
+ it "does not time out" do
38
+ expect { response }.to_not raise_error
47
39
  end
48
40
  end
41
+ end
49
42
 
50
- context "read" do
51
- context "of 0" do
52
- let(:read_timeout) { 0 }
43
+ context "read" do
44
+ context "of 0" do
45
+ let(:read_timeout) { 0 }
53
46
 
54
- it "times out", :flaky do
55
- expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
56
- end
47
+ it "times out", :flaky do
48
+ expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
57
49
  end
50
+ end
58
51
 
59
- context "of 2.5" do
60
- let(:read_timeout) { 2.5 }
52
+ context "of 2.5" do
53
+ let(:read_timeout) { 2.5 }
61
54
 
62
- it "does not time out", :flaky do
63
- expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
64
- end
55
+ it "does not time out", :flaky do
56
+ expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
65
57
  end
66
58
  end
67
59
  end
60
+ end
68
61
 
69
- context "with a global timeout" do
70
- let(:timeout_class) { HTTP::Timeout::Global }
71
-
72
- let(:conn_timeout) { 0 }
73
- let(:read_timeout) { 1 }
74
- let(:write_timeout) { 0 }
75
-
76
- let(:response) { client.get(server.endpoint).body.to_s }
62
+ context "with a global timeout" do
63
+ let(:options) do
64
+ {
65
+ :timeout_class => HTTP::Timeout::Global,
66
+ :timeout_options => {
67
+ :global_timeout => global_timeout
68
+ }
69
+ }
70
+ end
71
+ let(:global_timeout) { 1 }
77
72
 
78
- it "errors if connecting takes too long" do
79
- expect(TCPSocket).to receive(:open) do
80
- sleep 1.25
81
- end
73
+ let(:response) { client.get(server.endpoint).body.to_s }
82
74
 
83
- expect { response }.to raise_error(HTTP::TimeoutError, /execution/)
75
+ it "errors if connecting takes too long" do
76
+ expect(TCPSocket).to receive(:open) do
77
+ sleep 1.25
84
78
  end
85
79
 
86
- it "errors if reading takes too long" do
87
- expect { client.get("#{server.endpoint}/sleep").body.to_s }.
88
- to raise_error(HTTP::TimeoutError, /Timed out/)
89
- end
80
+ expect { response }.to raise_error(HTTP::TimeoutError, /execution/)
81
+ end
90
82
 
91
- context "it resets state when reusing connections" do
92
- let(:extra_options) { {:persistent => server.endpoint} }
83
+ it "errors if reading takes too long" do
84
+ expect { client.get("#{server.endpoint}/sleep").body.to_s }.
85
+ to raise_error(HTTP::TimeoutError, /Timed out/)
86
+ end
93
87
 
94
- let(:read_timeout) { 2.5 }
88
+ context "it resets state when reusing connections" do
89
+ let(:extra_options) { {:persistent => server.endpoint} }
95
90
 
96
- it "does not timeout", :flaky do
97
- client.get("#{server.endpoint}/sleep").body.to_s
98
- client.get("#{server.endpoint}/sleep").body.to_s
99
- end
91
+ let(:global_timeout) { 2.5 }
92
+
93
+ it "does not timeout", :flaky do
94
+ client.get("#{server.endpoint}/sleep").body.to_s
95
+ client.get("#{server.endpoint}/sleep").body.to_s
100
96
  end
101
97
  end
102
98
  end