net-http-persistent 2.1 → 2.2

Sign up to get free protection for your applications and to get access to all the features.
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