net-http-persistent 1.0.1 → 1.1

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
@@ -1,9 +1,23 @@
1
+ === 1.1
2
+
3
+ * Minor Enhancements
4
+ * Proxy support, see Net::HTTP::Persistent::new,
5
+ Net::HTTP::Persistent#proxy_from_env
6
+ * Added +name+ parameter to Net::HTTP::Persistent::new for separation of
7
+ connection pools.
8
+ * Added Net::HTTP::Persistent#shutdown so you can clean up after yourself
9
+ * Net::HTTP::Persistent now suppresses "peer certificate won't be verified
10
+ in this SSL session" warning.
11
+
12
+ * Bug Fixes
13
+ * Net::HTTP::Persistent retries requests in accordance with RFC 2616.
14
+
1
15
  === 1.0.1 / 2010-05-05
2
16
 
3
17
  * Minor Enhancements
4
18
  * Added #debug_output
5
19
  * Now uses Hoe minitest plugin
6
- * Bug fixes
20
+ * Bug Fixes
7
21
  * Tests pass on 1.9
8
22
 
9
23
  === 1.0.0 / 2010-05-04
@@ -12,6 +12,13 @@ require 'uri'
12
12
  # Multiple Net::HTTP::Persistent objects will share the same set of
13
13
  # connections.
14
14
  #
15
+ # For each thread you start a new connection will be created. A
16
+ # Net::HTTP::Persistent connection will not be shared across threads.
17
+ #
18
+ # You can shut down the HTTP connections when done by calling #shutdown. You
19
+ # should name your Net::HTTP::Persistent object if you intend to call this
20
+ # method.
21
+ #
15
22
  # Example:
16
23
  #
17
24
  # uri = URI.parse 'http://example.com/awesome/web/service'
@@ -29,7 +36,7 @@ class Net::HTTP::Persistent
29
36
  ##
30
37
  # The version of Net::HTTP::Persistent use are using
31
38
 
32
- VERSION = '1.0.1'
39
+ VERSION = '1.1'
33
40
 
34
41
  ##
35
42
  # Error class for errors raised by Net::HTTP::Persistent. Various
@@ -49,6 +56,11 @@ class Net::HTTP::Persistent
49
56
 
50
57
  attr_accessor :ca_file
51
58
 
59
+ ##
60
+ # Where this instance's connections live in the thread local variables
61
+
62
+ attr_reader :connection_key # :nodoc:
63
+
52
64
  ##
53
65
  # Sends debug_output to this IO via Net::HTTP#set_debug_output.
54
66
  #
@@ -63,33 +75,97 @@ class Net::HTTP::Persistent
63
75
  attr_reader :headers
64
76
 
65
77
  ##
66
- # The value sent in the Keep-Alive header. Defaults to 30 seconds
78
+ # The value sent in the Keep-Alive header. Defaults to 30. Not needed for
79
+ # HTTP/1.1 servers.
80
+ #
81
+ # This may not work correctly for HTTP/1.0 servers
82
+ #
83
+ # This method may be removed in a future version as RFC 2616 does not
84
+ # require this header.
67
85
 
68
86
  attr_accessor :keep_alive
69
87
 
88
+ ##
89
+ # A name for this connection. Allows you to keep your connections apart
90
+ # from everybody else's.
91
+
92
+ attr_reader :name
93
+
70
94
  ##
71
95
  # This client's SSL private key
72
96
 
73
97
  attr_accessor :private_key
74
98
 
99
+ ##
100
+ # The URL through which requests will be proxied
101
+
102
+ attr_reader :proxy_uri
103
+
104
+ ##
105
+ # Where this instance's request counts live in the thread local variables
106
+
107
+ attr_reader :request_key # :nodoc:
108
+
75
109
  ##
76
110
  # SSL verification callback. Used when ca_file is set.
77
111
 
78
112
  attr_accessor :verify_callback
79
113
 
80
114
  ##
81
- # HTTPS verify mode. Set to OpenSSL::SSL::VERIFY_NONE to ignore certificate
82
- # problems.
115
+ # HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_NONE which ignores
116
+ # certificate problems.
83
117
  #
84
118
  # You can use +verify_mode+ to override any default values.
85
119
 
86
120
  attr_accessor :verify_mode
87
121
 
