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
@@ -1,556 +0,0 @@
1
- # coding: utf-8
2
- # frozen_string_literal: true
3
-
4
- require "cgi"
5
- require "logger"
6
-
7
- require "support/http_handling_shared"
8
- require "support/dummy_server"
9
- require "support/ssl_helper"
10
-
11
- RSpec.describe HTTP::Client do
12
- run_server(:dummy) { DummyServer.new }
13
-
14
- before do
15
- stubbed_client = Class.new(HTTP::Client) do
16
- def perform(request, options)
17
- stubbed = stubs[HTTP::URI::NORMALIZER.call(request.uri).to_s]
18
- stubbed ? stubbed.call(request) : super(request, options)
19
- end
20
-
21
- def stubs
22
- @stubs ||= {}
23
- end
24
-
25
- def stub(stubs)
26
- @stubs = stubs.transform_keys do |k|
27
- HTTP::URI::NORMALIZER.call(k).to_s
28
- end
29
-
30
- self
31
- end
32
- end
33
-
34
- def redirect_response(location, status = 302)
35
- lambda do |request|
36
- HTTP::Response.new(
37
- :status => status,
38
- :version => "1.1",
39
- :headers => {"Location" => location},
40
- :body => "",
41
- :request => request
42
- )
43
- end
44
- end
45
-
46
- def simple_response(body, status = 200)
47
- lambda do |request|
48
- HTTP::Response.new(
49
- :status => status,
50
- :version => "1.1",
51
- :body => body,
52
- :request => request
53
- )
54
- end
55
- end
56
-
57
- stub_const("StubbedClient", stubbed_client)
58
- end
59
-
60
- describe "following redirects" do
61
- it "returns response of new location" do
62
- client = StubbedClient.new(:follow => true).stub(
63
- "http://example.com/" => redirect_response("http://example.com/blog"),
64
- "http://example.com/blog" => simple_response("OK")
65
- )
66
-
67
- expect(client.get("http://example.com/").to_s).to eq "OK"
68
- end
69
-
70
- it "prepends previous request uri scheme and host if needed" do
71
- client = StubbedClient.new(:follow => true).stub(
72
- "http://example.com/" => redirect_response("/index"),
73
- "http://example.com/index" => redirect_response("/index.html"),
74
- "http://example.com/index.html" => simple_response("OK")
75
- )
76
-
77
- expect(client.get("http://example.com/").to_s).to eq "OK"
78
- end
79
-
80
- it "fails upon endless redirects" do
81
- client = StubbedClient.new(:follow => true).stub(
82
- "http://example.com/" => redirect_response("/")
83
- )
84
-
85
- expect { client.get("http://example.com/") }.
86
- to raise_error(HTTP::Redirector::EndlessRedirectError)
87
- end
88
-
89
- it "fails if max amount of hops reached" do
90
- client = StubbedClient.new(:follow => {:max_hops => 5}).stub(
91
- "http://example.com/" => redirect_response("/1"),
92
- "http://example.com/1" => redirect_response("/2"),
93
- "http://example.com/2" => redirect_response("/3"),
94
- "http://example.com/3" => redirect_response("/4"),
95
- "http://example.com/4" => redirect_response("/5"),
96
- "http://example.com/5" => redirect_response("/6"),
97
- "http://example.com/6" => simple_response("OK")
98
- )
99
-
100
- expect { client.get("http://example.com/") }.
101
- to raise_error(HTTP::Redirector::TooManyRedirectsError)
102
- end
103
-
104
- context "with non-ASCII URLs" do
105
- it "theoretically works like a charm" do
106
- client = StubbedClient.new(:follow => true).stub(
107
- "http://example.com/" => redirect_response("/könig"),
108
- "http://example.com/könig" => simple_response("OK")
109
- )
110
-
111
- expect { client.get "http://example.com/könig" }.not_to raise_error
112
- end
113
-
114
- it "works like a charm in real world" do
115
- expect(HTTP.follow.get("https://bit.ly/2UaBT4R").parse(:json)).
116
- to include("url" => "https://httpbin.org/anything/könig")
117
- end
118
- end
119
- end
120
-
121
- describe "following redirects with logging" do
122
- let(:logger) do
123
- logger = Logger.new(logdev)
124
- logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
125
- logger.level = Logger::INFO
126
- logger
127
- end
128
-
129
- let(:logdev) { StringIO.new }
130
-
131
- it "logs all requests" do
132
- client = StubbedClient.new(:follow => true, :features => { :logging => { :logger => logger } }).stub(
133
- "http://example.com/" => redirect_response("/1"),
134
- "http://example.com/1" => redirect_response("/2"),
135
- "http://example.com/2" => redirect_response("/3"),
136
- "http://example.com/3" => simple_response("OK")
137
- )
138
-
139
- expect { client.get("http://example.com/") }.not_to raise_error
140
-
141
- expect(logdev.string).to eq <<~OUTPUT
142
- ** INFO **
143
- > GET http://example.com/
144
- ** INFO **
145
- > GET http://example.com/1
146
- ** INFO **
147
- > GET http://example.com/2
148
- ** INFO **
149
- > GET http://example.com/3
150
- OUTPUT
151
- end
152
- end
153
-
154
- describe "parsing params" do
155
- let(:client) { HTTP::Client.new }
156
- before { allow(client).to receive :perform }
157
-
158
- it "accepts params within the provided URL" do
159
- expect(HTTP::Request).to receive(:new) do |opts|
160
- expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w[bar])
161
- end
162
-
163
- client.get("http://example.com/?foo=bar")
164
- end
165
-
166
- it "combines GET params from the URI with the passed in params" do
167
- expect(HTTP::Request).to receive(:new) do |opts|
168
- expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w[bar], "baz" => %w[quux])
169
- end
170
-
171
- client.get("http://example.com/?foo=bar", :params => {:baz => "quux"})
172
- end
173
-
174
- it "merges duplicate values" do
175
- expect(HTTP::Request).to receive(:new) do |opts|
176
- expect(opts[:uri].query).to match(/^(a=1&a=2|a=2&a=1)$/)
177
- end
178
-
179
- client.get("http://example.com/?a=1", :params => {:a => 2})
180
- end
181
-
182
- it "does not modifies query part if no params were given" do
183
- expect(HTTP::Request).to receive(:new) do |opts|
184
- expect(opts[:uri].query).to eq "deadbeef"
185
- end
186
-
187
- client.get("http://example.com/?deadbeef")
188
- end
189
-
190
- it "does not corrupts index-less arrays" do
191
- expect(HTTP::Request).to receive(:new) do |opts|
192
- expect(CGI.parse(opts[:uri].query)).to eq "a[]" => %w[b c], "d" => %w[e]
193
- end
194
-
195
- client.get("http://example.com/?a[]=b&a[]=c", :params => {:d => "e"})
196
- end
197
-
198
- it "properly encodes colons" do
199
- expect(HTTP::Request).to receive(:new) do |opts|
200
- expect(opts[:uri].query).to eq "t=1970-01-01T00%3A00%3A00Z"
201
- end
202
-
203
- client.get("http://example.com/", :params => {:t => "1970-01-01T00:00:00Z"})
204
- end
205
-
206
- it 'does not convert newlines into \r\n before encoding string values' do
207
- expect(HTTP::Request).to receive(:new) do |opts|
208
- expect(opts[:uri].query).to eq "foo=bar%0Abaz"
209
- end
210
-
211
- client.get("http://example.com/", :params => {:foo => "bar\nbaz"})
212
- end
213
- end
214
-
215
- describe "passing multipart form data" do
216
- it "creates url encoded form data object" do
217
- client = HTTP::Client.new
218
- allow(client).to receive(:perform)
219
-
220
- expect(HTTP::Request).to receive(:new) do |opts|
221
- expect(opts[:body]).to be_a(HTTP::FormData::Urlencoded)
222
- expect(opts[:body].to_s).to eq "foo=bar"
223
- end
224
-
225
- client.get("http://example.com/", :form => {:foo => "bar"})
226
- end
227
-
228
- it "creates multipart form data object" do
229
- client = HTTP::Client.new
230
- allow(client).to receive(:perform)
231
-
232
- expect(HTTP::Request).to receive(:new) do |opts|
233
- expect(opts[:body]).to be_a(HTTP::FormData::Multipart)
234
- expect(opts[:body].to_s).to include("content")
235
- end
236
-
237
- client.get("http://example.com/", :form => {:foo => HTTP::FormData::Part.new("content")})
238
- end
239
-
240
- context "when passing an HTTP::FormData object directly" do
241
- it "creates url encoded form data object" do
242
- client = HTTP::Client.new
243
- form_data = HTTP::FormData::Multipart.new({ :foo => "bar" })
244
-
245
- allow(client).to receive(:perform)
246
-
247
- expect(HTTP::Request).to receive(:new) do |opts|
248
- expect(opts[:body]).to be form_data
249
- expect(opts[:body].to_s).to match(/^Content-Disposition: form-data; name="foo"\r\n\r\nbar\r\n/m)
250
- end
251
-
252
- client.get("http://example.com/", :form => form_data)
253
- end
254
- end
255
- end
256
-
257
- describe "passing json" do
258
- it "encodes given object" do
259
- client = HTTP::Client.new
260
- allow(client).to receive(:perform)
261
-
262
- expect(HTTP::Request).to receive(:new) do |opts|
263
- expect(opts[:body]).to eq '{"foo":"bar"}'
264
- expect(opts[:headers]["Content-Type"]).to eq "application/json; charset=utf-8"
265
- end
266
-
267
- client.get("http://example.com/", :json => {:foo => :bar})
268
- end
269
- end
270
-
271
- describe "#request" do
272
- context "with non-ASCII URLs" do
273
- it "theoretically works like a charm" do
274
- client = described_class.new
275
- expect { client.get "#{dummy.endpoint}/könig" }.not_to raise_error
276
- end
277
-
278
- it "works like a charm in real world" do
279
- url = "https://httpbin.org/anything/ö無"
280
-
281
- expect(HTTP.follow.get(url).parse(:json)).to include("url" => url)
282
- end
283
- end
284
-
285
- context "with explicitly given `Host` header" do
286
- let(:headers) { {"Host" => "another.example.com"} }
287
- let(:client) { described_class.new :headers => headers }
288
-
289
- it "keeps `Host` header as is" do
290
- expect(client).to receive(:perform) do |req, _|
291
- expect(req["Host"]).to eq "another.example.com"
292
- end
293
-
294
- client.request(:get, "http://example.com/")
295
- end
296
- end
297
-
298
- context "when :auto_deflate was specified" do
299
- let(:headers) { {"Content-Length" => "12"} }
300
- let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}}, :body => "foo" }
301
-
302
- it "deletes Content-Length header" do
303
- expect(client).to receive(:perform) do |req, _|
304
- expect(req["Content-Length"]).to eq nil
305
- end
306
-
307
- client.request(:get, "http://example.com/")
308
- end
309
-
310
- it "sets Content-Encoding header" do
311
- expect(client).to receive(:perform) do |req, _|
312
- expect(req["Content-Encoding"]).to eq "gzip"
313
- end
314
-
315
- client.request(:get, "http://example.com/")
316
- end
317
-
318
- context "and there is no body" do
319
- let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}} }
320
-
321
- it "doesn't set Content-Encoding header" do
322
- expect(client).to receive(:perform) do |req, _|
323
- expect(req.headers).not_to include "Content-Encoding"
324
- end
325
-
326
- client.request(:get, "http://example.com/")
327
- end
328
- end
329
- end
330
-
331
- context "Feature" do
332
- let(:feature_class) do
333
- Class.new(HTTP::Feature) do
334
- attr_reader :captured_request, :captured_response, :captured_error
335
-
336
- def wrap_request(request)
337
- @captured_request = request
338
- end
339
-
340
- def wrap_response(response)
341
- @captured_response = response
342
- end
343
-
344
- def on_error(request, error)
345
- @captured_request = request
346
- @captured_error = error
347
- end
348
- end
349
- end
350
-
351
- it "is given a chance to wrap the Request" do
352
- feature_instance = feature_class.new
353
-
354
- response = client.use(:test_feature => feature_instance).
355
- request(:get, dummy.endpoint)
356
-
357
- expect(response.code).to eq(200)
358
- expect(feature_instance.captured_request.verb).to eq(:get)
359
- expect(feature_instance.captured_request.uri.to_s).to eq("#{dummy.endpoint}/")
360
- end
361
-
362
- it "is given a chance to wrap the Response" do
363
- feature_instance = feature_class.new
364
-
365
- response = client.use(:test_feature => feature_instance).
366
- request(:get, dummy.endpoint)
367
-
368
- expect(feature_instance.captured_response).to eq(response)
369
- end
370
-
371
- it "is given a chance to handle an error" do
372
- sleep_url = "#{dummy.endpoint}/sleep"
373
- feature_instance = feature_class.new
374
-
375
- expect do
376
- client.use(:test_feature => feature_instance).
377
- timeout(0.2).
378
- request(:post, sleep_url)
379
- end.to raise_error(HTTP::TimeoutError)
380
-
381
- expect(feature_instance.captured_error).to be_a(HTTP::TimeoutError)
382
- expect(feature_instance.captured_request.verb).to eq(:post)
383
- expect(feature_instance.captured_request.uri.to_s).to eq(sleep_url)
384
- end
385
-
386
- it "is given a chance to handle a connection timeout error" do
387
- allow(TCPSocket).to receive(:open) { sleep 1 }
388
- sleep_url = "#{dummy.endpoint}/sleep"
389
- feature_instance = feature_class.new
390
-
391
- expect do
392
- client.use(:test_feature => feature_instance).
393
- timeout(0.001).
394
- request(:post, sleep_url)
395
- end.to raise_error(HTTP::ConnectTimeoutError)
396
- expect(feature_instance.captured_error).to be_a(HTTP::ConnectTimeoutError)
397
- end
398
- end
399
- end
400
-
401
- include_context "HTTP handling" do
402
- let(:extra_options) { {} }
403
- let(:options) { {} }
404
- let(:server) { dummy }
405
- let(:client) { described_class.new(options.merge(extra_options)) }
406
- end
407
-
408
- # TODO: https://github.com/httprb/http/issues/627
409
- xdescribe "working with SSL" do
410
- run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
411
-
412
- let(:extra_options) { {} }
413
-
414
- let(:client) do
415
- described_class.new options.merge(:ssl_context => SSLHelper.client_context).merge(extra_options)
416
- end
417
-
418
- include_context "HTTP handling" do
419
- let(:server) { dummy_ssl }
420
- end
421
-
422
- it "just works" do
423
- response = client.get(dummy_ssl.endpoint)
424
- expect(response.body.to_s).to eq("<!doctype html>")
425
- end
426
-
427
- it "fails with OpenSSL::SSL::SSLError if host mismatch" do
428
- expect { client.get(dummy_ssl.endpoint.gsub("127.0.0.1", "localhost")) }.
429
- to raise_error(OpenSSL::SSL::SSLError, /does not match/)
430
- end
431
-
432
- context "with SSL options instead of a context" do
433
- let(:client) do
434
- described_class.new options.merge :ssl => SSLHelper.client_params
435
- end
436
-
437
- it "just works" do
438
- response = client.get(dummy_ssl.endpoint)
439
- expect(response.body.to_s).to eq("<!doctype html>")
440
- end
441
- end
442
- end
443
-
444
- describe "#perform" do
445
- let(:client) { described_class.new }
446
-
447
- it "calls finish_response once body was fully flushed" do
448
- expect_any_instance_of(HTTP::Connection).to receive(:finish_response).and_call_original
449
- client.get(dummy.endpoint).to_s
450
- end
451
-
452
- it "provides access to the Request from the Response" do
453
- unique_value = "20190424"
454
- response = client.headers("X-Value" => unique_value).get(dummy.endpoint)
455
-
456
- expect(response.request).to be_a(HTTP::Request)
457
- expect(response.request.headers["X-Value"]).to eq(unique_value)
458
- end
459
-
460
- context "with HEAD request" do
461
- it "does not iterates through body" do
462
- expect_any_instance_of(HTTP::Connection).to_not receive(:readpartial)
463
- client.head(dummy.endpoint)
464
- end
465
-
466
- it "finishes response after headers were received" do
467
- expect_any_instance_of(HTTP::Connection).to receive(:finish_response).and_call_original
468
- client.head(dummy.endpoint)
469
- end
470
- end
471
-
472
- context "when server fully flushes response in one chunk" do
473
- before do
474
- socket_spy = double
475
-
476
- chunks = [
477
- <<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
478
- | HTTP/1.1 200 OK
479
- | Content-Type: text/html
480
- | Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22)
481
- | Date: Mon, 24 Mar 2014 00:32:22 GMT
482
- | Content-Length: 15
483
- | Connection: Keep-Alive
484
- |
485
- | <!doctype html>
486
- RESPONSE
487
- ]
488
-
489
- allow(socket_spy).to receive(:close) { nil }
490
- allow(socket_spy).to receive(:closed?) { true }
491
- allow(socket_spy).to receive(:readpartial) { chunks.shift || :eof }
492
- allow(socket_spy).to receive(:write) { chunks[0].length }
493
-
494
- allow(TCPSocket).to receive(:open) { socket_spy }
495
- end
496
-
497
- it "properly reads body" do
498
- body = client.get(dummy.endpoint).to_s
499
- expect(body).to eq "<!doctype html>"
500
- end
501
- end
502
-
503
- context "when uses chunked transfer encoding" do
504
- let(:chunks) do
505
- [
506
- <<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n") << body
507
- | HTTP/1.1 200 OK
508
- | Content-Type: application/json
509
- | Transfer-Encoding: chunked
510
- | Connection: close
511
- |
512
- RESPONSE
513
- ]
514
- end
515
- let(:body) do
516
- <<-BODY.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
517
- | 9
518
- | {"state":
519
- | 5
520
- | "ok"}
521
- | 0
522
- |
523
- BODY
524
- end
525
-
526
- before do
527
- socket_spy = double
528
-
529
- allow(socket_spy).to receive(:close) { nil }
530
- allow(socket_spy).to receive(:closed?) { true }
531
- allow(socket_spy).to receive(:readpartial) { chunks.shift || :eof }
532
- allow(socket_spy).to receive(:write) { chunks[0].length }
533
-
534
- allow(TCPSocket).to receive(:open) { socket_spy }
535
- end
536
-
537
- it "properly reads body" do
538
- body = client.get(dummy.endpoint).to_s
539
- expect(body).to eq '{"state":"ok"}'
540
- end
541
-
542
- context "with broken body (too early closed connection)" do
543
- let(:body) do
544
- <<-BODY.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
545
- | 9
546
- | {"state":
547
- BODY
548
- end
549
-
550
- xit "raises HTTP::ConnectionError" do
551
- expect { client.get(dummy.endpoint).to_s }.to raise_error(HTTP::ConnectionError)
552
- end
553
- end
554
- end
555
- end
556
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::Connection do
4
- let(:req) do
5
- HTTP::Request.new(
6
- :verb => :get,
7
- :uri => "http://example.com/",
8
- :headers => {}
9
- )
10
- end
11
- let(:socket) { double(:connect => nil, :close => nil) }
12
- let(:timeout_class) { double(:new => socket) }
13
- let(:opts) { HTTP::Options.new(:timeout_class => timeout_class) }
14
- let(:connection) { HTTP::Connection.new(req, opts) }
15
-
16
- describe "#initialize times out" do
17
- let(:req) do
18
- HTTP::Request.new(
19
- :verb => :get,
20
- :uri => "https://example.com/",
21
- :headers => {}
22
- )
23
- end
24
-
25
- before do
26
- expect(socket).to receive(:start_tls).and_raise(HTTP::TimeoutError)
27
- expect(socket).to receive(:closed?) { false }
28
- expect(socket).to receive(:close)
29
- end
30
-
31
- it "closes the connection" do
32
- expect { connection }.to raise_error(HTTP::TimeoutError)
33
- end
34
- end
35
-
36
- describe "#read_headers!" do
37
- before do
38
- connection.instance_variable_set(:@pending_response, true)
39
- expect(socket).to receive(:readpartial) do
40
- <<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
41
- | HTTP/1.1 200 OK
42
- | Content-Type: text
43
- | foo_bar: 123
44
- |
45
- RESPONSE
46
- end
47
- end
48
-
49
- it "populates headers collection, preserving casing" do
50
- connection.read_headers!
51
- expect(connection.headers).to eq("Content-Type" => "text", "foo_bar" => "123")
52
- expect(connection.headers["Foo-Bar"]).to eq("123")
53
- expect(connection.headers["foo_bar"]).to eq("123")
54
- end
55
- end
56
-
57
- describe "#readpartial" do
58
- before do
59
- connection.instance_variable_set(:@pending_response, true)
60
- expect(socket).to receive(:readpartial) do
61
- <<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
62
- | HTTP/1.1 200 OK
63
- | Content-Type: text
64
- |
65
- RESPONSE
66
- end
67
- expect(socket).to receive(:readpartial) { "1" }
68
- expect(socket).to receive(:readpartial) { "23" }
69
- expect(socket).to receive(:readpartial) { "456" }
70
- expect(socket).to receive(:readpartial) { "78" }
71
- expect(socket).to receive(:readpartial) { "9" }
72
- expect(socket).to receive(:readpartial) { "0" }
73
- expect(socket).to receive(:readpartial) { :eof }
74
- expect(socket).to receive(:closed?) { true }
75
- end
76
-
77
- it "reads data in parts" do
78
- connection.read_headers!
79
- buffer = String.new
80
- while (s = connection.readpartial(3))
81
- expect(connection.finished_request?).to be false if s != ""
82
- buffer << s
83
- end
84
- expect(buffer).to eq "1234567890"
85
- expect(connection.finished_request?).to be true
86
- end
87
- end
88
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe HTTP::ContentType do
4
- describe ".parse" do
5
- context "with text/plain" do
6
- subject { described_class.parse "text/plain" }
7
- its(:mime_type) { is_expected.to eq "text/plain" }
8
- its(:charset) { is_expected.to be_nil }
9
- end
10
-
11
- context "with tEXT/plaIN" do
12
- subject { described_class.parse "tEXT/plaIN" }
13
- its(:mime_type) { is_expected.to eq "text/plain" }
14
- its(:charset) { is_expected.to be_nil }
15
- end
16
-
17
- context "with text/plain; charset=utf-8" do
18
- subject { described_class.parse "text/plain; charset=utf-8" }
19
- its(:mime_type) { is_expected.to eq "text/plain" }
20
- its(:charset) { is_expected.to eq "utf-8" }
21
- end
22
-
23
- context 'with text/plain; charset="utf-8"' do
24
- subject { described_class.parse 'text/plain; charset="utf-8"' }
25
- its(:mime_type) { is_expected.to eq "text/plain" }
26
- its(:charset) { is_expected.to eq "utf-8" }
27
- end
28
-
29
- context "with text/plain; charSET=utf-8" do
30
- subject { described_class.parse "text/plain; charSET=utf-8" }
31
- its(:mime_type) { is_expected.to eq "text/plain" }
32
- its(:charset) { is_expected.to eq "utf-8" }
33
- end
34
-
35
- context "with text/plain; foo=bar; charset=utf-8" do
36
- subject { described_class.parse "text/plain; foo=bar; charset=utf-8" }
37
- its(:mime_type) { is_expected.to eq "text/plain" }
38
- its(:charset) { is_expected.to eq "utf-8" }
39
- end
40
-
41
- context "with text/plain;charset=utf-8;foo=bar" do
42
- subject { described_class.parse "text/plain;charset=utf-8;foo=bar" }
43
- its(:mime_type) { is_expected.to eq "text/plain" }
44
- its(:charset) { is_expected.to eq "utf-8" }
45
- end
46
- end
47
- end