net-http-persistent 2.1 → 2.2

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.tar.gz.sig CHANGED
Binary file
data/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ === 2.2 / 2011-10-24
2
+
3
+ * Minor Enhancement
4
+ * Added timeouts for idle connections which are set through #idle_timeout.
5
+ The default timeout is 5 seconds. Reducing the idle timeout is preferred
6
+ over setting #retry_change_requests to true if you wish to avoid the "too
7
+ many connection resets" error when POSTing data.
8
+ * Documented tunables and settings in one place in Net::HTTP::Persistent
9
+
1
10
  === 2.1 / 2011-09-19
2
11
 
3
12
  * Minor Enhancement
data/README.txt CHANGED
@@ -13,7 +13,7 @@ Creating a new HTTP connection for every request involves an extra TCP
13
13
  round-trip and causes TCP congestion avoidance negotiation to start over.
14
14
 
15
15
  Net::HTTP supports persistent connections with some API methods but does not
16
- handle reconnection gracefully. net-http-persistent supports reconnection
16
+ handle reconnection gracefully. Net::HTTP::Persistent supports reconnection
17
17
  and retry according to RFC 2616.
18
18
 
19
19
  == FEATURES/PROBLEMS:
@@ -28,22 +28,128 @@ end
28
28
  #
29
29
  # Example:
30
30
  #
31
- # uri = URI.parse 'http://example.com/awesome/web/service'
32
- # http = Net::HTTP::Persistent.new
33
- # stuff = http.request uri # performs a GET
31
+ # require 'net/http/persistent'
34
32
  #
35
- # # perform a POST
33
+ # uri = URI 'http://example.com/awesome/web/service'
34
+ #
35
+ # http = Net::HTTP::Persistent.new 'my_app_name'
36
+ #
37
+ # # perform a GET
38
+ # response = http.request uri
39
+ #
40
+ # # create a POST
36
41
  # post_uri = uri + 'create'
37
42
  # post = Net::HTTP::Post.new post_uri.path
38
43
  # post.set_form_data 'some' => 'cool data'
39
- # http.request post_uri, post # URI is always required
44
+ #
45
+ # # perform the POST, the URI is always required
46
+ # response http.request post_uri, post
47
+ #
48
+ # == SSL
49
+ #
50
+ # SSL connections are automatically created depending upon the scheme of the
51
+ # URI. SSL connections are automatically verified against the default
52
+ # certificate store for your computer. You can override this by changing
53
+ # verify_mode or by specifying an alternate cert_store.
54
+ #
55
+ # Here are the SSL settings, see the individual methods for documentation:
56
+ #
57
+ # #certificate :: This client's certificate
58
+ # #ca_file :: The certificate-authority
59
+ # #cert_store :: An SSL certificate store
60
+ # #private_key :: The client's SSL private key
61
+ # #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new
62
+ # connection
63
+ # #verify_callback :: For server certificate verification
64
+ # #verify_mode :: How connections should be verified
65
+ #
66
+ # == Proxies
67
+ #
68
+ # A proxy can be used either by providing a URI as the second argument of
69
+ # ::new or by giving <code>:ENV</code> as the second argument which will
70
+ # consult environment variables. See proxy_from_env for details.
71
+ #
72
+ # == Tuning
73
+ #
74
+ # === Segregation
75
+ #
76
+ # By providing an application name to ::new you can separate your connections
77
+ # from the connections of other applications.
78
+ #
79
+ # === Idle Timeout
80
+ #
81
+ # If a connection hasn't been used for 5 seconds it will automatically be
82
+ # reset upon the next use to avoid attempting to send to a closed connection.
83
+ # This can be adjusted through idle_timeout.
84
+ #
85
+ # Reducing this value may help avoid the "too many connection resets" error
86
+ # when sending non-idempotent requests while increasing this value will cause
87
+ # fewer round-trips.
88
+ #
89
+ # === Read Timeout
90
+ #
91
+ # The amount of time allowed between reading two chunks from the socket. Set
92
+ # through #read_timeout
93
+ #
94
+ # === Open Timeout
95
+ #
96
+ # The amount of time to wait for a connection to be opened. Set through
97
+ # #open_timeout.
98
+ #
99
+ # === Socket Options
100
+ #
101
+ # Socket options may be set on newly-created connections. See #socket_options
102
+ # for details.
103
+ #
104
+ # === Non-Idempotent Requests
105
+ #
106
+ # By default non-idempotent requests will not be retried per RFC 2616. By
107
+ # setting retry_change_requests to true requests will automatically be retried
108
+ # once.
109
+ #
110
+ # Only do this when you know that retrying a POST is safe for your
111
+ # application and will not create duplicate resources.
112
+ #
113
+ # The recommended way to handle non-idempotent requests is the following:
114
+ #
115
+ # require 'net/http/persistent'
116
+ #
117
+ # uri = URI 'http://example.com/awesome/web/service'
118
+ # post_uri = uri + 'create'
119
+ #
120
+ # http = Net::HTTP::Persistent.new 'my_app_name'
121
+ #
122
+ # post = Net::HTTP::Post.new post_uri.path
123
+ # # ... fill in POST request
124
+ #
125
+ # begin
126
+ # response = http.request post_uri, post
127
+ # rescue Net::HTTP::Persistent::Error
128
+ #
129
+ # # POST failed, make a new request to verify the server did not process
130
+ # # the request
131
+ # exists_uri = uri + '...'
132
+ # response = http.get exists_uri
133
+ #
134
+ # # Retry if it failed
135
+ # retry if response.code == '404'
136
+ # end
137
+ #
138
+ # The method of determining if the resource was created or not is unique to
139
+ # the particular service you are using. Of course, you will want to add
140
+ # protection from infinite looping.
40
141
 