88
- def initialize # :nodoc:
122
+ ##
123
+ # Creates a new Net::HTTP::Persistent.
124
+ #
125
+ # Set +name+ to keep your connections apart from everybody else's. Not
126
+ # required currently, but highly recommended. Your library name should be
127
+ # good enough. This parameter will be required in a future version.
128
+ #
129
+ # +proxy+ may be set to a URI::HTTP or :ENV to pick up proxy options from
130
+ # the environment. See proxy_from_env for details.
131
+ #
132
+ # In order to use a URI for the proxy you'll need to do some extra work
133
+ # beyond URI.parse:
134
+ #
135
+ # proxy = URI.parse 'http://proxy.example'
136
+ # proxy.user = 'AzureDiamond'
137
+ # proxy.password = 'hunter2'
138
+
139
+ def initialize name = nil, proxy = nil
140
+ @name = name
141
+
142
+ @proxy_uri = case proxy
143
+ when :ENV then proxy_from_env
144
+ when URI::HTTP then proxy
145
+ when nil then # ignore
146
+ else raise ArgumentError, 'proxy must be :ENV or a URI::HTTP'
147
+ end
148
+
149
+ if @proxy_uri then
150
+ @proxy_args = [
151
+ @proxy_uri.host,
152
+ @proxy_uri.port,
153
+ @proxy_uri.user,
154
+ @proxy_uri.password,
155
+ ]
156
+
157
+ @proxy_connection_id = [nil, *@proxy_args].join ':'
158
+ end
159
+
89
160
  @debug_output = nil
90
161
  @headers = {}
91
162
  @keep_alive = 30
92
163
 
164
+ key = ['net_http_persistent', name, 'connections'].compact.join '_'
165
+ @connection_key = key.intern
166
+ key = ['net_http_persistent', name, 'requests'].compact.join '_'
167
+ @request_key = key.intern
168
+
93
169
  @certificate = nil
94
170
  @ca_file = nil
95
171
  @private_key = nil
@@ -101,12 +177,18 @@ class Net::HTTP::Persistent
101
177
  # Creates a new connection for +uri+
102
178
 
103
179
  def connection_for uri
104
- Thread.current[:net_http_persistent_connections] ||= {}
105
- connections = Thread.current[:net_http_persistent_connections]
180
+ Thread.current[@connection_key] ||= {}
181
+ connections = Thread.current[@connection_key]
106
182
 
107
- connection_id = [uri.host, uri.port].join ':'
183
+ net_http_args = [uri.host, uri.port]
184
+ connection_id = net_http_args.join ':'
108
185
 
109
- connections[connection_id] ||= Net::HTTP.new uri.host, uri.port
186
+ if @proxy_uri then
187
+ connection_id << @proxy_connection_id
188
+ net_http_args.concat @proxy_args
189
+ end
190
+
191
+ connections[connection_id] ||= Net::HTTP.new(*net_http_args)
110
192
  connection = connections[connection_id]
111
193
 
112
194
  unless connection.started? then
@@ -128,16 +210,68 @@ class Net::HTTP::Persistent
128
210
 
129
211
  def error_message connection
130
212
  requests =
131
- Thread.current[:net_http_persistent_requests][connection.object_id]
213
+ Thread.current[@request_key][connection.object_id]
132
214
 
133
215
  "after #{requests} requests on #{connection.object_id}"
134
216
  end
135
217
 
218
+ ##
219
+ # URI::escape wrapper
220
+
221
+ def escape str
222
+ URI.escape str if str
223
+ end
224
+
225
+ ##
226
+ # Is +req+ idempotent according to RFC 2616?
227
+
228
+ def idempotent? req
229
+ case req
230
+ when Net::HTTP::Delete, Net::HTTP::Get, Net::HTTP::Head,
231
+ Net::HTTP::Options, Net::HTTP::Put, Net::HTTP::Trace then
232
+ true
233
+ end
234
+ end
235
+
236
+ ##
237
+ # Adds "http://" to the String +uri+ if it is missing.
238
+
239
+ def normalize_uri uri
240
+ (uri =~ /^https?:/) ? uri : "http://#{uri}"
241
+ end
242
+
243
+ ##
244
+ # Creates a URI for an HTTP proxy server from ENV variables.
245
+ #
246
+ # If +HTTP_PROXY+ is set a proxy will be returned.
247
+ #
248
+ # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the URI is given the
249
+ # indicated user and password unless HTTP_PROXY contains either of these in
250
+ # the URI.
251
+ #
252
+ # For Windows users lowercase ENV variables are preferred over uppercase ENV
253
+ # variables.
254
+
255
+ def proxy_from_env
256
+ env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
257
+
258
+ return nil if env_proxy.nil? or env_proxy.empty?
259
+
260
+ uri = URI.parse normalize_uri env_proxy
261
+
262
+ unless uri.user or uri.password then
263
+ uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
264
+ uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
265
+ end
266
+
267
+ uri
268
+ end
269
+
136
270
  ##
