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 +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
|