patron 0.13.3 → 0.13.4

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.
data/patron.gemspec CHANGED
@@ -6,23 +6,23 @@ require 'patron/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "patron"
8
8
  spec.version = Patron::VERSION
9
+ spec.licenses = ["MIT"]
9
10
  spec.platform = Gem::Platform::RUBY
10
- spec.authors = ["Phillip Toland"]
11
- spec.email = ["phil.toland@gmail.com"]
11
+ spec.authors = ["Aeryn Riley Dowling-Toland"]
12
+ spec.email = ["aeryn.toland@gmail.com"]
12
13
  spec.homepage = "https://github.com/toland/patron"
13
14
  spec.summary = "Patron HTTP Client"
14
15
  spec.description = "Ruby HTTP client library based on libcurl"
15
16
 
16
- # Prevent pushing this gem to RubyGemspec.org. To allow pushes either set the 'allowed_push_host'
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
18
  # to allow pushing to a single host or delete this section to allow pushing to any host.
18
19
  if spec.respond_to?(:metadata)
19
20
  spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
21
  else
21
- raise "RubyGems 2.0 or newer is required to protect against public gem pushespec."
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
23
  end
23
24
 
24
25
  spec.required_rubygems_version = ">= 1.2.0"
25
- spec.rubyforge_project = "patron"
26
26
 
27
27
  spec.files = `git ls-files -z`.split("\x0")
28
28
  spec.bindir = "exe"
@@ -31,16 +31,8 @@ Gem::Specification.new do |spec|
31
31
  spec.extensions = ["ext/patron/extconf.rb"]
32
32
  spec.post_install_message = %q{
33
33
  Thank you for installing Patron. On OSX, make sure you are using libCURL with OpenSSL.
34
- SecureTransport-based builds might cause crashes in forking environment.
34
+ SecureTransport-based builds might cause crashes in forking environments.
35
35
 
36
36
  For more info see https://github.com/curl/curl/issues/788
37
37
  }
38
- spec.add_development_dependency "rake", "~> 10"
39
- spec.add_development_dependency "bundler"
40
- spec.add_development_dependency "rspec", ">= 2.3.0"
41
- spec.add_development_dependency "simplecov", "~> 0.10"
42
- spec.add_development_dependency "yard", "~> 0.9.11"
43
- spec.add_development_dependency "rack", "~> 1"
44
- spec.add_development_dependency "puma", '~> 3.11'
45
- spec.add_development_dependency "rake-compiler"
46
38
  end
data/spec/session_spec.rb CHANGED
@@ -7,6 +7,14 @@ require 'securerandom'
7
7
 
8
8
  describe Patron::Session do
9
9
 
10
+ def yaml_load(str)
11
+ if RUBY_VERSION >= '3.1.0'
12
+ YAML::safe_load(str, permitted_classes: [OpenStruct])
13
+ else
14
+ YAML::load(str)
15
+ end
16
+ end
17
+
10
18
  before(:each) do
11
19
  @session = Patron::Session.new
12
20
  @session.base_url = "http://localhost:9001"
@@ -72,7 +80,7 @@ describe Patron::Session do
72
80
 
73
81
  it "should retrieve a url with :get" do
74
82
  response = @session.get("/test")
75
- body = YAML::load(response.body)
83
+ body = yaml_load(response.body)
76
84
  expect(body.request_method).to be == "GET"
77
85
  end
78
86
 
@@ -88,38 +96,68 @@ describe Patron::Session do
88
96
  end
89
97
 
90
98
  it "should download content with :get and a file path" do
91
- tmpfile = "/tmp/patron_test.yaml"
99
+ tf = Tempfile.new
100
+ tf.close
101
+ tmpfile = tf.path
102
+
92
103
  response = @session.get_file "/test", tmpfile
93
104
  expect(response.body).to be_nil
94
- body = YAML::load_file(tmpfile)
105
+ body = yaml_load(File.open(tmpfile).read)
95
106
  expect(body.request_method).to be == "GET"
