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 +0 -0
- data/History.txt +9 -0
- data/README.txt +1 -1
- data/lib/net/http/persistent.rb +149 -16
- data/test/test_net_http_persistent.rb +40 -5
- metadata +8 -8
- metadata.gz.sig +0 -0
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.
|
16
|
+
handle reconnection gracefully. Net::HTTP::Persistent supports reconnection
|
17
17
|
and retry according to RFC 2616.
|
18
18
|
|
19
19
|
== FEATURES/PROBLEMS:
|
data/lib/net/http/persistent.rb
CHANGED
@@ -28,22 +28,128 @@ end
|
|
28
28
|
#
|
29
29
|
# Example:
|
30
30
|
#
|
31
|
-
#
|
32
|
-
# http = Net::HTTP::Persistent.new
|
33
|
-
# stuff = http.request uri # performs a GET
|
31
|
+
# require 'net/http/persistent'
|
34
32
|
#
|
35
|
-
#
|
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
|
-
#
|
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
|
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.
|
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
|
199
|
-
# beyond URI
|
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
|
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
|
238
|
-
@connection_key = key.intern
|
239
|
-
|
240
|
-
@
|
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
|
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
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
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
|
-
|
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:
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: "2.
|
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-
|
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:
|
48
|
+
hash: 15
|
49
49
|
segments:
|
50
50
|
- 2
|
51
|
-
-
|
52
|
-
version: "2.
|
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.
|
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
|