137
271
  # Finishes then restarts the Net::HTTP +connection+
138
272
 
139
273
  def reset connection
140
- Thread.current[:net_http_persistent_requests].delete connection.object_id
274
+ Thread.current[@request_key].delete connection.object_id
141
275
 
142
276
  begin
143
277
  connection.finish
@@ -156,9 +290,12 @@ class Net::HTTP::Persistent
156
290
  # against +uri+.
157
291
  #
158
292
  # +req+ must be a Net::HTTPRequest subclass (see Net::HTTP for a list).
293
+ #
294
+ # If there is an error and the request is idempontent according to RFC 2616
295
+ # it will be retried automatically.
159
296
 
160
297
  def request uri, req = nil
161
- Thread.current[:net_http_persistent_requests] ||= Hash.new 0
298
+ Thread.current[@request_key] ||= Hash.new 0
162
299
  retried = false
163
300
  bad_response = false
164
301
 
@@ -175,7 +312,7 @@ class Net::HTTP::Persistent
175
312
  connection_id = connection.object_id
176
313
 
177
314
  begin
178
- count = Thread.current[:net_http_persistent_requests][connection_id] += 1
315
+ count = Thread.current[@request_key][connection_id] += 1
179
316
  response = connection.request req
180
317
 
181
318
  rescue Net::HTTPBadResponse => e
@@ -183,7 +320,8 @@ class Net::HTTP::Persistent
183
320
 
184
321
  reset connection
185
322
 
186
- raise Error, "too many bad responses #{message}" if bad_response
323
+ raise Error, "too many bad responses #{message}" if
324
+ bad_response or not idempotent? req
187
325
 
188
326
  bad_response = true
189
327
  retry
@@ -194,7 +332,8 @@ class Net::HTTP::Persistent
194
332
 
195
333
  reset connection
196
334
 
197
- raise Error, "too many connection resets #{due_to} #{message}" if retried
335
+ raise Error, "too many connection resets #{due_to} #{message}" if
336
+ retried or not idempotent? req
198
337
 
199
338
  retried = true
200
339
  retry
@@ -203,6 +342,21 @@ class Net::HTTP::Persistent
203
342
  response
204
343
  end
205
344
 
345
+ ##
346
+ # Shuts down all connections in this thread.
347
+ #
348
+ # If you've used Net::HTTP::Persistent across multiple threads you must call
349
+ # this in each thread.
350
+
351
+ def shutdown
352
+ Thread.current[@connection_key].each do |_, connection|
353
+ connection.finish
354
+ end
355
+
356
+ Thread.current[@connection_key] = nil
357
+ Thread.current[@request_key] = nil
358
+ end
359
+
206
360
  ##
207
361
  # Enables SSL on +connection+
208
362
 
@@ -210,6 +364,9 @@ class Net::HTTP::Persistent
210
364
  require 'net/https'
211
365
  connection.use_ssl = true
212
366
 
367
+ # suppress warning but allow override
368
+ connection.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @verify_mode
369
+
213
370
  if @ca_file then
214
371
  connection.ca_file = @ca_file
215
372
  connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
@@ -3,22 +3,35 @@ require 'net/http/persistent'
3
3
  require 'openssl'
4
4
  require 'stringio'
5
5
 
6
+ class Net::HTTP
7
+ def connect
8
+ end
9
+ end
10
+
6
11
  class TestNetHttpPersistent < MiniTest::Unit::TestCase
7
12
 
8
13
  def setup
9
14
  @http = Net::HTTP::Persistent.new
10
15
  @uri = URI.parse 'http://example.com/path'
16
+
17
+ ENV.delete 'http_proxy'
18
+ ENV.delete 'HTTP_PROXY'
19
+ ENV.delete 'http_proxy_user'
20
+ ENV.delete 'HTTP_PROXY_USER'
21
+ ENV.delete 'http_proxy_pass'
22
+ ENV.delete 'HTTP_PROXY_PASS'
11
23
  end
12
24
 
13
25
  def teardown
14
- Thread.current[:net_http_persistent_connections] = nil
15
- Thread.current[:net_http_persistent_requests] = nil
26
+ Thread.current.keys.each do |key|
27
+ Thread.current[key] = nil
28
+ end
16
29
  end
17
30
 
18
31
  def connection
19
32
  c = Object.new
20
33
  # Net::HTTP
21
- def c.finish; @finish = true end
34
+ def c.finish; @finished = true end
22
35
  def c.request(req) @req = req; :response end
23
36
  def c.reset; @reset = true end
24
37
  def c.start; end