41
142
  class Net::HTTP::Persistent
42
143
 
43
144
  ##
44
- # The version of Net::HTTP::Persistent use are using
145
+ # The beginning of Time
146
+
147
+ EPOCH = Time.at 0 # :nodoc:
148
+
149
+ ##
150
+ # The version of Net::HTTP::Persistent you are using
45
151
 
46
- VERSION = '2.1'
152
+ VERSION = '2.2'
47
153
 
48
154
  ##
49
155
  # Error class for errors raised by Net::HTTP::Persistent. Various
@@ -93,6 +199,12 @@ class Net::HTTP::Persistent
93
199
 
94
200
  attr_reader :http_versions
95
201
 
202
+ ##
203
+ # Maximum time an unused connection can remain idle before being
204
+ # automatically closed.
205
+
206
+ attr_accessor :idle_timeout
207
+
96
208
  ##
97
209
  # The value sent in the Keep-Alive header. Defaults to 30. Not needed for
98
210
  # HTTP/1.1 servers.
@@ -155,6 +267,11 @@ class Net::HTTP::Persistent
155
267
 
156
268
  attr_reader :socket_options
157
269
 
270
+ ##
271
+ # Where this instance's last-use times live in the thread local variables
272
+
273
+ attr_reader :timeout_key # :nodoc:
274
+
158
275
  ##
159
276
  # SSL verification callback. Used when ca_file is set.
160
277
 
@@ -195,10 +312,10 @@ class Net::HTTP::Persistent
195
312
  # +proxy+ may be set to a URI::HTTP or :ENV to pick up proxy options from
196
313
  # the environment. See proxy_from_env for details.
197
314
  #
198
- # In order to use a URI for the proxy you'll need to do some extra work
199
- # beyond URI.parse:
315
+ # In order to use a URI for the proxy you may need to do some extra work
316
+ # beyond URI parsing if the proxy requires a password:
200
317
  #
201
- # proxy = URI.parse 'http://proxy.example'
318
+ # proxy = URI 'http://proxy.example'
202
319
  # proxy.user = 'AzureDiamond'
203
320
  # proxy.password = 'hunter2'
204
321
 
@@ -229,15 +346,16 @@ class Net::HTTP::Persistent
229
346
  @keep_alive = 30
230
347
  @open_timeout = nil
231
348
  @read_timeout = nil
349
+ @idle_timeout = 5
232
350
  @socket_options = []
233
351
 
234
352
  @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
235
353
  Socket.const_defined? :TCP_NODELAY
236
354
 
237
- key = ['net_http_persistent', name, 'connections'].compact.join '_'
238
- @connection_key = key.intern
239
- key = ['net_http_persistent', name, 'requests'].compact.join '_'
240
- @request_key = key.intern
355
+ key = ['net_http_persistent', name].compact
356
+ @connection_key = [key, 'connections'].join('_').intern
357
+ @request_key = [key, 'requests'].join('_').intern
358
+ @timeout_key = [key, 'timeouts'].join('_').intern
241
359
 
242
360
  @certificate = nil
243
361
  @ca_file = nil
@@ -256,6 +374,7 @@ class Net::HTTP::Persistent
256
374
  def connection_for uri
257
375
  Thread.current[@connection_key] ||= {}