96
- FileUtils.rm tmpfile
97
107
  end
98
108
 
99
109
  it "should download correctly(md5 ok) with get_file" do
100
- tmpfile = "/tmp/picture"
110
+ tf = Tempfile.new
111
+ tf.close
112
+ tmpfile = tf.path
113
+
101
114
  response = @session.get_file "/picture", tmpfile
102
115
  expect(response.body).to be_nil
103
116
  expect(File.size(File.join(File.dirname(__FILE__),"../pic.png"))).to be == File.size(tmpfile)
104
- FileUtils.rm tmpfile
117
+ end
118
+
119
+ it "should follow a 307 redirect with Range headers" do
120
+ tf = Tempfile.new
121
+ tf.close
122
+ tmpfile = tf.path
123
+
124
+ response = @session.get_file "/redirect-to-picture", tmpfile, {'Range' => 'bytes=0-3'}
125
+ expect(response.body).to be_nil
126
+ expect(File.size(tmpfile)).to eq(4)
105
127
  end
106
128
 
107
129
  it "downloads a very large file" do
108
- tmpfile = "/tmp/large-succeeded"
130
+ tf = Tempfile.new
131
+ tf.close
132
+ tmpfile = tf.path
133
+
109
134
  @session.get_file "/very-large", tmpfile
110
135
  expect(File.size(tmpfile)).to eq(15 * 1024 * 1024)
111
136
  end
112
137
 
113
138
  it "raises an exception if the download body limit is exceeded when using direct-to-file download" do
114
- tmpfile = "/tmp/large-aborted"
139
+ tf = Tempfile.new
140
+ tf.close
141
+ tmpfile = tf.path
142
+
115
143
  @session.download_byte_limit = 1024
116
144
  @session.buffer_size = 1024
117
145
  expect {
118
146
  @session.get_file "/very-large", tmpfile
119
147
  }.to raise_error(Patron::Error)
120
148
  # On Ruby 2.x Patron::Aborted takes precedence, but
121
- # on 1.9 it will be Patron::PartialFileError
122
- expect(File.size(tmpfile)).to be < (1024 * 3)
149
+ # on 1.9 it will be Patron::PartialFileError, so we cannot match on exception.
150
+ #
151
+ # Not clear why, but Patron (or libCURL, or the two in combination)
152
+ # may fetch more bytes than we permit it to - for example,
153
+ # on Darwin the limit is respected exactly, while on Linux in GH actions
154
+ # it does retrieve substantially more.
155
+ # The URL we are hitting is serving a file that is 15 megabytes. On Linux
156
+ # the tempfile fills to 103344 in our tests, so this is probably either
157
+ # OS-dependent or related to timings with the RubyVM context switching.
158
+ # To ensure our limiting works, it is enough to check that the downloaded file
159
+ # is perceptibly smaller than the complete size of 15MB. Let's say < 1MB.
160
+ expect(File.size(tmpfile)).to be < (1024 * 1024)
123
161
  end
124
162
 
125
163
  it "raises an exception if the download body limit is exceeded when using download-to-memory" do
@@ -135,33 +173,33 @@ describe Patron::Session do
135
173
  it "should not send the user-agent if it has been deleted from headers" do
136
174
  @session.headers.delete 'User-Agent'
137
175
  response = @session.get("/test")
138
- body = YAML::load(response.body)
176
+ body = yaml_load(response.body)
139
177
  expect(body.header["user-agent"]).to be_nil
140
178
  end
141
179
 
142
180
  it "should set the default User-agent" do
143
181
  response = @session.get("/test")
144
- body = YAML::load(response.body)
182
+ body = yaml_load(response.body)
145
183
  expect(body.header["user-agent"]).to be == [Patron.user_agent_string]
146
184
  end
147
185
 
148
186
  it "should include custom headers in a request" do
149
187
  response = @session.get("/test", {"User-Agent" => "PatronTest"})
150
- body = YAML::load(response.body)
188
+ body = yaml_load(response.body)
151
189
  expect(body.header["user-agent"]).to be == ["PatronTest"]