@@ -27,22 +40,48 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
27
40
  def c.req() @req; end
28
41
  def c.reset?; @reset end
29
42
  def c.started?; true end
43
+ def c.finished?; @finished end
30
44
  conns["#{@uri.host}:#{@uri.port}"] = c
31
45
  c
32
46
  end
33
47
 
34
48
  def conns
35
- Thread.current[:net_http_persistent_connections] ||= {}
49
+ Thread.current[@http.connection_key] ||= {}
36
50
  end
37
51
 
38
52
  def reqs
39
- Thread.current[:net_http_persistent_requests] ||= {}
53
+ Thread.current[@http.request_key] ||= {}
54
+ end
55
+
56
+ def test_initialize
57
+ assert_nil @http.proxy_uri
58
+ end
59
+
60
+ def test_initialize_name
61
+ http = Net::HTTP::Persistent.new 'name'
62
+ assert_equal 'name', http.name
63
+ end
64
+
65
+ def test_initialize_env
66
+ ENV['HTTP_PROXY'] = 'proxy.example'
67
+ http = Net::HTTP::Persistent.new nil, :ENV
68
+
69
+ assert_equal URI.parse('http://proxy.example'), http.proxy_uri
70
+ end
71
+
72
+ def test_initialize_uri
73
+ proxy_uri = URI.parse 'http://proxy.example'
74
+
75
+ http = Net::HTTP::Persistent.new nil, proxy_uri
76
+
77
+ assert_equal proxy_uri, http.proxy_uri
40
78
  end
41
79
 
42
80
  def test_connection_for
43
81
  c = @http.connection_for @uri
44
82
 
45
83
  assert c.started?
84
+ refute c.proxy?
46
85
 
47
86
  assert_includes conns.keys, 'example.com:80'
48
87
  assert_same c, conns['example.com:80']
@@ -73,6 +112,34 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
73
112
  assert_same c, conns['example.com:80']
74
113
  end
75
114
 
115
+ def test_connection_for_name
116
+ http = Net::HTTP::Persistent.new 'name'
117
+ uri = URI.parse 'http://example/'
118
+
119
+ c = http.connection_for uri
120
+
121
+ assert c.started?
122
+
123
+ refute_includes conns.keys, 'example:80'
124
+ end
125
+
126
+ def test_connection_for_proxy
127
+ uri = URI.parse 'http://proxy.example'
128
+ uri.user = 'johndoe'
129
+ uri.password = 'muffins'
130
+
131
+ http = Net::HTTP::Persistent.new nil, uri
132
+
133
+ c = http.connection_for @uri
134
+
135
+ assert c.started?
136
+ assert c.proxy?
137
+
138
+ assert_includes conns.keys,
139
+ 'example.com:80:proxy.example:80:johndoe:muffins'
140
+ assert_same c, conns['example.com:80:proxy.example:80:johndoe:muffins']
141
+ end
142
+
76
143
  def test_connection_for_refused
77
144
  cached = Object.new
78
145
  def cached.address; 'example.com' end
@@ -95,6 +162,69 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
95
162
  assert_equal "after 5 requests on #{c.object_id}", @http.error_message(c)
96
163
  end
97
164
 
165
+ def test_escape
166
+ assert_nil @http.escape nil
167
+
168
+ assert_equal '%20', @http.escape(' ')
169
+ end
170
+
171
+ def test_idempotent_eh
172
+ assert @http.idempotent? Net::HTTP::Delete.new '/'
173
+ assert @http.idempotent? Net::HTTP::Get.new '/'
174
+ assert @http.idempotent? Net::HTTP::Head.new '/'
175
+ assert @http.idempotent? Net::HTTP::Options.new '/'
176
+ assert @http.idempotent? Net::HTTP::Put.new '/'
177
+ assert @http.idempotent? Net::HTTP::Trace.new '/'
178
+
179
+ refute @http.idempotent? Net::HTTP::Post.new '/'
180
+ end
181
+
182
+ def test_normalize_uri
183
+ assert_equal 'http://example', @http.normalize_uri('example')
184
+ assert_equal 'http://example', @http.normalize_uri('http://example')
185
+ assert_equal 'https://example', @http.normalize_uri('https://example')
186
+ end
187
+
188
+ def test_proxy_from_env
189
+ ENV['HTTP_PROXY'] = 'proxy.example'
190
+ ENV['HTTP_PROXY_USER'] = 'johndoe'
191
+ ENV['HTTP_PROXY_PASS'] = 'muffins'
192
+
193
+ uri = @http.proxy_from_env
194
+
195
+ expected = URI.parse 'http://proxy.example'
196
+ expected.user = 'johndoe'
197
+ expected.password = 'muffins'
198
+
199
+ assert_equal expected, uri
200
+ end
201
+
202
+ def test_proxy_from_env_lower
203
+ ENV['http_proxy'] = 'proxy.example'
204
+ ENV['http_proxy_user'] = 'johndoe'
205
+ ENV['http_proxy_pass'] = 'muffins'
206
+
207
+ uri = @http.proxy_from_env
208
+
209
+ expected = URI.parse 'http://proxy.example'
210
+ expected.user = 'johndoe'
211
+ expected.password = 'muffins'
212
+
213
+ assert_equal expected, uri
214
+ end
215
+
216
+ def test_proxy_from_env_nil
217
+ uri = @http.proxy_from_env
218
+
219
+ assert_nil uri
220
+
221
+ ENV['HTTP_PROXY'] = ''
222
+
223
+ uri = @http.proxy_from_env
224
+
225
+ assert_nil uri
226
+ end
227
+
98
228
  def test_reset
