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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /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