152
190
  end
153
191
 
154
192
  it "should include default headers in a request, if they were defined" do
155
193
  @session.headers = {"User-Agent" => "PatronTest"}
156
194
  response = @session.get("/test")
157
- body = YAML::load(response.body)
195
+ body = yaml_load(response.body)
158
196
  expect(body.header["user-agent"]).to be == ["PatronTest"]
159
197
  end
160
198
 
161
199
  it "should merge custom headers with session headers" do
162
200
  @session.headers["X-Test"] = "Testing"
163
201
  response = @session.get("/test", {"User-Agent" => "PatronTest"})
164
- body = YAML::load(response.body)
202
+ body = yaml_load(response.body)
165
203
  expect(body.header["user-agent"]).to be == ["PatronTest"]
166
204
  expect(body.header["x-test"]).to be == ["Testing"]
167
205
  end
@@ -181,70 +219,83 @@ describe Patron::Session do
181
219
  expect {@session.get("/timeout?millis=400")}.to raise_error(Patron::TimeoutError)
182
220
  end
183
221
 
184
- it "should raise an exception on timeout when reading from a slow resource" do
185
- @session.timeout = 40
222
+ it "should raise an exception on timeout when reading from a slow resource and low speed limit is set" do
223
+ @session.timeout = 10 # Greater than the low_speed_time
186
224
  @session.low_speed_time = 2
187
225
  @session.low_speed_limit = 10
226
+
227
+ # Our test server starts sending the body only after 5 seconds. We should be able to abort
228
+ # by exceeding the low_speed_time.
229
+ started = Time.now.to_f
188
230
  expect {@session.get("/slow")}.to raise_error(Patron::TimeoutError)
231
+ delta_s = Time.now.to_f - started
232
+ # For some reason libCURL times out after 3 seconds instead of 2 sometimes.
233
+ expect(delta_s).to be_within(0.1).of(2).or be_within(0.1).of(3)
189
234
  end
190
235
 
191
236
  it "is able to terminate the thread that is running a slow request using Thread#kill (uses the custom unblock)" do
192
237
  t = Thread.new do
193
238
  session = Patron::Session.new
194
- session.timeout = 40
239
+ session.timeout = 10 # Greater than the sleep duration
195
240
  session.base_url = "http://localhost:9001"
196
241
  session.get("/slow")
197
242
  end
198
243
 
199
- # Our test server starts sending the body only after 20 seconds. We should be able to abort
244
+ # Our test server starts sending the body only after 5 seconds. We should be able to abort
200
245
  # using a signal during that time.
201
- started = Time.now.to_i
202
- sleep 5 # Less than what it takes for the server to respond
246
+ started = Time.now.to_f
247
+ sleep 2 # Less than what it takes for the server to respond
203
248
  t.kill # Kill the thread forcibly
204
- t.join # wrap up the thread. If Patron is still busy there, this join call will still take 15s.
249
+ t.join # wrap up the thread. If Patron is still busy there, this join call will still take another 1s.
205
250
 
206
- delta_s = Time.now.to_i - started
207
- expect(delta_s).to be_within(2).of(5)
251
+ delta_s = Time.now.to_f - started
252
+ # For some reason libCURL times out after 3 seconds instead of 2 sometimes.
253
+ expect(delta_s).to be_within(0.1).of(2).or be_within(0.1).of(3)
208
254
  end
209
255
 
210
- it "is able to terminate the thread that is running a slow request" do
256
+ it "is able to terminate the process that is running a slow request with SIGINT" do
211
257
  pid = Process.fork do
212
258
  trap('SIGINT') do
213
259
  exit # exit the process
214
260
  end
215
261
  session = Patron::Session.new
216
- session.timeout = 40
262
+ session.timeout = 10 # Greater than the sleep duration
217
263
  session.base_url = "http://localhost:9001"
218
264
  session.get("/slow")
219
265
  end
220
266
 
221
- # Our test server starts sending the body only after 20 seconds. We should be able to abort
267
+ # Our test server starts sending the body only after 5 seconds. We should be able to abort
222
268
  # using a signal during that time.