99
229
  c = Object.new
100
230
  def c.finish; @finished = true end
@@ -184,6 +314,25 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
184
314
  assert_match %r%too many bad responses%, e.message
185
315
  end
186
316
 
317
+ def test_request_bad_response_unsafe
318
+ c = connection
319
+ def c.request(*a)
320
+ if instance_variable_defined? :@request then
321
+ raise 'POST must not be retried'
322
+ else
323
+ @request = true
324
+ raise Net::HTTPBadResponse
325
+ end
326
+ end
327
+
328
+ e = assert_raises Net::HTTP::Persistent::Error do
329
+ @http.request @uri, Net::HTTP::Post.new(@uri.path)
330
+ end
331
+
332
+ assert_equal 0, reqs[c.object_id]
333
+ assert_match %r%too many bad responses%, e.message
334
+ end
335
+
187
336
  def test_request_reset
188
337
  c = connection
189
338
  def c.request(*a) raise Errno::ECONNRESET end
@@ -196,6 +345,25 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
196
345
  assert_match %r%too many connection resets%, e.message
197
346
  end
198
347
 
348
+ def test_request_reset_unsafe
349
+ c = connection
350
+ def c.request(*a)
351
+ if instance_variable_defined? :@request then
352
+ raise 'POST must not be retried'
353
+ else
354
+ @request = true
355
+ raise Errno::ECONNRESET
356
+ end
357
+ end
358
+
359
+ e = assert_raises Net::HTTP::Persistent::Error do
360
+ @http.request @uri, Net::HTTP::Post.new(@uri.path)
361
+ end
362
+
363
+ assert_equal 0, reqs[c.object_id]
364
+ assert_match %r%too many connection resets%, e.message
365
+ end
366
+
199
367
  def test_request_post
200
368
  c = connection
201
369
 
@@ -209,6 +377,24 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
209
377
  assert_same post, req
210
378
  end
211
379
 
380
+ def test_shutdown
381
+ c = connection
382
+ cs = conns
383
+ rs = reqs
384
+
385
+ orig = @http
386
+ @http = Net::HTTP::Persistent.new 'name'
387
+ c2 = connection
388
+
389
+ orig.shutdown
390
+
391
+ assert c.finished?
392
+ refute c2.finished?
393
+
394
+ refute_same cs, conns
395
+ refute_same rs, reqs
396
+ end
397
+
212
398
  def test_ssl
213
399
  @http.verify_callback = :callback
214
400
  c = Net::HTTP.new 'localhost', 80
@@ -216,7 +402,7 @@ class TestNetHttpPersistent < MiniTest::Unit::TestCase
216
402
  @http.ssl c
217
403
 
218
404
  assert c.use_ssl?
219
- assert_nil c.verify_mode
405
+ assert_equal OpenSSL::SSL::VERIFY_NONE, c.verify_mode
220
406
  assert_nil c.verify_callback
221
407
  end
222
408
 
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-http-persistent
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 13
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 0
9
8
  - 1
10
- version: 1.0.1
9
+ version: "1.1"
11
10
  platform: ruby
12
11
  authors:
13
12
  - Eric Hodel
@@ -36,7 +35,7 @@ cert_chain:
36
35
  x52qPcexcYZR7w==
37
36
  -----END CERTIFICATE-----
38
37
 
39
- date: 2010-05-05 00:00:00 -07:00
38
+ date: 2010-05-18 00:00:00 -07:00
40
39
  default_executable:
41
40
  dependencies:
42
41
  - !ruby/object:Gem::Dependency
metadata.gz.sig CHANGED
Binary file