258
376
  Thread.current[@request_key] ||= Hash.new 0
377
+ Thread.current[@timeout_key] ||= Hash.new EPOCH
259
378
 
260
379
  connections = Thread.current[@connection_key]
261
380
 
@@ -267,10 +386,15 @@ class Net::HTTP::Persistent
267
386
  net_http_args.concat @proxy_args
268
387
  end
269
388
 
389
+ connection = connections[connection_id]
390
+
270
391
  unless connection = connections[connection_id] then
271
392
  connections[connection_id] = http_class.new(*net_http_args)
272
393
  connection = connections[connection_id]
273
394
  ssl connection if uri.scheme.downcase == 'https'
395
+ else
396
+ last_used = Thread.current[@timeout_key][connection.object_id]
397
+ reset connection unless last_used > max_age
274
398
  end
275
399
 
276
400
  unless connection.started? then
@@ -353,6 +477,13 @@ class Net::HTTP::Persistent
353
477
  retry_change_requests or idempotent?(req)
354
478
  end
355
479
 
480
+ ##
481
+ # If a connection hasn't been used since max_age it will be reset and reused
482
+
483
+ def max_age
484
+ Time.now - @idle_timeout
485
+ end
486
+
356
487
  ##
357
488
  # Adds "http://" to the String +uri+ if it is missing.
358
489
 
@@ -394,7 +525,7 @@ class Net::HTTP::Persistent
394
525
 
395
526
  return nil if env_proxy.nil? or env_proxy.empty?
396
527
 
397
- uri = URI.parse normalize_uri env_proxy
528
+ uri = URI normalize_uri env_proxy
398
529
 
399
530
  unless uri.user or uri.password then
400
531
  uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
@@ -409,6 +540,7 @@ class Net::HTTP::Persistent
409
540
 
410
541
  def reset connection
411
542
  Thread.current[@request_key].delete connection.object_id
543
+ Thread.current[@timeout_key].delete connection.object_id
412
544
 
413
545
  finish connection
414
546
 
@@ -450,7 +582,6 @@ class Net::HTTP::Persistent
450
582
  begin
451
583
  Thread.current[@request_key][connection_id] += 1
452
584
  response = connection.request req, &block
453
-
454
585
  rescue Net::HTTPBadResponse => e
455
586
  message = error_message connection
456
587
 
@@ -478,6 +609,8 @@ class Net::HTTP::Persistent
478
609
 
479
610
  retried = true
480
611
  retry
612
+ ensure
613
+ Thread.current[@timeout_key][connection_id] = Time.now
481
614
  end
482
615
 
483
616
  @http_versions["#{uri.host}:#{uri.port}"] ||= response.http_version
@@ -120,6 +120,10 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
120
120
  Thread.current[@http.request_key] ||= {}
121
121
  end
122
122
 
123
+ def touts
124
+ Thread.current[@http.timeout_key] ||= Hash.new Net::HTTP::Persistent::EPOCH
125
+ end
126
+
123
127
  def test_initialize
124
128
  assert_nil @http.proxy_uri
125
129
  end
@@ -186,9 +190,9 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
186
190
  cached = basic_connection
187
191
  cached.start
188
192
  if Socket.const_defined? :TCP_NODELAY then
189
- def (cached.instance_variable_get(:@socket).io).setsockopt(*a)
190
- raise IOError, 'closed stream'
191
- end
193
+ io = Object.new
194
+ def io.setsockopt(*a) raise IOError, 'closed stream' end
195
+ cached.instance_variable_set :@socket, Net::BufferedIO.new(io)
192
196
  end
193
197
  conns['example.com:80'] = cached
194
198
 
@@ -200,7 +204,9 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
200
204
  assert_same c, conns['example.com:80']
201
205
 
202
206
  socket = c.instance_variable_get :@socket
203
- assert_nil socket.io.instance_variable_get(:@setsockopt)
207
+
208
+ refute_includes socket.io.instance_variables, :@setsockopt
209
+ refute_includes socket.io.instance_variables, '@setsockopt'
204
210
  end
205
211
 
206
212
  def test_connection_for_debug_output
@@ -325,6 +331,21 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
325
331
  assert c.use_ssl?
326
332
  end
327
333
 
334
+ def test_connection_for_timeout
335
+ cached = basic_connection
336
+ cached.start
337
+ reqs[cached.object_id] = 10
338
+ touts[cached.object_id] = Time.now - 6
339
+ conns['example.com:80'] = cached
340
+
341
+ c = @http.connection_for @uri
342
+
343
+ assert c.started?
344
+ assert_nil reqs[c.object_id]
345
+
346
+ assert_same cached, c
347
+ end
348
+
328
349
  def test_error_message
