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,292 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPResponseBodyTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Response::Body*"
|
|
7
|
+
|
|
8
|
+
def build_connection(chunks)
|
|
9
|
+
fake(sequence_id: 0, readpartial: proc { chunks.shift || raise(EOFError) }, body_completed?: proc {
|
|
10
|
+
chunks.empty?
|
|
11
|
+
})
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# streaming
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
def test_streams_bodies_from_responses
|
|
18
|
+
chunks = ["Hello, ", "World!"]
|
|
19
|
+
connection = build_connection(chunks)
|
|
20
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
21
|
+
result = body.to_s
|
|
22
|
+
|
|
23
|
+
assert_equal "Hello, World!", result
|
|
24
|
+
assert_equal Encoding::UTF_8, result.encoding
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# empty body
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
def test_when_body_empty_responds_to_empty_with_true
|
|
31
|
+
chunks = [""]
|
|
32
|
+
connection = build_connection(chunks)
|
|
33
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
34
|
+
|
|
35
|
+
assert_empty body
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# #readpartial
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
def test_readpartial_with_size_passes_value_to_underlying_connection
|
|
42
|
+
received_size = nil
|
|
43
|
+
conn = Object.new
|
|
44
|
+
conn.define_singleton_method(:readpartial) do |size = nil|
|
|
45
|
+
received_size = size
|
|
46
|
+
"data"
|
|
47
|
+
end
|
|
48
|
+
b = HTTP::Response::Body.new(conn, encoding: Encoding::UTF_8)
|
|
49
|
+
b.readpartial(42)
|
|
50
|
+
|
|
51
|
+
assert_equal 42, received_size
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_readpartial_without_size_does_not_blow_up
|
|
55
|
+
chunks = ["Hello, ", "World!"]
|
|
56
|
+
connection = build_connection(chunks)
|
|
57
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
58
|
+
body.readpartial
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_readpartial_without_size_calls_underlying_without_specific_size
|
|
62
|
+
call_args = nil
|
|
63
|
+
conn = Object.new
|
|
64
|
+
conn.define_singleton_method(:readpartial) do |*args|
|
|
65
|
+
call_args = args
|
|
66
|
+
"data"
|
|
67
|
+
end
|
|
68
|
+
b = HTTP::Response::Body.new(conn, encoding: Encoding::UTF_8)
|
|
69
|
+
b.readpartial
|
|
70
|
+
|
|
71
|
+
assert_equal [], call_args
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_readpartial_returns_content_in_specified_encoding
|
|
75
|
+
conn1 = fake(readpartial: proc { String.new("content", encoding: Encoding::UTF_8) })
|
|
76
|
+
b1 = HTTP::Response::Body.new(conn1)
|
|
77
|
+
|
|
78
|
+
assert_equal Encoding::BINARY, b1.readpartial.encoding
|
|
79
|
+
|
|
80
|
+
conn2 = fake(readpartial: proc { String.new("content", encoding: Encoding::BINARY) })
|
|
81
|
+
b2 = HTTP::Response::Body.new(conn2, encoding: Encoding::UTF_8)
|
|
82
|
+
|
|
83
|
+
assert_equal Encoding::UTF_8, b2.readpartial.encoding
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# #each
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
def test_each_yields_each_chunk
|
|
90
|
+
chunks = ["Hello, ", "World!"]
|
|
91
|
+
connection = build_connection(chunks)
|
|
92
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
93
|
+
collected = body.map { |chunk| chunk }
|
|
94
|
+
|
|
95
|
+
assert_equal "Hello, World!", collected.join
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# #to_s when streaming
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
def test_to_s_raises_state_error_if_body_is_being_streamed
|
|
102
|
+
chunks = ["Hello, ", "World!"]
|
|
103
|
+
connection = build_connection(chunks)
|
|
104
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
105
|
+
body.readpartial
|
|
106
|
+
err = assert_raises(HTTP::StateError) { body.to_s }
|
|
107
|
+
assert_match(/body is being streamed/, err.message)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# #stream! after consumption
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
def test_readpartial_raises_state_error_if_body_already_consumed
|
|
114
|
+
chunks = ["Hello, ", "World!"]
|
|
115
|
+
connection = build_connection(chunks)
|
|
116
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
117
|
+
body.to_s
|
|
118
|
+
err = assert_raises(HTTP::StateError) { body.readpartial }
|
|
119
|
+
assert_match(/body has already been consumed/, err.message)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# #to_s
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
def test_to_s_returns_same_string_on_subsequent_calls
|
|
126
|
+
chunks = ["Hello, ", "World!"]
|
|
127
|
+
connection = build_connection(chunks)
|
|
128
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
129
|
+
first = body.to_s
|
|
130
|
+
second = body.to_s
|
|
131
|
+
|
|
132
|
+
assert_equal "Hello, World!", first
|
|
133
|
+
assert_same first, second
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_to_s_re_raises_error_when_error_occurs_during_reading
|
|
137
|
+
connection = fake(readpartial: proc { raise IOError, "read error" })
|
|
138
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
139
|
+
|
|
140
|
+
assert_raises(IOError) { body.to_s }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_to_s_raises_state_error_on_subsequent_call_after_error
|
|
144
|
+
connection = fake(readpartial: proc { raise IOError, "read error" })
|
|
145
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
146
|
+
|
|
147
|
+
assert_raises(IOError) { body.to_s }
|
|
148
|
+
|
|
149
|
+
err = assert_raises(HTTP::StateError) { body.to_s }
|
|
150
|
+
assert_match(/body is being streamed/, err.message)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# #loggable?
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
def test_loggable_with_text_encoding_returns_true
|
|
157
|
+
chunks = ["Hello, ", "World!"]
|
|
158
|
+
connection = build_connection(chunks)
|
|
159
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
160
|
+
|
|
161
|
+
assert_predicate body, :loggable?
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_loggable_with_binary_encoding_returns_false
|
|
165
|
+
chunks = ["Hello, ", "World!"]
|
|
166
|
+
connection = build_connection(chunks)
|
|
167
|
+
body = HTTP::Response::Body.new(connection)
|
|
168
|
+
|
|
169
|
+
refute_predicate body, :loggable?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
# #connection
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
def test_connection_returns_streams_connection_when_stream_responds_to_connection
|
|
176
|
+
inner_conn = Object.new
|
|
177
|
+
stream = fake(
|
|
178
|
+
connection: inner_conn,
|
|
179
|
+
readpartial: proc { raise EOFError }
|
|
180
|
+
)
|
|
181
|
+
b = HTTP::Response::Body.new(stream)
|
|
182
|
+
|
|
183
|
+
assert_same inner_conn, b.connection
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_connection_returns_stream_itself_when_stream_does_not_respond_to_connection
|
|
187
|
+
stream = fake(readpartial: proc { raise EOFError })
|
|
188
|
+
b = HTTP::Response::Body.new(stream)
|
|
189
|
+
|
|
190
|
+
assert_same stream, b.connection
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
# #initialize
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
def test_initialize_explicitly_initializes_streaming
|
|
197
|
+
chunks = ["Hello, ", "World!"]
|
|
198
|
+
connection = build_connection(chunks)
|
|
199
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
200
|
+
|
|
201
|
+
assert body.instance_variable_defined?(:@streaming)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def test_initialize_explicitly_initializes_contents
|
|
205
|
+
chunks = ["Hello, ", "World!"]
|
|
206
|
+
connection = build_connection(chunks)
|
|
207
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
208
|
+
|
|
209
|
+
assert body.instance_variable_defined?(:@contents)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
# #inspect
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
def test_inspect_includes_class_name_hex_object_id_and_streaming_state
|
|
216
|
+
chunks = ["Hello, ", "World!"]
|
|
217
|
+
connection = build_connection(chunks)
|
|
218
|
+
body = HTTP::Response::Body.new(connection, encoding: Encoding::UTF_8)
|
|
219
|
+
result = body.inspect
|
|
220
|
+
hex_id = body.object_id.to_s(16)
|
|
221
|
+
|
|
222
|
+
assert_equal "#<HTTP::Response::Body:#{hex_id} @streaming=false>", result
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
# invalid encoding
|
|
227
|
+
# ---------------------------------------------------------------------------
|
|
228
|
+
def test_with_invalid_encoding_falls_back_to_binary
|
|
229
|
+
chunks = ["Hello, ", "World!"]
|
|
230
|
+
connection = build_connection(chunks)
|
|
231
|
+
body = HTTP::Response::Body.new(connection, encoding: "nonexistent-encoding")
|
|
232
|
+
|
|
233
|
+
assert_equal Encoding::BINARY, body.to_s.encoding
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
# gzipped body
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
def test_gzipped_body_decodes_body
|
|
240
|
+
compressed = Zlib::Deflate.deflate("Hi, HTTP here \u263A")
|
|
241
|
+
len = compressed.length
|
|
242
|
+
chunks = [compressed[0, len / 2], compressed[(len / 2)..]]
|
|
243
|
+
connection = build_connection(chunks)
|
|
244
|
+
inflater = HTTP::Response::Inflater.new(connection)
|
|
245
|
+
body = HTTP::Response::Body.new(inflater, encoding: Encoding::UTF_8)
|
|
246
|
+
|
|
247
|
+
assert_equal "Hi, HTTP here \u263A", body.to_s
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def test_gzipped_body_readpartial_streams_decoded_body
|
|
251
|
+
compressed = Zlib::Deflate.deflate("Hi, HTTP here \u263A")
|
|
252
|
+
len = compressed.length
|
|
253
|
+
chunks = [compressed[0, len / 2], compressed[(len / 2)..]]
|
|
254
|
+
connection = build_connection(chunks)
|
|
255
|
+
inflater = HTTP::Response::Inflater.new(connection)
|
|
256
|
+
body = HTTP::Response::Body.new(inflater, encoding: Encoding::UTF_8)
|
|
257
|
+
|
|
258
|
+
assert_equal "Hi, HTTP ", body.readpartial
|
|
259
|
+
assert_equal "here \u263A", body.readpartial
|
|
260
|
+
assert_raises(EOFError) { body.readpartial }
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# inflater with non-gzip data
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
def test_inflater_with_non_gzip_data_does_not_raise_zlib_buf_error
|
|
267
|
+
chunks = [" "]
|
|
268
|
+
connection = build_connection(chunks)
|
|
269
|
+
inflater = HTTP::Response::Inflater.new(connection)
|
|
270
|
+
body = HTTP::Response::Body.new(inflater, encoding: Encoding::UTF_8)
|
|
271
|
+
|
|
272
|
+
assert_equal "", body.to_s
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# ---------------------------------------------------------------------------
|
|
276
|
+
# inflater with EOFError without prior data
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
def test_inflater_closes_zstream_and_re_raises_on_eof_without_prior_data
|
|
279
|
+
conn = fake(readpartial: proc { raise EOFError })
|
|
280
|
+
inflater = HTTP::Response::Inflater.new(conn)
|
|
281
|
+
|
|
282
|
+
assert_raises(EOFError) { inflater.readpartial }
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def test_inflater_handles_repeated_eof_after_zstream_already_closed
|
|
286
|
+
conn = fake(readpartial: proc { raise EOFError })
|
|
287
|
+
inflater = HTTP::Response::Inflater.new(conn)
|
|
288
|
+
|
|
289
|
+
assert_raises(EOFError) { inflater.readpartial }
|
|
290
|
+
assert_raises(EOFError) { inflater.readpartial }
|
|
291
|
+
end
|
|
292
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class HTTPResponseParserTest < Minitest::Test
|
|
6
|
+
cover "HTTP::Response::Parser*"
|
|
7
|
+
|
|
8
|
+
RAW_RESPONSE = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: application/json\r\n" \
|
|
9
|
+
"MyHeader: val\r\nEmptyHeader: \r\n\r\n{}"
|
|
10
|
+
EXPECTED_HEADERS = {
|
|
11
|
+
"Content-Length" => "2",
|
|
12
|
+
"Content-Type" => "application/json",
|
|
13
|
+
"MyHeader" => "val",
|
|
14
|
+
"EmptyHeader" => ""
|
|
15
|
+
}.freeze
|
|
16
|
+
EXPECTED_BODY = "{}"
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# whole response in one part
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
def test_whole_response_parses_headers
|
|
22
|
+
parser = HTTP::Response::Parser.new
|
|
23
|
+
parser.add(RAW_RESPONSE)
|
|
24
|
+
|
|
25
|
+
assert_equal EXPECTED_HEADERS, parser.headers.to_h
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_whole_response_parses_body
|
|
29
|
+
parser = HTTP::Response::Parser.new
|
|
30
|
+
parser.add(RAW_RESPONSE)
|
|
31
|
+
|
|
32
|
+
assert_equal EXPECTED_BODY, parser.read(EXPECTED_BODY.size)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# response in many parts
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
def test_many_parts_parses_headers
|
|
39
|
+
parser = HTTP::Response::Parser.new
|
|
40
|
+
RAW_RESPONSE.chars.each { |part| parser.add(part) }
|
|
41
|
+
|
|
42
|
+
assert_equal EXPECTED_HEADERS, parser.headers.to_h
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_many_parts_parses_body
|
|
46
|
+
parser = HTTP::Response::Parser.new
|
|
47
|
+
RAW_RESPONSE.chars.each { |part| parser.add(part) }
|
|
48
|
+
|
|
49
|
+
assert_equal EXPECTED_BODY, parser.read(EXPECTED_BODY.size)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# #add with invalid data
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
def test_add_raises_io_error_on_invalid_http_data
|
|
56
|
+
parser = HTTP::Response::Parser.new
|
|
57
|
+
|
|
58
|
+
assert_raises(IOError) { parser.add("NOT HTTP AT ALL\r\n\r\n") }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# #read with chunk larger than requested size
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
def test_read_returns_only_requested_bytes_and_retains_rest
|
|
65
|
+
raw = "HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n0123456789"
|
|
66
|
+
parser = HTTP::Response::Parser.new
|
|
67
|
+
parser.add(raw)
|
|
68
|
+
|
|
69
|
+
chunk = parser.read(4)
|
|
70
|
+
|
|
71
|
+
assert_equal "0123", chunk
|
|
72
|
+
chunk = parser.read(6)
|
|
73
|
+
|
|
74
|
+
assert_equal "456789", chunk
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# 100 Continue response
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
def test_100_continue_in_one_part_skips_to_next_non_info_response
|
|
81
|
+
raw = "HTTP/1.1 100 Continue\r\n\r\n" \
|
|
82
|
+
"HTTP/1.1 200 OK\r\n" \
|
|
83
|
+
"Content-Length: 12\r\n\r\n" \
|
|
84
|
+
"Hello World!"
|
|
85
|
+
parser = HTTP::Response::Parser.new
|
|
86
|
+
parser.add(raw)
|
|
87
|
+
|
|
88
|
+
assert_equal 200, parser.status_code
|
|
89
|
+
assert_equal({ "Content-Length" => "12" }, parser.headers)
|
|
90
|
+
assert_equal "Hello World!", parser.read(12)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_100_continue_in_many_parts_skips_to_next_non_info_response
|
|
94
|
+
raw = "HTTP/1.1 100 Continue\r\n\r\n" \
|
|
95
|
+
"HTTP/1.1 200 OK\r\n" \
|
|
96
|
+
"Content-Length: 12\r\n\r\n" \
|
|
97
|
+
"Hello World!"
|
|
98
|
+
parser = HTTP::Response::Parser.new
|
|
99
|
+
raw.chars.each { |part| parser.add(part) }
|
|
100
|
+
|
|
101
|
+
assert_equal 200, parser.status_code
|
|
102
|
+
assert_equal({ "Content-Length" => "12" }, parser.headers)
|
|
103
|
+
assert_equal "Hello World!", parser.read(12)
|
|
104
|
+
end
|
|
105
|
+
end
|