223
- started = Time.now.to_i
224
- sleep 5 # Less than what it takes for the server to respond
225
- Process.kill("INT", pid) # Signal ourselves...
226
- Process.wait(pid) # wrap up the process. If Patron is still busy there, this call will still take 15s.
227
- delta_s = Time.now.to_i - started
228
- expect(delta_s).to be_within(2).of(5)
269
+ started = Time.now.to_f
270
+ sleep 2 # Less than what it takes for the server to respond
271
+ Process.kill("INT", pid) # Signal the process...
272
+ Process.wait(pid) # wrap up the process. If Patron is still busy there, this call will still take another 1s.
273
+ delta_s = Time.now.to_f - started
274
+ # For some reason libCURL times out after 3 seconds instead of 2 sometimes.
275
+ expect(delta_s).to be_within(0.1).of(2).or be_within(0.1).of(3)
229
276
  end
230
277
 
231
278
  it "receives progress callbacks" do
232
279
  session = Patron::Session.new
233
- session.timeout = 40
280
+ session.timeout = 10 # # Greater than the /slow respond time (5 seconds)
234
281
  session.base_url = "http://localhost:9001"
235
282
  callback_args = []
236
283
  session.progress_callback = Proc.new {|dltotal, dlnow, ultotal, ulnow|
237
284
  callback_args << [dltotal, dlnow, ultotal, ulnow]
238
285
  }
239
286
  session.get("/slow")
287
+ expect(callback_args).not_to be_empty
240
288
 
289
+ # Make sure that the callback setting was not reset.
290
+ callback_args = []
291
+ session.get("/slow")
241
292
  expect(callback_args).not_to be_empty
242
293
  end
243
294
 
244
295
  it "should follow redirects by default" do
245
296
  @session.max_redirects = 1
246
297
  response = @session.get("/redirect")
247
- body = YAML::load(response.body)
298
+ body = yaml_load(response.body)
248
299
  expect(response.status).to be == 200
249
300
  expect(body.path).to be == "/test"
250
301
  end
@@ -277,26 +328,26 @@ describe Patron::Session do
277
328
 
278
329
  it "should send a delete request with :delete" do
279
330
  response = @session.delete("/test")
280
- body = YAML::load(response.body)
331
+ body = yaml_load(response.body)
281
332
  expect(body.request_method).to be == "DELETE"
282
333
  end
283
334
 
284
335
  it "should send a COPY request with :copy" do
285
336
  response = @session.copy("/test", "/test2")
286
- body = YAML::load(response.body)
337
+ body = yaml_load(response.body)
287
338
  expect(body.request_method).to be == "COPY"
288
339
  end
289
340
 
290
341
  it "should include a Destination header in COPY requests" do
291
342
  response = @session.copy("/test", "/test2")
292
- body = YAML::load(response.body)
343
+ body = yaml_load(response.body)
293
344
  expect(body.header['destination'].first).to be == "/test2"
294
345
  end
295
346
 
296
347
  it "should upload data with :get" do
297
348
  data = "upload data"
298
349
  response = @session.request(:get, "/test", {}, :data => data)
299
- body = YAML::load(response.body)
350
+ body = yaml_load(response.body)
300
351
  expect(body.request_method).to be == "GET"
301
352
  expect(body.header['content-length']).to be == [data.size.to_s]
302
353
  end
@@ -304,7 +355,7 @@ describe Patron::Session do
304
355
  it "should call to_s on the data being uploaded via GET if it is not already a String" do
305
356
  data = 12345
306
357
  response = @session.request(:get, "/test", {}, :data => data)
307
- body = YAML::load(response.body)
358
+ body = yaml_load(response.body)
308
359
  expect(body.request_method).to be == "GET"
309
360
  end
310
361
 
@@ -315,7 +366,7 @@ describe Patron::Session do
315
366
  # you can have very deeply going queries, which are still technically GETs
316
367
  data = Random.new.bytes(1024 * 24)
