net-http-persistent 1.0.1 → 1.1

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