bpardee-net-http-persistent 1.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.
@@ -0,0 +1,631 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
3
+ require 'net/http/persistent'
4
+ require 'openssl'
5
+ require 'stringio'
6
+ require 'ostruct'
7
+ require 'logger'
8
+
9
+ CMD_SUCCESS = 'success'
10
+ CMD_SLEEP = 'sleep'
11
+ CMD_BAD_RESPONSE = 'bad_response'
12
+ CMD_EOF_ERROR = 'eof_error'
13
+ CMD_CONNRESET = 'connreset'
14
+ CMD_ECHO = 'echo'
15
+
16
+ PASS = 'pass'
17
+ FAIL = 'fail'
18
+
19
+ DUMMY_OPEN_TIMEOUT_FOR_HOSTDOWN = 9000
20
+ DUMMY_OPEN_TIMEOUT_FOR_CONNREFUSED = 9001
21
+
22
+ $debug = false
23
+
24
+ class Net::HTTP
25
+ def connect
26
+ raise Errno::EHOSTDOWN if open_timeout == DUMMY_OPEN_TIMEOUT_FOR_HOSTDOWN
27
+ raise Errno::ECONNREFUSED if open_timeout == DUMMY_OPEN_TIMEOUT_FOR_CONNREFUSED
28
+ end
29
+
30
+ def successful_response
31
+ r = Net::HTTPResponse.allocate
32
+ def r.http_version() '1.1' end
33
+ def r.read_body() :read_body end
34
+ yield r if block_given?
35
+ r
36
+ end
37
+
38
+ def request(req, &block)
39
+ $count += 1
40
+ puts "path=#{req.path} count=#{$count}" if $debug
41
+ args = req.path[1..-1].split('/')
42
+ cmd = args.shift
43
+ i = $count % args.size if args.size > 0
44
+ puts "i=#{i}" if $debug
45
+ if cmd == CMD_ECHO
46
+ res = successful_response(&block)
47
+ eval "def res.body() \"#{req.body}\" end"
48
+ return res
49
+ elsif cmd == CMD_SUCCESS || args[i] == PASS
50
+ return successful_response(&block)
51
+ end
52
+ case cmd
53
+ when CMD_SLEEP
54
+ sleep args[i].to_i
55
+ return successful_response(&block)
56
+ when CMD_BAD_RESPONSE
57
+ raise Net::HTTPBadResponse.new('Dummy bad response')
58
+ when CMD_EOF_ERROR
59
+ raise EOFError.new('Dummy EOF error')
60
+ when CMD_CONNRESET
61
+ raise Errno::ECONNRESET
62
+ else
63
+ return successful_response(&block)
64
+ end
65
+ end
66
+ end
67
+
68
+ class Net::HTTP::Persistent
69
+ attr_reader :pool_hash
70
+
71
+ # Make private methods public
72
+ send(:public, *(self.private_instance_methods - Object.private_instance_methods))
73
+ end
74
+
75
+ class TestNetHttpPersistent < MiniTest::Unit::TestCase
76
+
77
+ def uri_for(*args)
78
+ URI.parse("http://example.com/#{args.join('/')}")
79
+ end
80
+
81
+ def request_command(req, *args)
82
+ puts "uri=#{uri_for(args)}" if $debug
83
+ @http.request(uri_for(args), req)
84
+ end
85
+
86
+ def http_and_io(options={})
87
+ io = StringIO.new
88
+ logger = Logger.new(io)
89
+ logger.level = Logger::INFO
90
+ default_options = {:name => 'TestNetHTTPPersistent', :logger => logger, :pool_size => 1}
91
+ http = Net::HTTP::Persistent.new(default_options.merge(options))
92
+ [http, io]
93
+ end
94
+
95
+ def setup
96
+ $count = -1
97
+ @http, @io = http_and_io
98
+ @uri = uri_for CMD_SUCCESS
99
+
100
+ ENV.delete 'http_proxy'
101
+ ENV.delete 'HTTP_PROXY'
102
+ ENV.delete 'http_proxy_user'
103
+ ENV.delete 'HTTP_PROXY_USER'
104
+ ENV.delete 'http_proxy_pass'
105
+ ENV.delete 'HTTP_PROXY_PASS'
106
+ end
107
+
108
+ def teardown
109
+ end
110
+
111
+ def test_initialize
112
+ assert_nil @http.proxy_uri
113
+ end
114
+
115
+ def test_initialize_name
116
+ http = Net::HTTP::Persistent.new(:name => 'name')
117
+ assert_equal 'name', http.name
118
+ end
119
+
120
+ def test_initialize_env
121
+ ENV['HTTP_PROXY'] = 'proxy.example'
122
+ http = Net::HTTP::Persistent.new(:proxy => :ENV)
123
+
124
+ assert_equal URI.parse('http://proxy.example'), http.proxy_uri
125
+ end
126
+
127
+ def test_initialize_uri
128
+ proxy_uri = URI.parse 'http://proxy.example'
129
+
130
+ http = Net::HTTP::Persistent.new(:proxy => proxy_uri)
131
+
132
+ assert_equal proxy_uri, http.proxy_uri
133
+ end
134
+
135
+ def test_connection
136
+ @http.open_timeout = 123
137
+ @http.read_timeout = 321
138
+ pool = @http.pool_for @uri
139
+ assert_same pool, @http.pool_hash['example.com:80']
140
+ pool.with_connection do |c|
141
+ assert c.started?
142
+ refute c.proxy?
143
+
144
+ assert_equal 123, c.open_timeout
145
+ assert_equal 321, c.read_timeout
146
+
147
+ assert_equal 'example.com', c.address
148
+ assert_equal 80, c.port
149
+ end
150
+ end
151
+
152
+ def test_connection_for_cached
153
+ c1, c2 = nil, nil
154
+ pool = @http.pool_for @uri
155
+ assert_same pool, @http.pool_hash['example.com:80']
156
+ pool.with_connection do |c|
157
+ c1 = c
158
+ assert c.started?
159
+ end
160
+ pool.with_connection do |c|
161
+ c2 = c
162
+ assert c.started?
163
+ end
164
+ assert_same c1,c2
165
+ end
166
+
167
+ def test_connection_for_debug_output
168
+ io = StringIO.new
169
+ @http.debug_output = io
170
+
171
+ pool = @http.pool_for @uri
172
+ assert_same pool, @http.pool_hash['example.com:80']
173
+ pool.with_connection do |c|
174
+ assert c.started?
175
+ assert_equal io, c.instance_variable_get(:@debug_output)
176
+ assert_equal 'example.com', c.address
177
+ assert_equal 80, c.port
178
+ end
179
+ end
180
+
181
+ def test_connection_for_hostdown
182
+ @http.open_timeout = DUMMY_OPEN_TIMEOUT_FOR_HOSTDOWN
183
+ e = assert_raises Net::HTTP::Persistent::Error do
184
+ request_command(nil, CMD_SUCCESS)
185
+ end
186
+
187
+ assert_match %r%host down%, e.message
188
+ end
189
+
190
+ def test_connection_for_connrefused
191
+ @http.open_timeout = DUMMY_OPEN_TIMEOUT_FOR_CONNREFUSED
192
+ e = assert_raises Net::HTTP::Persistent::Error do
193
+ request_command(nil, CMD_SUCCESS)
194
+ end
195
+
196
+ assert_match %r%connection refused%, e.message
197
+ end
198
+
199
+ def test_connection_for_proxy
200
+ uri = URI.parse 'http://proxy.example'
201
+ uri.user = 'johndoe'
202
+ uri.password = 'muffins'
203
+
204
+ http = Net::HTTP::Persistent.new(:proxy => uri)
205
+ pool = http.pool_for(@uri)
206
+ assert_same pool, http.pool_hash['example.com:80:proxy.example:80:johndoe:muffins']
207
+ pool.with_connection do |c|
208
+ assert c.started?
209
+ assert c.proxy?
210
+ end
211
+ end
212
+
213
+ def test_error_message
214
+ 6.times do
215
+ request_command nil, CMD_EOF_ERROR, PASS, PASS, PASS, PASS, FAIL, PASS, PASS
216
+ end
217
+
218
+ assert_match "after 5 requests on", @io.string
219
+ end
220
+
221
+ def test_escape
222
+ assert_nil @http.escape nil
223
+
224
+ assert_equal '%20', @http.escape(' ')
225
+ end
226
+
227
+ def test_finish
228
+ c = Object.new
229
+ def c.finish; @finished = true end
230
+ def c.finished?; @finished end
231
+ def c.start; @started = true end
232
+ def c.started?; @started end
233
+
234
+ @http.finish c
235
+
236
+ refute c.started?
237
+ assert c.finished?
238
+ end
239
+
240
+ def test_finish_io_error
241
+ c = Object.new
242
+ def c.finish; @finished = true; raise IOError end
243
+ def c.finished?; @finished end
244
+ def c.start; @started = true end
245
+ def c.started?; @started end
246
+
247
+ @http.finish c
248
+
249
+ refute c.started?
250
+ assert c.finished?
251
+ end
252
+
253
+ def test_http_version
254
+ assert_nil @http.http_version @uri
255
+
256
+ request_command nil, CMD_SUCCESS
257
+
258
+ assert_equal '1.1', @http.http_version(@uri)
259
+ end
260
+
261
+ def test_idempotent_eh
262
+ assert @http.idempotent? Net::HTTP::Delete.new '/'
263
+ assert @http.idempotent? Net::HTTP::Get.new '/'
264
+ assert @http.idempotent? Net::HTTP::Head.new '/'
265
+ assert @http.idempotent? Net::HTTP::Options.new '/'
266
+ assert @http.idempotent? Net::HTTP::Put.new '/'
267
+ assert @http.idempotent? Net::HTTP::Trace.new '/'
268
+
269
+ refute @http.idempotent? Net::HTTP::Post.new '/'
270
+ end
271
+
272
+ def test_normalize_uri
273
+ assert_equal 'http://example', @http.normalize_uri('example')
274
+ assert_equal 'http://example', @http.normalize_uri('http://example')
275
+ assert_equal 'https://example', @http.normalize_uri('https://example')
276
+ end
277
+
278
+ def test_proxy_from_env
279
+ ENV['HTTP_PROXY'] = 'proxy.example'
280
+ ENV['HTTP_PROXY_USER'] = 'johndoe'
281
+ ENV['HTTP_PROXY_PASS'] = 'muffins'
282
+
283
+ uri = @http.proxy_from_env
284
+
285
+ expected = URI.parse 'http://proxy.example'
286
+ expected.user = 'johndoe'
287
+ expected.password = 'muffins'
288
+
289
+ assert_equal expected, uri
290
+ end
291
+
292
+ def test_proxy_from_env_lower
293
+ ENV['http_proxy'] = 'proxy.example'
294
+ ENV['http_proxy_user'] = 'johndoe'
295
+ ENV['http_proxy_pass'] = 'muffins'
296
+
297
+ uri = @http.proxy_from_env
298
+
299
+ expected = URI.parse 'http://proxy.example'
300
+ expected.user = 'johndoe'
301
+ expected.password = 'muffins'
302
+
303
+ assert_equal expected, uri
304
+ end
305
+
306
+ def test_proxy_from_env_nil
307
+ uri = @http.proxy_from_env
308
+
309
+ assert_nil uri
310
+
311
+ ENV['HTTP_PROXY'] = ''
312
+
313
+ uri = @http.proxy_from_env
314
+
315
+ assert_nil uri
316
+ end
317
+
318
+ def test_remove
319
+ http, io = http_and_io(:pool_size => 3)
320
+ request_command(nil, CMD_SUCCESS)
321
+ pool = http.pool_for(@uri)
322
+ 2.times do
323
+ conns = []
324
+ pool.with_connection do |c1|
325
+ pool.with_connection do |c2|
326
+ conns << c2
327
+ pool.with_connection do |c3|
328
+ conns << c3
329
+ begin
330
+ Timeout.timeout(2) do
331
+ pool.with_connection { |c4| }
332
+ flunk('should NOT have been able to get 4th connection')
333
+ end
334
+ rescue Timeout::Error => e
335
+ pass('successfully failed to get a connection')
336
+ end
337
+ http.remove(pool, c1)
338
+ Timeout.timeout(1) do
339
+ begin
340
+ pool.with_connection do |c4|
341
+ conns << c4
342
+ end
343
+ rescue Timeout::Error => e
344
+ flunk('should have been able to get 4th connection')
345
+ end
346
+ end
347
+ end
348
+ end
349
+ end
350
+ pool.with_connection do |c1|
351
+ pool.with_connection do |c2|
352
+ pool.with_connection do |c3|
353
+ assert_equal conns, [c1,c2,c3]
354
+ end
355
+ end
356
+ end
357
+ # Do it a 2nd time with finish returning an IOError
358
+ c1 = conns[0]
359
+ def c1.finish
360
+ super
361
+ raise IOError
362
+ end
363
+ end
364
+ end
365
+
366
+ def test_renew
367
+ http, io = http_and_io(:pool_size => 3)
368
+ request_command(nil, CMD_SUCCESS)
369
+ pool = http.pool_for(@uri)
370
+ 2.times do
371
+ conns = []
372
+ pool.with_connection do |c1|
373
+ pool.with_connection do |c2|
374
+ conns << c2
375
+ pool.with_connection do |c3|
376
+ conns << c3
377
+ new_c1 = http.renew(pool, c1)
378
+ refute_equal(c1, new_c1)
379
+ conns.unshift(new_c1)
380
+ end
381
+ end
382
+ end
383
+ pool.with_connection do |c1|
384
+ pool.with_connection do |c2|
385
+ pool.with_connection do |c3|
386
+ assert_equal conns, [c1,c2,c3]
387
+ end
388
+ end
389
+ end
390
+ # Do it a 2nd time with finish returning an IOError
391
+ c1 = conns[0]
392
+ def c1.finish
393
+ super
394
+ raise IOError
395
+ end
396
+ end
397
+ end
398
+
399
+ def test_renew_with_exception
400
+ http, io = http_and_io(:pool_size => 2)
401
+ pool = http.pool_for(@uri)
402
+ [[DUMMY_OPEN_TIMEOUT_FOR_HOSTDOWN, %r%host down%], [DUMMY_OPEN_TIMEOUT_FOR_CONNREFUSED, %r%connection refused%]].each do |pair|
403
+ dummy_open_timeout = pair.first
404
+ error_message = pair.last
405
+ pool.with_connection do |c|
406
+ old_c = c
407
+ http.open_timeout = dummy_open_timeout
408
+ e = assert_raises Net::HTTP::Persistent::Error do
409
+ new_c = http.renew pool, c
410
+ end
411
+ assert_match error_message, e.message
412
+
413
+ # Make sure our pool is still in good shape
414
+ http.open_timeout = 5 # Any valid timeout will do
415
+ pool.with_connection do |c1|
416
+ refute_equal old_c, c1
417
+ pool.with_connection do |c2|
418
+ refute_equal old_c, c2
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ def test_request
426
+ @http.headers['user-agent'] = 'test ua'
427
+ req = Net::HTTP::Get.new(@uri.request_uri)
428
+ res = @http.request(@uri, req)
429
+
430
+ assert_kind_of Net::HTTPResponse, res
431
+
432
+ assert_kind_of Net::HTTP::Get, req
433
+ assert_equal @uri.path, req.path
434
+ assert_equal 'keep-alive', req['connection']
435
+ assert_equal '30', req['keep-alive']
436
+ assert_match %r%test ua%, req['user-agent']
437
+ end
438
+
439
+ def test_request_block
440
+ @http.headers['user-agent'] = 'test ua'
441
+ body = nil
442
+
443
+ req = Net::HTTP::Get.new(@uri.request_uri)
444
+ res = @http.request(@uri, req) do |r|
445
+ body = r.read_body
446
+ end
447
+
448
+ assert_kind_of Net::HTTPResponse, res
449
+ refute_nil body
450
+
451
+ assert_kind_of Net::HTTP::Get, req
452
+ assert_equal @uri.path, req.path
453
+ assert_equal 'keep-alive', req['connection']
454
+ assert_equal '30', req['keep-alive']
455
+ assert_match %r%test ua%, req['user-agent']
456
+ end
457
+
458
+ def test_request_bad_response
459
+ e = assert_raises Net::HTTP::Persistent::Error do
460
+ request_command nil, CMD_BAD_RESPONSE, FAIL, FAIL
461
+ end
462
+ assert_match %r%too many bad responses%, e.message
463
+ assert_match %r%Renewing connection because of bad response%, @io.string
464
+ assert_match %r%Removing connection because of too many bad responses%, @io.string
465
+
466
+ res = request_command nil, CMD_SUCCESS
467
+ assert_kind_of Net::HTTPResponse, res
468
+ end
469
+
470
+ def test_request_connreset
471
+ e = assert_raises Net::HTTP::Persistent::Error do
472
+ request_command nil, CMD_CONNRESET, FAIL, FAIL
473
+ end
474
+
475
+ assert_match %r%too many connection resets%, e.message
476
+ assert_match %r%Renewing connection %, @io.string
477
+ assert_match %r%Removing connection %, @io.string
478
+
479
+ res = request_command nil, CMD_SUCCESS
480
+ assert_kind_of Net::HTTPResponse, res
481
+ end
482
+
483
+ def test_request_bad_response_retry
484
+ res = request_command nil, CMD_BAD_RESPONSE, FAIL, PASS
485
+ assert_match %r%Renewing connection because of bad response%, @io.string
486
+ assert_kind_of Net::HTTPResponse, res
487
+ end
488
+
489
+ def test_request_connreset_retry
490
+ res = request_command nil, CMD_CONNRESET, FAIL, PASS
491
+ assert_match %r%Renewing connection %, @io.string
492
+ assert_kind_of Net::HTTPResponse, res
493
+ end
494
+
495
+ def test_request_bad_response_post
496
+ uri = uri_for CMD_BAD_RESPONSE, FAIL, PASS
497
+ post = Net::HTTP::Post.new(uri.request_uri)
498
+ e = assert_raises Net::HTTP::Persistent::Error do
499
+ request_command post, CMD_BAD_RESPONSE, FAIL, PASS
500
+ end
501
+ assert_match %r%too many bad responses%, e.message
502
+ assert_match %r%Removing connection because of too many bad responses%, @io.string
503
+
504
+ res = request_command nil, CMD_SUCCESS
505
+ assert_kind_of Net::HTTPResponse, res
506
+ end
507
+
508
+
509
+ def test_request_connreset_post
510
+ uri = uri_for CMD_CONNRESET, FAIL, PASS
511
+ post = Net::HTTP::Post.new(uri.request_uri)
512
+ e = assert_raises Net::HTTP::Persistent::Error do
513
+ request_command post, CMD_CONNRESET, FAIL, PASS
514
+ end
515
+ assert_match %r%too many connection resets%, e.message
516
+ assert_match %r%Removing connection %, @io.string
517
+
518
+ res = request_command nil, CMD_SUCCESS
519
+ assert_kind_of Net::HTTPResponse, res
520
+ end
521
+
522
+ def test_request_bad_response_post_force_retry
523
+ @http.force_retry = true
524
+ uri = uri_for CMD_BAD_RESPONSE, FAIL, PASS
525
+ post = Net::HTTP::Post.new(uri.request_uri)
526
+ res = request_command post, CMD_BAD_RESPONSE, FAIL, PASS
527
+ assert_match %r%Renewing connection because of bad response%, @io.string
528
+ assert_kind_of Net::HTTPResponse, res
529
+ end
530
+
531
+ def test_request_connreset_post_force_retry
532
+ @http.force_retry = true
533
+ uri = uri_for CMD_CONNRESET, FAIL, PASS
534
+ post = Net::HTTP::Post.new(uri.request_uri)
535
+ res = request_command post, CMD_CONNRESET, FAIL, PASS
536
+ assert_match %r%Renewing connection %, @io.string
537
+ assert_kind_of Net::HTTPResponse, res
538
+ end
539
+
540
+ def test_request_post
541
+ uri = uri_for CMD_ECHO
542
+ post = Net::HTTP::Post.new(uri.request_uri)
543
+ post.body = 'hello Net::HTTP::Persistent'
544
+ res = request_command post, CMD_ECHO
545
+ assert_kind_of Net::HTTPResponse, res
546
+ assert_equal post.body, res.body
547
+ end
548
+
549
+ # def test_shutdown
550
+ # c = connection
551
+ # cs = conns
552
+ # rs = reqs
553
+ #
554
+ # orig = @http
555
+ # @http = Net::HTTP::Persistent.new 'name'
556
+ # c2 = connection
557
+ #
558
+ # orig.shutdown
559
+ #
560
+ # assert c.finished?
561
+ # refute c2.finished?
562
+ #
563
+ # refute_same cs, conns
564
+ # refute_same rs, reqs
565
+ # end
566
+ #
567
+ # def test_shutdown_not_started
568
+ # c = Object.new
569
+ # def c.finish() raise IOError end
570
+ #
571
+ # conns["#{@uri.host}:#{@uri.port}"] = c
572
+ #
573
+ # @http.shutdown
574
+ #
575
+ # assert_nil Thread.current[@http.connection_key]
576
+ # assert_nil Thread.current[@http.request_key]
577
+ # end
578
+ #
579
+ # def test_shutdown_no_connections
580
+ # @http.shutdown
581
+ #
582
+ # assert_nil Thread.current[@http.connection_key]
583
+ # assert_nil Thread.current[@http.request_key]
584
+ # end
585
+
586
+ def test_ssl
587
+ @http.verify_callback = :callback
588
+ c = Net::HTTP.new 'localhost', 80
589
+
590
+ @http.ssl c
591
+
592
+ assert c.use_ssl?
593
+ assert_equal OpenSSL::SSL::VERIFY_NONE, c.verify_mode
594
+ assert_nil c.verify_callback
595
+ end
596
+
597
+ def test_ssl_ca_file
598
+ @http.ca_file = 'ca_file'
599
+ @http.verify_callback = :callback
600
+ c = Net::HTTP.new 'localhost', 80
601
+
602
+ @http.ssl c
603
+
604
+ assert c.use_ssl?
605
+ assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode
606
+ assert_equal :callback, c.verify_callback
607
+ end
608
+
609
+ def test_ssl_certificate
610
+ @http.certificate = :cert
611
+ @http.private_key = :key
612
+ c = Net::HTTP.new 'localhost', 80
613
+
614
+ @http.ssl c
615
+
616
+ assert c.use_ssl?
617
+ assert_equal :cert, c.cert
618
+ assert_equal :key, c.key
619
+ end
620
+
621
+ def test_ssl_verify_mode
622
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
623
+ c = Net::HTTP.new 'localhost', 80
624
+
625
+ @http.ssl c
626
+
627
+ assert c.use_ssl?
628
+ assert_equal OpenSSL::SSL::VERIFY_NONE, c.verify_mode
629
+ end
630
+ end
631
+