317
368
  response = @session.request(:get, "/test", {}, :data => data)
318
- body = YAML::load(response.body)
369
+ body = yaml_load(response.body)
319
370
  expect(body.request_method).to be == "GET"
320
371
  expect(body.header['content-length']).to be == [data.size.to_s]
321
372
  end
@@ -323,7 +374,7 @@ describe Patron::Session do
323
374
  it "should upload data with :put" do
324
375
  data = Random.new.bytes(1024 * 24)
325
376
  response = @session.put("/test", data)
326
- body = YAML::load(response.body)
377
+ body = yaml_load(response.body)
327
378
  expect(body.request_method).to be == "PUT"
328
379
  expect(body.header['content-length']).to be == [data.size.to_s]
329
380
  end
@@ -334,7 +385,7 @@ describe Patron::Session do
334
385
  data.flush; data.rewind
335
386
 
336
387
  response = @session.put("/test", data, {'Expect' => ''})
337
- body = YAML::load(response.body)
388
+ body = yaml_load(response.body)
338
389
  expect(body.request_method).to be == "PUT"
339
390
  expect(body.header['content-length']).to be == [data.size.to_s]
340
391
  end
@@ -342,14 +393,14 @@ describe Patron::Session do
342
393
  it "should upload data with :patch" do
343
394
  data = "upload data"
344
395
  response = @session.patch("/testpatch", data)
345
- body = YAML::load(response.body)
396
+ body = yaml_load(response.body)
346
397
  expect(body.body).to eq("upload data")
347
398
  end
348
399
 
349
400
  it "should upload data with :delete" do
350
401
  data = "upload data"
351
402
  response = @session.request(:delete, "/test", {}, :data => data)
352
- body = YAML::load(response.body)
403
+ body = yaml_load(response.body)
353
404
  expect(body.request_method).to be == "DELETE"
354
405
  expect(body.header['content-length']).to be == [data.size.to_s]
355
406
  end
@@ -360,7 +411,7 @@ describe Patron::Session do
360
411
 
361
412
  it "should upload a file with :put" do
362
413
  response = @session.put_file("/test", "LICENSE")
363
- body = YAML::load(response.body)
414
+ body = yaml_load(response.body)
364
415
  expect(body.request_method).to be == "PUT"
365
416
  end
366
417
 
@@ -370,21 +421,21 @@ describe Patron::Session do
370
421
 
371
422
  it "should use chunked encoding when uploading a file with :put" do
372
423
  response = @session.put_file("/test", "LICENSE")
373
- body = YAML::load(response.body)
424
+ body = yaml_load(response.body)
374
425
  expect(body.header['transfer-encoding'].first).to be == "chunked"
375
426
  end
376
427
 
377
428
  it "should call to_s on the data being uploaded via POST if it is not already a String" do
378
429
  data = 12345
379
430
  response = @session.post("/testpost", data)
380
- body = YAML::load(response.body)
431
+ body = yaml_load(response.body)
381
432
  expect(body['body']).to eq("12345")
382
433
  end
383
434
 
384
435
  it "should upload data with :post" do
385
436
  data = "upload data"
386
437
  response = @session.post("/test", data)
387
- body = YAML::load(response.body)
438
+ body = yaml_load(response.body)
388
439
  expect(body.request_method).to be == "POST"
389
440
  expect(body.header['content-length']).to be == [data.size.to_s]
390
441
  end
@@ -392,7 +443,7 @@ describe Patron::Session do
392
443
  it "should POST a hash of arguments as a urlencoded form" do
393
444
  data = {:foo => 123, 'baz' => '++hello world++'}
394
445
  response = @session.post("/testpost", data)
395
- body = YAML::load(response.body)
446
+ body = yaml_load(response.body)
396
447
  expect(body['content_type']).to be == "application/x-www-form-urlencoded"
397
448
  expect(body['body']).to match(/baz=%2B%2Bhello%20world%2B%2B/)
398
449
  expect(body['body']).to match(/foo=123/)