329
350
  c = basic_connection
330
351
  reqs[c.object_id] = 5
@@ -387,6 +408,10 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
387
408
  assert_equal 'https://example', @http.normalize_uri('https://example')
388
409
  end
389
410
 
411
+ def test_max_age
412
+ assert_in_delta Time.now - 5, @http.max_age
413
+ end
414
+
390
415
  def test_pipeline
391
416
  skip 'net-http-pipeline not installed' unless defined?(Net::HTTP::Pipeline)
392
417
 
@@ -449,7 +474,8 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
449
474
  def test_reset
450
475
  c = basic_connection
451
476
  c.start
452
- reqs[c.object_id] = 5
477
+ touts[c.object_id] = Time.now
478
+ reqs[c.object_id] = 5
453
479
 
454
480
  @http.reset c
455
481
 
@@ -457,10 +483,12 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
457
483
  assert c.finished?
458
484
  assert c.reset?
459
485
  assert_nil reqs[c.object_id]
486
+ assert_equal Net::HTTP::Persistent::EPOCH, touts[c.object_id]
460
487
  end
461
488
 
462
489
  def test_reset_io_error
463
490
  c = basic_connection
491
+ touts[c.object_id] = Time.now
464
492
  reqs[c.object_id] = 5
465
493
 
466
494
  @http.reset c
@@ -471,6 +499,7 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
471
499
 
472
500
  def test_reset_host_down
473
501
  c = basic_connection
502
+ touts[c.object_id] = Time.now
474
503
  def c.start; raise Errno::EHOSTDOWN end
475
504
  reqs[c.object_id] = 5
476
505
 
@@ -483,6 +512,7 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
483
512
 
484
513
  def test_reset_refused
485
514
  c = basic_connection
515
+ touts[c.object_id] = Time.now
486
516
  def c.start; raise Errno::ECONNREFUSED end
487
517
  reqs[c.object_id] = 5
488
518
 
@@ -521,6 +551,8 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
521
551
  assert_equal '30', req['keep-alive']
522
552
  assert_match %r%test ua%, req['user-agent']
523
553
 
554
+ assert_in_delta Time.now, touts[c.object_id]
555
+
524
556
  assert_equal 1, reqs[c.object_id]
525
557
  end
526
558
 
@@ -608,6 +640,8 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
608
640
 
609
641
  def test_request_invalid_retry
610
642
  c = basic_connection
643
+ touts[c.object_id] = Time.now
644
+
611
645
  def c.request(*a)
612
646
  if defined? @called then
613
647
  Net::HTTPResponse.allocate
@@ -637,6 +671,7 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
637
671
 
638
672
  def test_request_reset_retry
639
673
  c = basic_connection
674
+ touts[c.object_id] = Time.now
640
675
  def c.request(*a)
641
676
  if defined? @called then
642
677
  Net::HTTPResponse.allocate
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-http-persistent
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
- - 1
9
- version: "2.1"
8
+ - 2
9
+ version: "2.2"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Eric Hodel
@@ -35,7 +35,7 @@ cert_chain:
35
35
  x52qPcexcYZR7w==
36
36
  -----END CERTIFICATE-----
37
37
 
38
- date: 2011-09-20 00:00:00 Z
38
+ date: 2011-10-25 00:00:00 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: minitest
@@ -45,11 +45,11 @@ dependencies:
45
45
  requirements:
46
46
  - - ~>
47
47
  - !ruby/object:Gem::Version
48
- hash: 5
48
+ hash: 15
49
49
  segments:
50
50
  - 2
51
- - 3
52
- version: "2.3"
51
+ - 6
52
+ version: "2.6"
53
53
  type: :development
54
54
  version_requirements: *id001
55
55
  - !ruby/object:Gem::Dependency
@@ -76,7 +76,7 @@ description: |-
76
76
  round-trip and causes TCP congestion avoidance negotiation to start over.
77
77
 
78
78
  Net::HTTP supports persistent connections with some API methods but does not
79
- handle reconnection gracefully. net-http-persistent supports reconnection
79
+ handle reconnection gracefully. Net::HTTP::Persistent supports reconnection
80
80
  and retry according to RFC 2616.
81
81
  email:
82
82
  - drbrain@segment7.net
metadata.gz.sig CHANGED
Binary file