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.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +110 -13
  4. data/http.gemspec +32 -29
  5. data/lib/http/base64.rb +11 -1
  6. data/lib/http/chainable/helpers.rb +62 -0
  7. data/lib/http/chainable/verbs.rb +136 -0
  8. data/lib/http/chainable.rb +232 -136
  9. data/lib/http/client.rb +158 -127
  10. data/lib/http/connection/internals.rb +141 -0
  11. data/lib/http/connection.rb +126 -97
  12. data/lib/http/content_type.rb +61 -6
  13. data/lib/http/errors.rb +25 -1
  14. data/lib/http/feature.rb +65 -5
  15. data/lib/http/features/auto_deflate.rb +124 -17
  16. data/lib/http/features/auto_inflate.rb +38 -15
  17. data/lib/http/features/caching/entry.rb +178 -0
  18. data/lib/http/features/caching/in_memory_store.rb +63 -0
  19. data/lib/http/features/caching.rb +216 -0
  20. data/lib/http/features/digest_auth.rb +234 -0
  21. data/lib/http/features/instrumentation.rb +97 -17
  22. data/lib/http/features/logging.rb +183 -5
  23. data/lib/http/features/normalize_uri.rb +17 -0
  24. data/lib/http/features/raise_error.rb +18 -3
  25. data/lib/http/form_data/composite_io.rb +106 -0
  26. data/lib/http/form_data/file.rb +95 -0
  27. data/lib/http/form_data/multipart/param.rb +62 -0
  28. data/lib/http/form_data/multipart.rb +106 -0
  29. data/lib/http/form_data/part.rb +52 -0
  30. data/lib/http/form_data/readable.rb +58 -0
  31. data/lib/http/form_data/urlencoded.rb +175 -0
  32. data/lib/http/form_data/version.rb +8 -0
  33. data/lib/http/form_data.rb +102 -0
  34. data/lib/http/headers/known.rb +3 -0
  35. data/lib/http/headers/normalizer.rb +17 -36
  36. data/lib/http/headers.rb +172 -65
  37. data/lib/http/mime_type/adapter.rb +24 -9
  38. data/lib/http/mime_type/json.rb +19 -4
  39. data/lib/http/mime_type.rb +21 -3
  40. data/lib/http/options/definitions.rb +189 -0
  41. data/lib/http/options.rb +172 -125
  42. data/lib/http/redirector.rb +80 -75
  43. data/lib/http/request/body.rb +87 -6
  44. data/lib/http/request/builder.rb +184 -0
  45. data/lib/http/request/proxy.rb +83 -0
  46. data/lib/http/request/writer.rb +76 -16
  47. data/lib/http/request.rb +214 -98
  48. data/lib/http/response/body.rb +103 -18
  49. data/lib/http/response/inflater.rb +35 -7
  50. data/lib/http/response/parser.rb +98 -4
  51. data/lib/http/response/status/reasons.rb +2 -4
  52. data/lib/http/response/status.rb +141 -31
  53. data/lib/http/response.rb +219 -61
  54. data/lib/http/retriable/delay_calculator.rb +38 -11
  55. data/lib/http/retriable/errors.rb +21 -0
  56. data/lib/http/retriable/performer.rb +82 -38
  57. data/lib/http/session.rb +280 -0
  58. data/lib/http/timeout/global.rb +147 -34
  59. data/lib/http/timeout/null.rb +155 -9
  60. data/lib/http/timeout/per_operation.rb +139 -18
  61. data/lib/http/uri/normalizer.rb +82 -0
  62. data/lib/http/uri/parsing.rb +182 -0
  63. data/lib/http/uri.rb +289 -124
  64. data/lib/http/version.rb +2 -1
  65. data/lib/http.rb +11 -2
  66. data/sig/http.rbs +1619 -0
  67. metadata +36 -171
  68. data/.github/workflows/ci.yml +0 -67
  69. data/.gitignore +0 -15
  70. data/.rspec +0 -1
  71. data/.rubocop/layout.yml +0 -8
  72. data/.rubocop/metrics.yml +0 -4
  73. data/.rubocop/rspec.yml +0 -9
  74. data/.rubocop/style.yml +0 -32
  75. data/.rubocop.yml +0 -11
  76. data/.rubocop_todo.yml +0 -219
  77. data/.yardopts +0 -2
  78. data/CHANGELOG.md +0 -67
  79. data/CHANGES_OLD.md +0 -1002
  80. data/CONTRIBUTING.md +0 -26
  81. data/Gemfile +0 -51
  82. data/Guardfile +0 -18
  83. data/Rakefile +0 -64
  84. data/SECURITY.md +0 -17
  85. data/lib/http/headers/mixin.rb +0 -34
  86. data/lib/http/retriable/client.rb +0 -37
  87. data/logo.png +0 -0
  88. data/spec/lib/http/client_spec.rb +0 -556
  89. data/spec/lib/http/connection_spec.rb +0 -88
  90. data/spec/lib/http/content_type_spec.rb +0 -47
  91. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  92. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  93. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  94. data/spec/lib/http/features/logging_spec.rb +0 -65
  95. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  96. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  97. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  98. data/spec/lib/http/headers_spec.rb +0 -527
  99. data/spec/lib/http/options/body_spec.rb +0 -15
  100. data/spec/lib/http/options/features_spec.rb +0 -33
  101. data/spec/lib/http/options/form_spec.rb +0 -15
  102. data/spec/lib/http/options/headers_spec.rb +0 -24
  103. data/spec/lib/http/options/json_spec.rb +0 -15
  104. data/spec/lib/http/options/merge_spec.rb +0 -68
  105. data/spec/lib/http/options/new_spec.rb +0 -30
  106. data/spec/lib/http/options/proxy_spec.rb +0 -20
  107. data/spec/lib/http/options_spec.rb +0 -13
  108. data/spec/lib/http/redirector_spec.rb +0 -530
  109. data/spec/lib/http/request/body_spec.rb +0 -211
  110. data/spec/lib/http/request/writer_spec.rb +0 -121
  111. data/spec/lib/http/request_spec.rb +0 -234
  112. data/spec/lib/http/response/body_spec.rb +0 -85
  113. data/spec/lib/http/response/parser_spec.rb +0 -74
  114. data/spec/lib/http/response/status_spec.rb +0 -253
  115. data/spec/lib/http/response_spec.rb +0 -262
  116. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  117. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  118. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  119. data/spec/lib/http/uri_spec.rb +0 -71
  120. data/spec/lib/http_spec.rb +0 -535
  121. data/spec/regression_specs.rb +0 -24
  122. data/spec/spec_helper.rb +0 -89
  123. data/spec/support/black_hole.rb +0 -13
  124. data/spec/support/capture_warning.rb +0 -10
  125. data/spec/support/dummy_server/servlet.rb +0 -203
  126. data/spec/support/dummy_server.rb +0 -44
  127. data/spec/support/fakeio.rb +0 -21
  128. data/spec/support/fuubar.rb +0 -21
  129. data/spec/support/http_handling_shared.rb +0 -190
  130. data/spec/support/proxy_server.rb +0 -39
  131. data/spec/support/servers/config.rb +0 -11
  132. data/spec/support/servers/runner.rb +0 -19
  133. data/spec/support/simplecov.rb +0 -19
  134. data/spec/support/ssl_helper.rb +0 -104
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::AutoDeflate do
4
- subject { HTTP::Features::AutoDeflate.new }
5
-
6
- it "raises error for wrong type" do
7
- expect { HTTP::Features::AutoDeflate.new(:method => :wrong) }.
8
- to raise_error(HTTP::Error) { |error|
9
- expect(error.message).to eq("Only gzip and deflate methods are supported")
10
- }
11
- end
12
-
13
- it "accepts gzip method" do
14
- expect(HTTP::Features::AutoDeflate.new(:method => :gzip).method).to eq "gzip"
15
- end
16
-
17
- it "accepts deflate method" do
18
- expect(HTTP::Features::AutoDeflate.new(:method => :deflate).method).to eq "deflate"
19
- end
20
-
21
- it "accepts string as method" do
22
- expect(HTTP::Features::AutoDeflate.new(:method => "gzip").method).to eq "gzip"
23
- end
24
-
25
- it "uses gzip by default" do
26
- expect(subject.method).to eq("gzip")
27
- end
28
-
29
- describe "#deflated_body" do
30
- let(:body) { %w[bees cows] }
31
- let(:deflated_body) { subject.deflated_body(body) }
32
-
33
- context "when method is gzip" do
34
- subject { HTTP::Features::AutoDeflate.new(:method => :gzip) }
35
-
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
43
-
44
- expect(deflated_body.each.to_a.join).to eq gzipped
45
- end
46
-
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
54
-
55
- expect(deflated_body.size).to eq gzipped.bytesize
56
- expect(deflated_body.each.to_a.join).to eq gzipped
57
- end
58
- end
59
-
60
- context "when method is deflate" do
61
- subject { HTTP::Features::AutoDeflate.new(:method => :deflate) }
62
-
63
- it "returns object which yields deflated content of the given body" do
64
- deflated = Zlib::Deflate.deflate("beescows")
65
-
66
- expect(deflated_body.each.to_a.join).to eq deflated
67
- end
68
-
69
- it "caches compressed content when size is called" do
70
- deflated = Zlib::Deflate.deflate("beescows")
71
-
72
- expect(deflated_body.size).to eq deflated.bytesize
73
- expect(deflated_body.each.to_a.join).to eq deflated
74
- end
75
- end
76
- end
77
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::AutoInflate do
4
- subject(:feature) { HTTP::Features::AutoInflate.new }
5
-
6
- let(:connection) { double }
7
- let(:headers) { {} }
8
-
9
- let(:response) do
10
- HTTP::Response.new(
11
- :version => "1.1",
12
- :status => 200,
13
- :headers => headers,
14
- :connection => connection,
15
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
16
- )
17
- end
18
-
19
- describe "#wrap_response" do
20
- subject(:result) { feature.wrap_response(response) }
21
-
22
- context "when there is no Content-Encoding header" do
23
- it "returns original request" do
24
- expect(result).to be response
25
- end
26
- end
27
-
28
- context "for identity Content-Encoding header" do
29
- let(:headers) { {:content_encoding => "identity"} }
30
-
31
- it "returns original request" do
32
- expect(result).to be response
33
- end
34
- end
35
-
36
- context "for unknown Content-Encoding header" do
37
- let(:headers) { {:content_encoding => "not-supported"} }
38
-
39
- it "returns original request" do
40
- expect(result).to be response
41
- end
42
- end
43
-
44
- context "for deflate Content-Encoding header" do
45
- let(:headers) { {:content_encoding => "deflate"} }
46
-
47
- it "returns a HTTP::Response wrapping the inflated response body" do
48
- expect(result.body).to be_instance_of HTTP::Response::Body
49
- end
50
- end
51
-
52
- context "for gzip Content-Encoding header" do
53
- let(:headers) { {:content_encoding => "gzip"} }
54
-
55
- it "returns a HTTP::Response wrapping the inflated response body" do
56
- expect(result.body).to be_instance_of HTTP::Response::Body
57
- end
58
- end
59
-
60
- context "for x-gzip Content-Encoding header" do
61
- let(:headers) { {:content_encoding => "x-gzip"} }
62
-
63
- it "returns a HTTP::Response wrapping the inflated response body" do
64
- expect(result.body).to be_instance_of HTTP::Response::Body
65
- end
66
- end
67
-
68
- # TODO(ixti): We should refactor API to either make uri non-optional,
69
- # or add reference to request into response object (better).
70
- context "when response has uri" do
71
- let(:response) do
72
- HTTP::Response.new(
73
- :version => "1.1",
74
- :status => 200,
75
- :headers => {:content_encoding => "gzip"},
76
- :connection => connection,
77
- :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
78
- )
79
- end
80
-
81
- it "preserves uri in wrapped response" do
82
- expect(result.uri).to eq HTTP::URI.parse("https://example.com")
83
- end
84
- end
85
- end
86
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::Instrumentation do
4
- subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) }
5
-
6
- let(:instrumenter) { TestInstrumenter.new }
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
-
28
- describe "logging the request" do
29
- let(:request) do
30
- HTTP::Request.new(
31
- :verb => :post,
32
- :uri => "https://example.com/",
33
- :headers => {:accept => "application/json"},
34
- :body => '{"hello": "world!"}'
35
- )
36
- end
37
-
38
- it "should log the request" do
39
- feature.wrap_request(request)
40
-
41
- expect(instrumenter.output[:start]).to eq(:request => request)
42
- end
43
- end
44
-
45
- describe "logging the response" do
46
- let(:response) do
47
- HTTP::Response.new(
48
- :version => "1.1",
49
- :status => 200,
50
- :headers => {:content_type => "application/json"},
51
- :body => '{"success": true}',
52
- :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
53
- )
54
- end
55
-
56
- it "should log the response" do
57
- feature.wrap_response(response)
58
-
59
- expect(instrumenter.output[:finish]).to eq(:response => response)
60
- end
61
- end
62
-
63
- describe "logging errors" do
64
- let(:request) do
65
- HTTP::Request.new(
66
- :verb => :post,
67
- :uri => "https://example.com/",
68
- :headers => {:accept => "application/json"},
69
- :body => '{"hello": "world!"}'
70
- )
71
- end
72
-
73
- let(:error) { HTTP::TimeoutError.new }
74
-
75
- it "should log the error" do
76
- feature.on_error(request, error)
77
-
78
- expect(instrumenter.output[:finish]).to eq(:request => request, :error => error)
79
- end
80
- end
81
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "logger"
4
-
5
- RSpec.describe HTTP::Features::Logging do
6
- subject(:feature) do
7
- logger = Logger.new(logdev)
8
- logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
9
-
10
- described_class.new(:logger => logger)
11
- end
12
-
13
- let(:logdev) { StringIO.new }
14
-
15
- describe "logging the request" do
16
- let(:request) do
17
- HTTP::Request.new(
18
- :verb => :post,
19
- :uri => "https://example.com/",
20
- :headers => {:accept => "application/json"},
21
- :body => '{"hello": "world!"}'
22
- )
23
- end
24
-
25
- it "should log the request" do
26
- feature.wrap_request(request)
27
-
28
- expect(logdev.string).to eq <<~OUTPUT
29
- ** INFO **
30
- > POST https://example.com/
31
- ** DEBUG **
32
- Accept: application/json
33
- Host: example.com
34
- User-Agent: http.rb/#{HTTP::VERSION}
35
-
36
- {"hello": "world!"}
37
- OUTPUT
38
- end
39
- end
40
-
41
- describe "logging the response" do
42
- let(:response) do
43
- HTTP::Response.new(
44
- :version => "1.1",
45
- :status => 200,
46
- :headers => {:content_type => "application/json"},
47
- :body => '{"success": true}',
48
- :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
49
- )
50
- end
51
-
52
- it "should log the response" do
53
- feature.wrap_response(response)
54
-
55
- expect(logdev.string).to eq <<~OUTPUT
56
- ** INFO **
57
- < 200 OK
58
- ** DEBUG **
59
- Content-Type: application/json
60
-
61
- {"success": true}
62
- OUTPUT
63
- end
64
- end
65
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Features::RaiseError do
4
- subject(:feature) { described_class.new(ignore: ignore) }
5
-
6
- let(:connection) { double }
7
- let(:status) { 200 }
8
- let(:ignore) { [] }
9
-
10
- describe "#wrap_response" do
11
- subject(:result) { feature.wrap_response(response) }
12
-
13
- let(:response) do
14
- HTTP::Response.new(
15
- version: "1.1",
16
- status: status,
17
- headers: {},
18
- connection: connection,
19
- request: HTTP::Request.new(verb: :get, uri: "https://example.com")
20
- )
21
- end
22
-
23
- context "when status is 200" do
24
- it "returns original request" do
25
- expect(result).to be response
26
- end
27
- end
28
-
29
- context "when status is 399" do
30
- let(:status) { 399 }
31
-
32
- it "returns original request" do
33
- expect(result).to be response
34
- end
35
- end
36
-
37
- context "when status is 400" do
38
- let(:status) { 400 }
39
-
40
- it "raises" do
41
- expect { result }.to raise_error(HTTP::StatusError, "Unexpected status code 400")
42
- end
43
- end
44
-
45
- context "when status is 599" do
46
- let(:status) { 599 }
47
-
48
- it "raises" do
49
- expect { result }.to raise_error(HTTP::StatusError, "Unexpected status code 599")
50
- end
51
- end
52
-
53
- context "when error status is ignored" do
54
- let(:status) { 500 }
55
- let(:ignore) { [500] }
56
-
57
- it "returns original request" do
58
- expect(result).to be response
59
- end
60
- end
61
- end
62
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Headers::Mixin do
4
- let :dummy_class do
5
- Class.new do
6
- include HTTP::Headers::Mixin
7
-
8
- def initialize(headers)
9
- @headers = headers
10
- end
11
- end
12
- end
13
-
14
- let(:headers) { HTTP::Headers.new }
15
- let(:dummy) { dummy_class.new headers }
16
-
17
- describe "#headers" do
18
- it "returns @headers instance variable" do
19
- expect(dummy.headers).to be headers
20
- end
21
- end
22
-
23
- describe "#[]" do
24
- it "proxies to headers#[]" do
25
- expect(headers).to receive(:[]).with(:accept)
26
- dummy[:accept]
27
- end
28
- end
29
-
30
- describe "#[]=" do
31
- it "proxies to headers#[]" do
32
- expect(headers).to receive(:[]=).with(:accept, "text/plain")
33
- dummy[:accept] = "text/plain"
34
- end
35
- end
36
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Headers::Normalizer do
4
- subject(:normalizer) { described_class.new }
5
-
6
- include_context RSpec::Memory
7
-
8
- describe "#call" do
9
- it "normalizes the header" do
10
- expect(normalizer.call("content_type")).to eq "Content-Type"
11
- end
12
-
13
- it "returns a non-frozen string" do
14
- expect(normalizer.call("content_type")).not_to be_frozen
15
- end
16
-
17
- it "evicts the oldest item when cache is full" do
18
- max_headers = (1..described_class::Cache::MAX_SIZE).map { |i| "Header#{i}" }
19
- max_headers.each { |header| normalizer.call(header) }
20
- normalizer.call("New-Header")
21
- cache_store = normalizer.instance_variable_get(:@cache).instance_variable_get(:@store)
22
- expect(cache_store.keys).to eq(max_headers[1..] + ["New-Header"])
23
- end
24
-
25
- it "retuns mutable strings" do
26
- normalized_headers = Array.new(3) { normalizer.call("content_type") }
27
-
28
- expect(normalized_headers)
29
- .to satisfy { |arr| arr.uniq.size == 1 }
30
- .and(satisfy { |arr| arr.map(&:object_id).uniq.size == normalized_headers.size })
31
- .and(satisfy { |arr| arr.none?(&:frozen?) })
32
- end
33
-
34
- it "allocates minimal memory for normalization of the same header" do
35
- normalizer.call("accept") # XXX: Ensure normalizer is pre-allocated
36
-
37
- # On first call it is expected to allocate during normalization
38
- expect { normalizer.call("content_type") }.to limit_allocations(
39
- Array => 1,
40
- MatchData => 1,
41
- String => 6
42
- )
43
-
44
- # On subsequent call it is expected to only allocate copy of a cached string
45
- expect { normalizer.call("content_type") }.to limit_allocations(
46
- Array => 0,
47
- MatchData => 0,
48
- String => 1
49
- )
50
- end
51
- end
52
- end