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,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPContentTypeTest < Minitest::Test
|
|
6
|
+
cover "HTTP::ContentType*"
|
|
7
|
+
|
|
8
|
+
# -- .parse -----------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
def test_parse_text_plain_has_correct_mime_type
|
|
11
|
+
ct = HTTP::ContentType.parse "text/plain"
|
|
12
|
+
|
|
13
|
+
assert_equal "text/plain", ct.mime_type
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_parse_text_plain_has_correct_charset
|
|
17
|
+
ct = HTTP::ContentType.parse "text/plain"
|
|
18
|
+
|
|
19
|
+
assert_nil ct.charset
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_parse_mixed_case_text_plain_has_correct_mime_type
|
|
23
|
+
ct = HTTP::ContentType.parse "tEXT/plaIN"
|
|
24
|
+
|
|
25
|
+
assert_equal "text/plain", ct.mime_type
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_parse_mixed_case_text_plain_has_correct_charset
|
|
29
|
+
ct = HTTP::ContentType.parse "tEXT/plaIN"
|
|
30
|
+
|
|
31
|
+
assert_nil ct.charset
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_parse_text_plain_with_charset_has_correct_mime_type
|
|
35
|
+
ct = HTTP::ContentType.parse "text/plain; charset=utf-8"
|
|
36
|
+
|
|
37
|
+
assert_equal "text/plain", ct.mime_type
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_parse_text_plain_with_charset_has_correct_charset
|
|
41
|
+
ct = HTTP::ContentType.parse "text/plain; charset=utf-8"
|
|
42
|
+
|
|
43
|
+
assert_equal "utf-8", ct.charset
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_parse_text_plain_with_quoted_charset_has_correct_mime_type
|
|
47
|
+
ct = HTTP::ContentType.parse 'text/plain; charset="utf-8"'
|
|
48
|
+
|
|
49
|
+
assert_equal "text/plain", ct.mime_type
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_parse_text_plain_with_quoted_charset_has_correct_charset
|
|
53
|
+
ct = HTTP::ContentType.parse 'text/plain; charset="utf-8"'
|
|
54
|
+
|
|
55
|
+
assert_equal "utf-8", ct.charset
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_parse_text_plain_with_mixed_case_charset_has_correct_mime_type
|
|
59
|
+
ct = HTTP::ContentType.parse "text/plain; charSET=utf-8"
|
|
60
|
+
|
|
61
|
+
assert_equal "text/plain", ct.mime_type
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_parse_text_plain_with_mixed_case_charset_has_correct_charset
|
|
65
|
+
ct = HTTP::ContentType.parse "text/plain; charSET=utf-8"
|
|
66
|
+
|
|
67
|
+
assert_equal "utf-8", ct.charset
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_parse_with_extra_params_has_correct_mime_type
|
|
71
|
+
ct = HTTP::ContentType.parse "text/plain; foo=bar; charset=utf-8"
|
|
72
|
+
|
|
73
|
+
assert_equal "text/plain", ct.mime_type
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_parse_with_extra_params_has_correct_charset
|
|
77
|
+
ct = HTTP::ContentType.parse "text/plain; foo=bar; charset=utf-8"
|
|
78
|
+
|
|
79
|
+
assert_equal "utf-8", ct.charset
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_parse_with_no_spaces_has_correct_mime_type
|
|
83
|
+
ct = HTTP::ContentType.parse "text/plain;charset=utf-8;foo=bar"
|
|
84
|
+
|
|
85
|
+
assert_equal "text/plain", ct.mime_type
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_parse_with_no_spaces_has_correct_charset
|
|
89
|
+
ct = HTTP::ContentType.parse "text/plain;charset=utf-8;foo=bar"
|
|
90
|
+
|
|
91
|
+
assert_equal "utf-8", ct.charset
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_parse_nil_returns_nil_mime_type
|
|
95
|
+
ct = HTTP::ContentType.parse nil
|
|
96
|
+
|
|
97
|
+
assert_nil ct.mime_type
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_parse_nil_returns_nil_charset
|
|
101
|
+
ct = HTTP::ContentType.parse nil
|
|
102
|
+
|
|
103
|
+
assert_nil ct.charset
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_parse_empty_string_returns_nil_mime_type
|
|
107
|
+
ct = HTTP::ContentType.parse ""
|
|
108
|
+
|
|
109
|
+
assert_nil ct.mime_type
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_parse_empty_string_returns_nil_charset
|
|
113
|
+
ct = HTTP::ContentType.parse ""
|
|
114
|
+
|
|
115
|
+
assert_nil ct.charset
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_parse_with_whitespace_strips_whitespace_from_mime_type
|
|
119
|
+
ct = HTTP::ContentType.parse " text/plain ; charset= utf-8 "
|
|
120
|
+
|
|
121
|
+
assert_equal "text/plain", ct.mime_type
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_parse_with_whitespace_strips_whitespace_from_charset
|
|
125
|
+
ct = HTTP::ContentType.parse " text/plain ; charset= utf-8 "
|
|
126
|
+
|
|
127
|
+
assert_equal "utf-8", ct.charset
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# -- #deconstruct_keys ------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def test_deconstruct_keys_returns_all_keys_when_given_nil
|
|
133
|
+
ct = HTTP::ContentType.new("text/html", "utf-8")
|
|
134
|
+
|
|
135
|
+
assert_equal({ mime_type: "text/html", charset: "utf-8" }, ct.deconstruct_keys(nil))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def test_deconstruct_keys_returns_only_requested_keys
|
|
139
|
+
ct = HTTP::ContentType.new("text/html", "utf-8")
|
|
140
|
+
|
|
141
|
+
assert_equal({ mime_type: "text/html" }, ct.deconstruct_keys([:mime_type]))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_deconstruct_keys_excludes_unrequested_keys
|
|
145
|
+
ct = HTTP::ContentType.new("text/html", "utf-8")
|
|
146
|
+
|
|
147
|
+
refute_includes ct.deconstruct_keys([:mime_type]).keys, :charset
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_deconstruct_keys_returns_empty_hash_for_empty_keys
|
|
151
|
+
ct = HTTP::ContentType.new("text/html", "utf-8")
|
|
152
|
+
|
|
153
|
+
assert_equal({}, ct.deconstruct_keys([]))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_deconstruct_keys_returns_nil_values_when_attributes_are_nil
|
|
157
|
+
ct = HTTP::ContentType.new
|
|
158
|
+
|
|
159
|
+
assert_equal({ mime_type: nil, charset: nil }, ct.deconstruct_keys(nil))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_deconstruct_keys_supports_pattern_matching_with_case_in
|
|
163
|
+
ct = HTTP::ContentType.new("text/html", "utf-8")
|
|
164
|
+
|
|
165
|
+
matched = case ct
|
|
166
|
+
in { mime_type: /html/ }
|
|
167
|
+
true
|
|
168
|
+
else
|
|
169
|
+
false
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
assert matched
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# -- #initialize ------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def test_initialize_stores_mime_type_and_charset
|
|
178
|
+
ct = HTTP::ContentType.new("text/html", "utf-8")
|
|
179
|
+
|
|
180
|
+
assert_equal "text/html", ct.mime_type
|
|
181
|
+
assert_equal "utf-8", ct.charset
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_initialize_defaults_to_nil
|
|
185
|
+
ct = HTTP::ContentType.new
|
|
186
|
+
|
|
187
|
+
assert_nil ct.mime_type
|
|
188
|
+
assert_nil ct.charset
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPStatusErrorTest < Minitest::Test
|
|
6
|
+
cover "HTTP::StatusError*"
|
|
7
|
+
|
|
8
|
+
def response
|
|
9
|
+
@response ||= HTTP::Response.new(
|
|
10
|
+
status: 404,
|
|
11
|
+
version: "1.1",
|
|
12
|
+
body: "Not Found",
|
|
13
|
+
request: HTTP::Request.new(verb: :get, uri: "http://example.com/")
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def error
|
|
18
|
+
@error ||= HTTP::StatusError.new(response)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_response_returns_the_response
|
|
22
|
+
assert_same response, error.response
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_message_includes_the_status_code
|
|
26
|
+
assert_equal "Unexpected status code 404", error.message
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPFeatureTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Feature*"
|
|
7
|
+
|
|
8
|
+
def feature
|
|
9
|
+
@feature ||= HTTP::Feature.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def request
|
|
13
|
+
@request ||= HTTP::Request.new(verb: :get, uri: "http://example.com/")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def response
|
|
17
|
+
@response ||= HTTP::Response.new(
|
|
18
|
+
version: "1.1",
|
|
19
|
+
status: 200,
|
|
20
|
+
body: "OK",
|
|
21
|
+
request: request
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_wrap_request_returns_the_same_request_object
|
|
26
|
+
assert_same request, feature.wrap_request(request)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_wrap_response_returns_the_same_response_object
|
|
30
|
+
assert_same response, feature.wrap_response(response)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_on_request_does_not_raise
|
|
34
|
+
feature.on_request(request)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_around_request_yields_the_request_and_returns_the_response
|
|
38
|
+
result = feature.around_request(request) do |req|
|
|
39
|
+
assert_same(request, req)
|
|
40
|
+
response
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
assert_same response, result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_on_error_does_not_raise
|
|
47
|
+
feature.on_error(request, RuntimeError.new("boom"))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPFeaturesAutoDeflateTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Features::AutoDeflate*"
|
|
7
|
+
|
|
8
|
+
# Returns a body source that can only be iterated once; second iteration yields nothing.
|
|
9
|
+
def single_use_source(chunks)
|
|
10
|
+
consumed = false
|
|
11
|
+
fake(each: proc { |&block|
|
|
12
|
+
unless consumed
|
|
13
|
+
consumed = true
|
|
14
|
+
chunks.each { |chunk| block.call(chunk) }
|
|
15
|
+
end
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def subject_under_test
|
|
20
|
+
@subject_under_test ||= HTTP::Features::AutoDeflate.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_raises_error_for_wrong_type
|
|
24
|
+
err = assert_raises(HTTP::Error) { HTTP::Features::AutoDeflate.new(method: :wrong) }
|
|
25
|
+
assert_equal "Only gzip and deflate methods are supported", err.message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_accepts_gzip_method
|
|
29
|
+
assert_equal "gzip", HTTP::Features::AutoDeflate.new(method: :gzip).method
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_accepts_deflate_method
|
|
33
|
+
assert_equal "deflate", HTTP::Features::AutoDeflate.new(method: :deflate).method
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_accepts_string_as_method
|
|
37
|
+
assert_equal "gzip", HTTP::Features::AutoDeflate.new(method: "gzip").method
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_uses_gzip_by_default
|
|
41
|
+
assert_equal "gzip", subject_under_test.method
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_is_a_feature
|
|
45
|
+
assert_kind_of HTTP::Feature, subject_under_test
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# -- #wrap_request --
|
|
49
|
+
|
|
50
|
+
def build_request
|
|
51
|
+
custom_normalizer = ->(uri) { HTTP::URI::NORMALIZER.call(uri) }
|
|
52
|
+
HTTP::Request.new(
|
|
53
|
+
verb: :post,
|
|
54
|
+
uri: "http://example.com/",
|
|
55
|
+
headers: { "Content-Length" => "4" },
|
|
56
|
+
body: "data",
|
|
57
|
+
version: "2.0",
|
|
58
|
+
proxy: { proxy_host: "proxy.example.com" },
|
|
59
|
+
uri_normalizer: custom_normalizer
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_wrap_request_when_method_is_nil_returns_the_original_request
|
|
64
|
+
sut = HTTP::Features::AutoDeflate.new
|
|
65
|
+
sut.instance_variable_set(:@method, nil)
|
|
66
|
+
request = build_request
|
|
67
|
+
|
|
68
|
+
assert_same request, sut.wrap_request(request)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_wrap_request_when_body_is_empty_returns_the_original_request
|
|
72
|
+
empty_request = HTTP::Request.new(
|
|
73
|
+
verb: :post,
|
|
74
|
+
uri: "http://example.com/"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert_same empty_request, subject_under_test.wrap_request(empty_request)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_wrap_request_with_gzip_returns_a_new_request
|
|
81
|
+
request = build_request
|
|
82
|
+
result = subject_under_test.wrap_request(request)
|
|
83
|
+
|
|
84
|
+
refute_same request, result
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_wrap_request_with_gzip_returns_an_http_request
|
|
88
|
+
request = build_request
|
|
89
|
+
result = subject_under_test.wrap_request(request)
|
|
90
|
+
|
|
91
|
+
assert_instance_of HTTP::Request, result
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_wrap_request_with_gzip_sets_content_encoding_header
|
|
95
|
+
request = build_request
|
|
96
|
+
result = subject_under_test.wrap_request(request)
|
|
97
|
+
|
|
98
|
+
assert_equal "gzip", result.headers["Content-Encoding"]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_wrap_request_with_gzip_removes_content_length_header
|
|
102
|
+
request = build_request
|
|
103
|
+
result = subject_under_test.wrap_request(request)
|
|
104
|
+
|
|
105
|
+
refute_includes result.headers.to_h.keys.map(&:downcase), "content-length"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_wrap_request_with_gzip_preserves_the_verb
|
|
109
|
+
request = build_request
|
|
110
|
+
result = subject_under_test.wrap_request(request)
|
|
111
|
+
|
|
112
|
+
assert_equal :post, result.verb
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_wrap_request_with_gzip_preserves_the_uri
|
|
116
|
+
request = build_request
|
|
117
|
+
result = subject_under_test.wrap_request(request)
|
|
118
|
+
|
|
119
|
+
assert_equal request.uri, result.uri
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def test_wrap_request_with_gzip_preserves_the_version
|
|
123
|
+
request = build_request
|
|
124
|
+
result = subject_under_test.wrap_request(request)
|
|
125
|
+
|
|
126
|
+
assert_equal request.version, result.version
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_wrap_request_with_gzip_wraps_the_body_in_a_gzipped_body
|
|
130
|
+
request = build_request
|
|
131
|
+
result = subject_under_test.wrap_request(request)
|
|
132
|
+
|
|
133
|
+
assert_instance_of HTTP::Features::AutoDeflate::GzippedBody, result.body
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_wrap_request_with_gzip_compresses_the_original_request_body_data
|
|
137
|
+
request = build_request
|
|
138
|
+
result = subject_under_test.wrap_request(request)
|
|
139
|
+
compressed = result.body.each.to_a.join
|
|
140
|
+
decompressed = Zlib::GzipReader.new(StringIO.new(compressed)).read
|
|
141
|
+
|
|
142
|
+
assert_equal "data", decompressed
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_wrap_request_with_gzip_preserves_the_proxy
|
|
146
|
+
request = build_request
|
|
147
|
+
result = subject_under_test.wrap_request(request)
|
|
148
|
+
|
|
149
|
+
assert_equal request.proxy, result.proxy
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_wrap_request_with_gzip_preserves_the_uri_normalizer
|
|
153
|
+
custom_normalizer = ->(uri) { HTTP::URI::NORMALIZER.call(uri) }
|
|
154
|
+
request = HTTP::Request.new(
|
|
155
|
+
verb: :post,
|
|
156
|
+
uri: "http://example.com/",
|
|
157
|
+
headers: { "Content-Length" => "4" },
|
|
158
|
+
body: "data",
|
|
159
|
+
version: "2.0",
|
|
160
|
+
proxy: { proxy_host: "proxy.example.com" },
|
|
161
|
+
uri_normalizer: custom_normalizer
|
|
162
|
+
)
|
|
163
|
+
result = subject_under_test.wrap_request(request)
|
|
164
|
+
|
|
165
|
+
assert_same custom_normalizer, result.uri_normalizer
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_wrap_request_with_deflate_sets_content_encoding_to_deflate
|
|
169
|
+
sut = HTTP::Features::AutoDeflate.new(method: :deflate)
|
|
170
|
+
request = build_request
|
|
171
|
+
result = sut.wrap_request(request)
|
|
172
|
+
|
|
173
|
+
assert_equal "deflate", result.headers["Content-Encoding"]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def test_wrap_request_with_deflate_wraps_the_body_in_a_deflated_body
|
|
177
|
+
sut = HTTP::Features::AutoDeflate.new(method: :deflate)
|
|
178
|
+
request = build_request
|
|
179
|
+
result = sut.wrap_request(request)
|
|
180
|
+
|
|
181
|
+
assert_instance_of HTTP::Features::AutoDeflate::DeflatedBody, result.body
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# -- #deflated_body --
|
|
185
|
+
|
|
186
|
+
def test_deflated_body_when_method_is_unknown_returns_nil
|
|
187
|
+
sut = HTTP::Features::AutoDeflate.new
|
|
188
|
+
sut.instance_variable_set(:@method, "unknown")
|
|
189
|
+
|
|
190
|
+
assert_nil sut.deflated_body(%w[bees cows])
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def test_deflated_body_when_method_is_gzip_yields_gzipped_content
|
|
194
|
+
sut = HTTP::Features::AutoDeflate.new(method: :gzip)
|
|
195
|
+
deflated_body = sut.deflated_body(%w[bees cows])
|
|
196
|
+
result = deflated_body.each.to_a.join
|
|
197
|
+
decompressed = Zlib::GzipReader.new(StringIO.new(result)).read
|
|
198
|
+
|
|
199
|
+
assert_equal "beescows", decompressed
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def test_deflated_body_when_method_is_gzip_caches_compressed_content_when_size_is_called
|
|
203
|
+
sut = HTTP::Features::AutoDeflate.new(method: :gzip)
|
|
204
|
+
deflated_body = sut.deflated_body(%w[bees cows])
|
|
205
|
+
size = deflated_body.size
|
|
206
|
+
result = deflated_body.each.to_a.join
|
|
207
|
+
decompressed = Zlib::GzipReader.new(StringIO.new(result)).read
|
|
208
|
+
|
|
209
|
+
assert_equal result.bytesize, size
|
|
210
|
+
assert_equal "beescows", decompressed
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_deflated_body_when_method_is_gzip_reuses_cached_compression_on_subsequent_size_calls
|
|
214
|
+
single_use_body = single_use_source(%w[bees cows])
|
|
215
|
+
gzipped = HTTP::Features::AutoDeflate::GzippedBody.new(single_use_body)
|
|
216
|
+
first_size = gzipped.size
|
|
217
|
+
|
|
218
|
+
assert_equal first_size, gzipped.size
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def test_deflated_body_when_method_is_deflate_yields_deflated_content
|
|
222
|
+
sut = HTTP::Features::AutoDeflate.new(method: :deflate)
|
|
223
|
+
deflated_body = sut.deflated_body(%w[bees cows])
|
|
224
|
+
deflated = Zlib::Deflate.deflate("beescows")
|
|
225
|
+
|
|
226
|
+
assert_equal deflated, deflated_body.each.to_a.join
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def test_deflated_body_when_method_is_deflate_caches_compressed_content_when_size_is_called
|
|
230
|
+
sut = HTTP::Features::AutoDeflate.new(method: :deflate)
|
|
231
|
+
deflated_body = sut.deflated_body(%w[bees cows])
|
|
232
|
+
deflated = Zlib::Deflate.deflate("beescows")
|
|
233
|
+
|
|
234
|
+
assert_equal deflated.bytesize, deflated_body.size
|
|
235
|
+
assert_equal deflated, deflated_body.each.to_a.join
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# -- CompressedBody --
|
|
239
|
+
|
|
240
|
+
def test_compressed_body_initialize_sets_source_to_nil_via_super
|
|
241
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(%w[hello world])
|
|
242
|
+
|
|
243
|
+
assert_nil body.source
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def test_compressed_body_each_returns_an_enumerator_when_no_block_is_given
|
|
247
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(%w[hello world])
|
|
248
|
+
enum = body.each
|
|
249
|
+
|
|
250
|
+
assert_instance_of Enumerator, enum
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def test_compressed_body_each_returns_self_when_block_is_given
|
|
254
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(%w[hello world])
|
|
255
|
+
result = body.each { |_chunk| nil }
|
|
256
|
+
|
|
257
|
+
assert_same body, result
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def test_compressed_body_each_yields_compressed_data_via_compress_when_not_cached
|
|
261
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(%w[hello world])
|
|
262
|
+
chunks = body.each.map { |chunk| chunk }
|
|
263
|
+
|
|
264
|
+
refute_empty chunks
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def test_compressed_body_each_reads_from_cache_when_size_was_called_first_on_single_use_body
|
|
268
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(single_use_source(%w[hello world]))
|
|
269
|
+
expected_size = body.size
|
|
270
|
+
chunks = body.each.map { |chunk| chunk }
|
|
271
|
+
actual = chunks.join
|
|
272
|
+
|
|
273
|
+
assert_equal expected_size, actual.bytesize
|
|
274
|
+
decompressed = Zlib::GzipReader.new(StringIO.new(actual)).read
|
|
275
|
+
|
|
276
|
+
assert_equal "helloworld", decompressed
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def test_compressed_body_each_cleans_up_tempfile_after_reading_cached_data
|
|
280
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(%w[hello world])
|
|
281
|
+
body.size
|
|
282
|
+
path = body.instance_variable_get(:@compressed).path
|
|
283
|
+
body.each { |_| nil }
|
|
284
|
+
|
|
285
|
+
refute_path_exists path
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def test_compressed_body_each_can_be_enumerated_multiple_times_via_to_enum
|
|
289
|
+
inner_body = %w[hello world]
|
|
290
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(inner_body)
|
|
291
|
+
first_result = body.each.to_a.join
|
|
292
|
+
# After each consumes via compress, we need a fresh body to test enum
|
|
293
|
+
body2 = HTTP::Features::AutoDeflate::GzippedBody.new(inner_body)
|
|
294
|
+
|
|
295
|
+
assert_equal first_result, body2.each.to_a.join
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# -- GzippedBody --
|
|
299
|
+
|
|
300
|
+
def test_gzipped_body_compress_produces_valid_gzip_data
|
|
301
|
+
body = HTTP::Features::AutoDeflate::GzippedBody.new(%w[hello world])
|
|
302
|
+
compressed = body.each.to_a.join
|
|
303
|
+
decompressed = Zlib::GzipReader.new(StringIO.new(compressed)).read
|
|
304
|
+
|
|
305
|
+
assert_equal "helloworld", decompressed
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# -- DeflatedBody --
|
|
309
|
+
|
|
310
|
+
def test_deflated_body_compress_produces_valid_deflate_data
|
|
311
|
+
body = HTTP::Features::AutoDeflate::DeflatedBody.new(%w[hello world])
|
|
312
|
+
compressed = body.each.to_a.join
|
|
313
|
+
decompressed = Zlib::Inflate.inflate(compressed)
|
|
314
|
+
|
|
315
|
+
assert_equal "helloworld", decompressed
|
|
316
|
+
end
|
|
317
|
+
end
|