@@ -404,13 +455,13 @@ describe Patron::Session do
404
455
 
405
456
  it "should upload a file with :post" do
406
457
  response = @session.post_file("/test", "LICENSE")
407
- body = YAML::load(response.body)
458
+ body = yaml_load(response.body)
408
459
  expect(body.request_method).to be == "POST"
409
460
  end
410
461
 
411
462
  it "should upload a multipart with :post" do
412
463
  response = @session.post_multipart("/test", { :test_data => "123" }, { :test_file => "LICENSE" } )
413
- body = YAML::load(response.body)
464
+ body = yaml_load(response.body)
414
465
  expect(body.request_method).to be == "POST"
415
466
  end
416
467
 
@@ -420,7 +471,7 @@ describe Patron::Session do
420
471
 
421
472
  it "should use chunked encoding when uploading a file with :post" do
422
473
  response = @session.post_file("/test", "LICENSE")
423
- body = YAML::load(response.body)
474
+ body = yaml_load(response.body)
424
475
  expect(body.header['transfer-encoding'].first).to be == "chunked"
425
476
  end
426
477
 
@@ -428,18 +479,20 @@ describe Patron::Session do
428
479
  @session.username = "foo"
429
480
  @session.password = "bar"
430
481
  response = @session.get("/test")
431
- body = YAML::load(response.body)
482
+ body = yaml_load(response.body)
432
483
  expect(body.header['authorization']).to be == [encode_authz("foo", "bar")]
433
484
  end
434
485
 
435
486
  it "should store cookies across multiple requests" do
436
487
  tf = Tempfile.new('cookiejar')
488
+ tf.close
437
489
  cookie_jar_path = tf.path
438
490
 
439
491
  @session.handle_cookies(cookie_jar_path)
440
492
  response = @session.get("/setcookie").body
441
493
 
442
- cookie_jar_contents = tf.read
494
+ cookie_jar_contents = tf.open.read
495
+ tf.unlink
443
496
  expect(cookie_jar_contents).not_to be_empty
444
497
  expect(cookie_jar_contents).to include('Netscape HTTP Cookie File')
445
498
  end
@@ -447,12 +500,12 @@ describe Patron::Session do
447
500
  it "should handle cookies if set" do
448
501
  @session.handle_cookies
449
502
  response = @session.get("/setcookie").body
450
- expect(YAML::load(response).header['cookie'].first).to be == "session_id=foo123"
503
+ expect(yaml_load(response).header['cookie'].first).to be == "session_id=foo123"
451
504
  end
452
505
 
453
506
  it "should not handle cookies by default" do
454
507
  response = @session.get("/setcookie").body
455
- expect(YAML::load(response).header).to_not include('cookie')
508
+ expect(yaml_load(response).header).to_not include('cookie')
456
509
  end
457
510
 
458
511
  it "should ignore a wrong Content-Length when asked to" do
@@ -497,7 +550,7 @@ describe Patron::Session do
497
550
 
498
551
  expect {
499
552
  response = @session.get("/test")
500
- body = YAML::load(response.body)
553
+ body = yaml_load(response.body)
501
554
  }.to_not raise_error
502
555
 
503
556
  expect(body.request_method).to be == "GET"
@@ -516,13 +569,13 @@ describe Patron::Session do
516
569
 
517
570
  it "should serialize query params and append them to the url" do
518
571
  response = @session.request(:get, "/test", {}, :query => {:foo => "bar"})
519
- request = YAML::load(response.body)
572
+ request = yaml_load(response.body)
520
573
  expect(request.path + '?' + request.query_string).to be == "/test?foo=bar"
521
574
  end
522
575
 
523
576
  it "should merge parameters in the :query option with pre-existing query parameters" do
524
577
  response = @session.request(:get, "/test?foo=bar", {}, :query => {:baz => "quux"})
525
- request = YAML::load(response.body)
578
+ request = yaml_load(response.body)
526
579
  expect(request.path + '?' + request.query_string).to be == "/test?foo=bar&baz=quux"
527
580
  end
528
581