http 5.3.1 → 6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +241 -41
- data/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/UPGRADING.md +491 -0
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/deps.rbs +122 -0
- data/sig/http.rbs +1619 -0
- data/test/http/base64_test.rb +28 -0
- data/test/http/client_test.rb +739 -0
- data/test/http/connection_test.rb +1533 -0
- data/test/http/content_type_test.rb +190 -0
- data/test/http/errors_test.rb +28 -0
- data/test/http/feature_test.rb +49 -0
- data/test/http/features/auto_deflate_test.rb +317 -0
- data/test/http/features/auto_inflate_test.rb +213 -0
- data/test/http/features/caching_test.rb +942 -0
- data/test/http/features/digest_auth_test.rb +996 -0
- data/test/http/features/instrumentation_test.rb +246 -0
- data/test/http/features/logging_test.rb +654 -0
- data/test/http/features/normalize_uri_test.rb +41 -0
- data/test/http/features/raise_error_test.rb +77 -0
- data/test/http/form_data/composite_io_test.rb +215 -0
- data/test/http/form_data/file_test.rb +255 -0
- data/test/http/form_data/fixtures/the-http-gem.info +1 -0
- data/test/http/form_data/multipart_test.rb +303 -0
- data/test/http/form_data/part_test.rb +90 -0
- data/test/http/form_data/urlencoded_test.rb +164 -0
- data/test/http/form_data_test.rb +232 -0
- data/test/http/headers/normalizer_test.rb +93 -0
- data/test/http/headers_test.rb +888 -0
- data/test/http/mime_type/json_test.rb +39 -0
- data/test/http/mime_type_test.rb +150 -0
- data/test/http/options/base_uri_test.rb +148 -0
- data/test/http/options/body_test.rb +21 -0
- data/test/http/options/features_test.rb +38 -0
- data/test/http/options/form_test.rb +21 -0
- data/test/http/options/headers_test.rb +32 -0
- data/test/http/options/json_test.rb +21 -0
- data/test/http/options/merge_test.rb +78 -0
- data/test/http/options/new_test.rb +37 -0
- data/test/http/options/proxy_test.rb +32 -0
- data/test/http/options_test.rb +575 -0
- data/test/http/redirector_test.rb +639 -0
- data/test/http/request/body_test.rb +318 -0
- data/test/http/request/builder_test.rb +623 -0
- data/test/http/request/writer_test.rb +391 -0
- data/test/http/request_test.rb +1733 -0
- data/test/http/response/body_test.rb +292 -0
- data/test/http/response/parser_test.rb +105 -0
- data/test/http/response/status_test.rb +322 -0
- data/test/http/response_test.rb +502 -0
- data/test/http/retriable/delay_calculator_test.rb +194 -0
- data/test/http/retriable/errors_test.rb +71 -0
- data/test/http/retriable/performer_test.rb +551 -0
- data/test/http/session_test.rb +424 -0
- data/test/http/timeout/global_test.rb +239 -0
- data/test/http/timeout/null_test.rb +218 -0
- data/test/http/timeout/per_operation_test.rb +220 -0
- data/test/http/uri/normalizer_test.rb +89 -0
- data/test/http/uri_test.rb +1140 -0
- data/test/http/version_test.rb +15 -0
- data/test/http_test.rb +818 -0
- data/test/regression_tests.rb +27 -0
- data/test/support/dummy_server/encoding_routes.rb +47 -0
- data/test/support/dummy_server/routes.rb +201 -0
- data/test/support/dummy_server/servlet.rb +81 -0
- data/test/support/dummy_server.rb +200 -0
- data/{spec → test}/support/fakeio.rb +2 -2
- data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
- data/test/support/http_handling_shared/timeout_tests.rb +134 -0
- data/test/support/http_handling_shared.rb +11 -0
- data/test/support/proxy_server.rb +207 -0
- data/test/support/servers/runner.rb +67 -0
- data/{spec → test}/support/simplecov.rb +11 -2
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +108 -168
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGES_OLD.md +0 -1002
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
- /data/{spec → test}/support/capture_warning.rb +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPTimeoutNullTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Timeout::Null*"
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
super
|
|
10
|
+
@io = fake(wait_readable: true, wait_writable: true)
|
|
11
|
+
@socket = fake(to_io: @io, closed?: false)
|
|
12
|
+
@timeout = HTTP::Timeout::Null.new
|
|
13
|
+
@timeout.instance_variable_set(:@socket, @socket)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# -- #initialize --
|
|
17
|
+
|
|
18
|
+
def test_initialize_stores_provided_options_compacted
|
|
19
|
+
t = HTTP::Timeout::Null.new(read_timeout: 5, write_timeout: 10)
|
|
20
|
+
|
|
21
|
+
assert_equal({ read_timeout: 5, write_timeout: 10 }, t.options)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# -- #start_tls --
|
|
25
|
+
|
|
26
|
+
def test_start_tls_skips_hostname_and_sync_close_when_not_responding
|
|
27
|
+
ssl_socket = fake(connect: nil)
|
|
28
|
+
ssl_socket_class = fake(new: ssl_socket)
|
|
29
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
30
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
31
|
+
@timeout.start_tls("example.com", ssl_socket_class, ssl_context)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_start_tls_skips_post_connection_check_when_verify_mode_not_verify_peer
|
|
35
|
+
post_connection_check_called = false
|
|
36
|
+
ssl_socket = fake(
|
|
37
|
+
connect: nil,
|
|
38
|
+
"hostname=": ->(*) {},
|
|
39
|
+
"sync_close=": ->(*) {},
|
|
40
|
+
post_connection_check: ->(*) { post_connection_check_called = true }
|
|
41
|
+
)
|
|
42
|
+
ssl_socket_class = fake(new: ssl_socket)
|
|
43
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
44
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
45
|
+
@timeout.start_tls("example.com", ssl_socket_class, ssl_context)
|
|
46
|
+
|
|
47
|
+
refute post_connection_check_called
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_start_tls_calls_post_connection_check_when_verify_peer_and_verify_hostname
|
|
51
|
+
post_connection_check_called = false
|
|
52
|
+
post_connection_check_arg = nil
|
|
53
|
+
ssl_socket = fake(
|
|
54
|
+
connect: nil,
|
|
55
|
+
"hostname=": ->(*) {},
|
|
56
|
+
"sync_close=": ->(*) {},
|
|
57
|
+
post_connection_check: lambda { |host|
|
|
58
|
+
post_connection_check_called = true
|
|
59
|
+
post_connection_check_arg = host
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
ssl_socket_class = fake(new: ssl_socket)
|
|
63
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
64
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
65
|
+
ssl_context.verify_hostname = true
|
|
66
|
+
@timeout.start_tls("example.com", ssl_socket_class, ssl_context)
|
|
67
|
+
|
|
68
|
+
assert post_connection_check_called
|
|
69
|
+
assert_equal "example.com", post_connection_check_arg
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_start_tls_skips_post_connection_check_when_verify_hostname_false
|
|
73
|
+
post_connection_check_called = false
|
|
74
|
+
ssl_socket = fake(
|
|
75
|
+
connect: nil,
|
|
76
|
+
"hostname=": ->(*) {},
|
|
77
|
+
"sync_close=": ->(*) {},
|
|
78
|
+
post_connection_check: ->(*) { post_connection_check_called = true }
|
|
79
|
+
)
|
|
80
|
+
ssl_socket_class = fake(new: ssl_socket)
|
|
81
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
82
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
83
|
+
ssl_context.verify_hostname = false
|
|
84
|
+
@timeout.start_tls("example.com", ssl_socket_class, ssl_context)
|
|
85
|
+
|
|
86
|
+
refute post_connection_check_called
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# -- #rescue_readable (private) --
|
|
90
|
+
|
|
91
|
+
def test_rescue_readable_yields_the_block
|
|
92
|
+
assert_equal :ok, @timeout.send(:rescue_readable, 1) { :ok }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_rescue_readable_when_wait_readable_raised_and_wait_succeeds_retries
|
|
96
|
+
call_count = 0
|
|
97
|
+
result = @timeout.send(:rescue_readable, 1) do
|
|
98
|
+
raise IO::EAGAINWaitReadable if (call_count += 1) == 1
|
|
99
|
+
|
|
100
|
+
:done
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
assert_equal :done, result
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_rescue_readable_when_wait_readable_raised_and_wait_times_out_raises_timeout_error
|
|
107
|
+
io_with_nil_wait = fake(wait_readable: nil, wait_writable: true)
|
|
108
|
+
socket_with_nil_wait = fake(to_io: io_with_nil_wait, closed?: false)
|
|
109
|
+
@timeout.instance_variable_set(:@socket, socket_with_nil_wait)
|
|
110
|
+
|
|
111
|
+
err = assert_raises(HTTP::TimeoutError) do
|
|
112
|
+
@timeout.send(:rescue_readable, 1) { raise IO::EAGAINWaitReadable }
|
|
113
|
+
end
|
|
114
|
+
assert_match(/Read timed out/, err.message)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# -- #rescue_writable (private) --
|
|
118
|
+
|
|
119
|
+
def test_rescue_writable_yields_the_block
|
|
120
|
+
assert_equal :ok, @timeout.send(:rescue_writable, 1) { :ok }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_rescue_writable_when_wait_writable_raised_and_wait_succeeds_retries
|
|
124
|
+
call_count = 0
|
|
125
|
+
result = @timeout.send(:rescue_writable, 1) do
|
|
126
|
+
raise IO::EAGAINWaitWritable if (call_count += 1) == 1
|
|
127
|
+
|
|
128
|
+
:done
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
assert_equal :done, result
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_rescue_writable_when_wait_writable_raised_and_wait_times_out_raises_timeout_error
|
|
135
|
+
io_with_nil_wait = fake(wait_readable: true, wait_writable: nil)
|
|
136
|
+
socket_with_nil_wait = fake(to_io: io_with_nil_wait, closed?: false)
|
|
137
|
+
@timeout.instance_variable_set(:@socket, socket_with_nil_wait)
|
|
138
|
+
|
|
139
|
+
err = assert_raises(HTTP::TimeoutError) do
|
|
140
|
+
@timeout.send(:rescue_writable, 1) { raise IO::EAGAINWaitWritable }
|
|
141
|
+
end
|
|
142
|
+
assert_match(/Write timed out/, err.message)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# -- NATIVE_CONNECT_TIMEOUT --
|
|
146
|
+
|
|
147
|
+
def test_native_connect_timeout_is_true_on_ruby_3_4_plus
|
|
148
|
+
assert_equal RUBY_VERSION >= "3.4", HTTP::Timeout::Null::NATIVE_CONNECT_TIMEOUT
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# -- #open_socket (private) --
|
|
152
|
+
|
|
153
|
+
def test_open_socket_opens_a_socket_without_timeout
|
|
154
|
+
tcp_socket = fake(closed?: false)
|
|
155
|
+
socket_class = fake(open: tcp_socket)
|
|
156
|
+
result = @timeout.send(:open_socket, socket_class, "example.com", 80)
|
|
157
|
+
|
|
158
|
+
assert_same tcp_socket, result
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_open_socket_passes_connect_timeout_natively_when_supported
|
|
162
|
+
received_args = nil
|
|
163
|
+
stub_open = lambda do |*args, **kwargs|
|
|
164
|
+
received_args = [args, kwargs]
|
|
165
|
+
fake(closed?: false)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
@timeout.stub(:native_timeout?, true) do
|
|
169
|
+
TCPSocket.stub(:open, stub_open) do
|
|
170
|
+
@timeout.send(:open_socket, TCPSocket, "127.0.0.1", 1, connect_timeout: 5)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
assert_equal [["127.0.0.1", 1], { connect_timeout: 5 }], received_args
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_open_socket_does_not_pass_connect_timeout_to_non_tcp_socket_classes
|
|
178
|
+
received_args = nil
|
|
179
|
+
tcp_socket = fake(closed?: false)
|
|
180
|
+
socket_class = fake(open: proc { |*args|
|
|
181
|
+
received_args = args
|
|
182
|
+
tcp_socket
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
@timeout.send(:open_socket, socket_class, "example.com", 80, connect_timeout: 5)
|
|
186
|
+
|
|
187
|
+
assert_equal ["example.com", 80], received_args
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def test_open_socket_converts_io_timeout_error_to_connect_timeout_error
|
|
191
|
+
socket_class = fake(open: proc { |*| raise IO::TimeoutError, "Connect timed out!" })
|
|
192
|
+
|
|
193
|
+
err = assert_raises(HTTP::ConnectTimeoutError) do
|
|
194
|
+
@timeout.send(:open_socket, socket_class, "example.com", 80, connect_timeout: 5)
|
|
195
|
+
end
|
|
196
|
+
assert_match(/Connect timed out/, err.message)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# -- #native_timeout? (private) --
|
|
200
|
+
|
|
201
|
+
if RUBY_VERSION >= "3.4"
|
|
202
|
+
def test_native_timeout_returns_true_for_tcp_socket_on_ruby_3_4_plus
|
|
203
|
+
assert @timeout.send(:native_timeout?, TCPSocket)
|
|
204
|
+
end
|
|
205
|
+
else
|
|
206
|
+
def test_native_timeout_returns_false_for_tcp_socket_on_ruby_below_3_4
|
|
207
|
+
refute @timeout.send(:native_timeout?, TCPSocket)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def test_native_timeout_returns_false_for_non_tcp_socket_classes
|
|
212
|
+
refute @timeout.send(:native_timeout?, OpenSSL::SSL::SSLSocket)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def test_native_timeout_returns_false_for_non_class_objects
|
|
216
|
+
refute @timeout.send(:native_timeout?, fake(open: nil))
|
|
217
|
+
end
|
|
218
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPTimeoutPerOperationTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Timeout::PerOperation*"
|
|
7
|
+
|
|
8
|
+
# -- instance tests --
|
|
9
|
+
|
|
10
|
+
def setup
|
|
11
|
+
super
|
|
12
|
+
@io = fake(wait_readable: true, wait_writable: true)
|
|
13
|
+
@socket = fake(to_io: @io, closed?: false)
|
|
14
|
+
@timeout = HTTP::Timeout::PerOperation.new(connect_timeout: 1, read_timeout: 1, write_timeout: 1)
|
|
15
|
+
@timeout.instance_variable_set(:@socket, @socket)
|
|
16
|
+
end
|
|
17
|
+
# -- .extract_global_timeout! --
|
|
18
|
+
|
|
19
|
+
def test_extract_global_timeout_extracts_short_global_key
|
|
20
|
+
opts = { global: 60, read: 5 }
|
|
21
|
+
|
|
22
|
+
assert_equal 60, HTTP::Timeout::PerOperation.send(:extract_global_timeout!, opts)
|
|
23
|
+
assert_equal({ read: 5 }, opts)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_extract_global_timeout_extracts_long_global_timeout_key
|
|
27
|
+
opts = { global_timeout: 60, read: 5 }
|
|
28
|
+
|
|
29
|
+
assert_equal 60, HTTP::Timeout::PerOperation.send(:extract_global_timeout!, opts)
|
|
30
|
+
assert_equal({ read: 5 }, opts)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_extract_global_timeout_returns_nil_when_no_global_key_present
|
|
34
|
+
opts = { read: 5 }
|
|
35
|
+
|
|
36
|
+
assert_nil HTTP::Timeout::PerOperation.send(:extract_global_timeout!, opts)
|
|
37
|
+
assert_equal({ read: 5 }, opts)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_extract_global_timeout_raises_when_both_global_and_global_timeout_given
|
|
41
|
+
assert_raises(ArgumentError) do
|
|
42
|
+
HTTP::Timeout::PerOperation.send(:extract_global_timeout!, global: 60, global_timeout: 60)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_extract_global_timeout_raises_for_non_numeric_global_value
|
|
47
|
+
assert_raises(ArgumentError) do
|
|
48
|
+
HTTP::Timeout::PerOperation.send(:extract_global_timeout!, global: "60")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# -- .normalize_options --
|
|
53
|
+
|
|
54
|
+
def test_normalize_options_normalizes_short_read_key
|
|
55
|
+
assert_equal({ read_timeout: 5 }, HTTP::Timeout::PerOperation.normalize_options(read: 5))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_normalize_options_normalizes_short_write_key
|
|
59
|
+
assert_equal({ write_timeout: 3 }, HTTP::Timeout::PerOperation.normalize_options(write: 3))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_normalize_options_normalizes_short_connect_key
|
|
63
|
+
assert_equal({ connect_timeout: 1 }, HTTP::Timeout::PerOperation.normalize_options(connect: 1))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_normalize_options_passes_through_long_form_keys
|
|
67
|
+
assert_equal({ read_timeout: 5 }, HTTP::Timeout::PerOperation.normalize_options(read_timeout: 5))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_normalize_options_normalizes_all_keys_together
|
|
71
|
+
result = HTTP::Timeout::PerOperation.normalize_options(read: 1, write: 2, connect: 3)
|
|
72
|
+
|
|
73
|
+
assert_equal({ read_timeout: 1, write_timeout: 2, connect_timeout: 3 }, result)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_normalize_options_accepts_float_values
|
|
77
|
+
assert_equal({ read_timeout: 1.5 }, HTTP::Timeout::PerOperation.normalize_options(read: 1.5))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_normalize_options_handles_frozen_hashes
|
|
81
|
+
result = HTTP::Timeout::PerOperation.normalize_options({ read: 5 }.freeze)
|
|
82
|
+
|
|
83
|
+
assert_equal({ read_timeout: 5 }, result)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_normalize_options_raises_when_both_short_and_long_form_given
|
|
87
|
+
assert_raises(ArgumentError) do
|
|
88
|
+
HTTP::Timeout::PerOperation.normalize_options(read: 1, read_timeout: 2)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_normalize_options_raises_for_non_numeric_values
|
|
93
|
+
assert_raises(ArgumentError) do
|
|
94
|
+
HTTP::Timeout::PerOperation.normalize_options(read: "5")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_normalize_options_raises_for_unknown_keys
|
|
99
|
+
assert_raises(ArgumentError) do
|
|
100
|
+
HTTP::Timeout::PerOperation.normalize_options(timeout: 5)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_normalize_options_raises_for_empty_hash
|
|
105
|
+
assert_raises(ArgumentError) do
|
|
106
|
+
HTTP::Timeout::PerOperation.normalize_options({})
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# -- #connect --
|
|
111
|
+
|
|
112
|
+
def test_connect_sets_tcp_nodelay_when_nodelay_is_true
|
|
113
|
+
setsockopt_args = nil
|
|
114
|
+
tcp_socket = fake(
|
|
115
|
+
setsockopt: ->(*args) { setsockopt_args = args }
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
socket_class = fake(open: tcp_socket)
|
|
119
|
+
@timeout.connect(socket_class, "example.com", 80, nodelay: true)
|
|
120
|
+
|
|
121
|
+
assert_equal [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1], setsockopt_args
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# -- #connect_ssl --
|
|
125
|
+
|
|
126
|
+
def test_connect_ssl_completes_without_error
|
|
127
|
+
connected = Object.new
|
|
128
|
+
socket = fake(
|
|
129
|
+
to_io: @io,
|
|
130
|
+
closed?: false,
|
|
131
|
+
connect_nonblock: ->(*) { connected }
|
|
132
|
+
)
|
|
133
|
+
@timeout.instance_variable_set(:@socket, socket)
|
|
134
|
+
@timeout.connect_ssl
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# -- #readpartial --
|
|
138
|
+
|
|
139
|
+
def test_readpartial_when_read_returns_nil_returns_eof
|
|
140
|
+
socket = fake(
|
|
141
|
+
to_io: @io,
|
|
142
|
+
closed?: false,
|
|
143
|
+
read_nonblock: nil
|
|
144
|
+
)
|
|
145
|
+
@timeout.instance_variable_set(:@socket, socket)
|
|
146
|
+
|
|
147
|
+
assert_equal :eof, @timeout.readpartial(10)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_readpartial_when_wait_writable_then_data_waits_and_retries
|
|
151
|
+
call_count = 0
|
|
152
|
+
socket = fake(
|
|
153
|
+
to_io: @io,
|
|
154
|
+
closed?: false,
|
|
155
|
+
read_nonblock: ->(*) { (call_count += 1) == 1 ? :wait_writable : "data" }
|
|
156
|
+
)
|
|
157
|
+
@timeout.instance_variable_set(:@socket, socket)
|
|
158
|
+
|
|
159
|
+
assert_equal "data", @timeout.readpartial(10)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_readpartial_when_wait_writable_and_times_out_raises_timeout_error
|
|
163
|
+
io_with_nil_wait = fake(wait_readable: nil, wait_writable: nil)
|
|
164
|
+
socket = fake(
|
|
165
|
+
to_io: io_with_nil_wait,
|
|
166
|
+
closed?: false,
|
|
167
|
+
read_nonblock: :wait_writable
|
|
168
|
+
)
|
|
169
|
+
@timeout.instance_variable_set(:@socket, socket)
|
|
170
|
+
|
|
171
|
+
err = assert_raises(HTTP::TimeoutError) do
|
|
172
|
+
@timeout.readpartial(10)
|
|
173
|
+
end
|
|
174
|
+
assert_match(/Read timed out/, err.message)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# -- #write --
|
|
178
|
+
|
|
179
|
+
def test_write_when_times_out_raises_timeout_error
|
|
180
|
+
io_with_nil_wait = fake(wait_readable: true, wait_writable: nil)
|
|
181
|
+
socket = fake(
|
|
182
|
+
to_io: io_with_nil_wait,
|
|
183
|
+
closed?: false,
|
|
184
|
+
write_nonblock: :wait_writable
|
|
185
|
+
)
|
|
186
|
+
@timeout.instance_variable_set(:@socket, socket)
|
|
187
|
+
|
|
188
|
+
err = assert_raises(HTTP::TimeoutError) do
|
|
189
|
+
@timeout.write("data")
|
|
190
|
+
end
|
|
191
|
+
assert_match(/Write timed out/, err.message)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_write_when_wait_readable_then_completes_waits_and_retries
|
|
195
|
+
call_count = 0
|
|
196
|
+
socket = fake(
|
|
197
|
+
to_io: @io,
|
|
198
|
+
closed?: false,
|
|
199
|
+
write_nonblock: ->(*) { (call_count += 1) == 1 ? :wait_readable : 4 }
|
|
200
|
+
)
|
|
201
|
+
@timeout.instance_variable_set(:@socket, socket)
|
|
202
|
+
|
|
203
|
+
assert_equal 4, @timeout.write("data")
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def test_write_when_wait_readable_and_times_out_raises_timeout_error
|
|
207
|
+
io_with_nil_wait = fake(wait_readable: nil, wait_writable: nil)
|
|
208
|
+
socket = fake(
|
|
209
|
+
to_io: io_with_nil_wait,
|
|
210
|
+
closed?: false,
|
|
211
|
+
write_nonblock: :wait_readable
|
|
212
|
+
)
|
|
213
|
+
@timeout.instance_variable_set(:@socket, socket)
|
|
214
|
+
|
|
215
|
+
err = assert_raises(HTTP::TimeoutError) do
|
|
216
|
+
@timeout.write("data")
|
|
217
|
+
end
|
|
218
|
+
assert_match(/Write timed out/, err.message)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPURINormalizerTest < Minitest::Test
|
|
6
|
+
def test_scheme_lower_cases_scheme
|
|
7
|
+
assert_equal "http", HTTP::URI::NORMALIZER.call("HttP://example.com").scheme
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_hostname_lower_cases_hostname
|
|
11
|
+
assert_equal "example.com", HTTP::URI::NORMALIZER.call("http://EXAMPLE.com").host
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_hostname_decodes_percent_encoded_hostname
|
|
15
|
+
assert_equal "example.com", HTTP::URI::NORMALIZER.call("http://ex%61mple.com").host
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_hostname_removes_trailing_period_in_hostname
|
|
19
|
+
assert_equal "example.com", HTTP::URI::NORMALIZER.call("http://example.com.").host
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_hostname_idn_encodes_non_ascii_hostname
|
|
23
|
+
assert_equal "xn--exmple-cua.com", HTTP::URI::NORMALIZER.call("http://ex\u00E4mple.com").host
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_path_ensures_path_is_not_empty
|
|
27
|
+
assert_equal "/", HTTP::URI::NORMALIZER.call("http://example.com").path
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_path_preserves_double_slashes_in_path
|
|
31
|
+
assert_equal "//a///b", HTTP::URI::NORMALIZER.call("http://example.com//a///b").path
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_path_resolves_single_dot_segments_in_path
|
|
35
|
+
assert_equal "/a/b", HTTP::URI::NORMALIZER.call("http://example.com/a/./b").path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_path_resolves_double_dot_segments_in_path
|
|
39
|
+
assert_equal "/a/c", HTTP::URI::NORMALIZER.call("http://example.com/a/b/../c").path
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_path_resolves_leading_double_dot_segments_in_path
|
|
43
|
+
assert_equal "/a/b", HTTP::URI::NORMALIZER.call("http://example.com/../a/b").path
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_path_percent_encodes_control_characters_in_path
|
|
47
|
+
assert_equal "/%00%7F%0A", HTTP::URI::NORMALIZER.call("http://example.com/\x00\x7F\n").path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_path_percent_encodes_space_in_path
|
|
51
|
+
assert_equal "/a%20b", HTTP::URI::NORMALIZER.call("http://example.com/a b").path
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_path_percent_encodes_non_ascii_characters_in_path
|
|
55
|
+
assert_equal "/%E3%82%AD%E3%83%A7", HTTP::URI::NORMALIZER.call("http://example.com/\u30AD\u30E7").path
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_path_does_not_percent_encode_non_special_characters_in_path
|
|
59
|
+
assert_equal "/~.-_!$&()*,;=:@{}", HTTP::URI::NORMALIZER.call("http://example.com/~.-_!$&()*,;=:@{}").path
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_path_preserves_escape_sequences_in_path
|
|
63
|
+
assert_equal "/%41", HTTP::URI::NORMALIZER.call("http://example.com/%41").path
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_query_allows_no_query
|
|
67
|
+
assert_nil HTTP::URI::NORMALIZER.call("http://example.com").query
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_query_percent_encodes_control_characters_in_query
|
|
71
|
+
assert_equal "%00%7F%0A", HTTP::URI::NORMALIZER.call("http://example.com/?\x00\x7F\n").query
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_query_percent_encodes_space_in_query
|
|
75
|
+
assert_equal "a%20b", HTTP::URI::NORMALIZER.call("http://example.com/?a b").query
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_query_percent_encodes_non_ascii_characters_in_query
|
|
79
|
+
assert_equal "%E3%82%AD%E3%83%A7", HTTP::URI::NORMALIZER.call("http://example.com?\u30AD\u30E7").query
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_query_does_not_percent_encode_non_special_characters_in_query
|
|
83
|
+
assert_equal "~.-_!$&()*,;=:@{}?", HTTP::URI::NORMALIZER.call("http://example.com/?~.-_!$&()*,;=:@{}?").query
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_query_preserves_escape_sequences_in_query
|
|
87
|
+
assert_equal "%41", HTTP::URI::NORMALIZER.call("http://example.com/?%41").query
|
|
88
|
+
end
|
|
89
|
+
end
|