softwaregravy-net-http-persistent 2.6
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/.autotest +7 -0
- data/.gemtest +0 -0
- data/History.txt +257 -0
- data/Manifest.txt +11 -0
- data/README.rdoc +82 -0
- data/Rakefile +20 -0
- data/lib/net/http/faster.rb +27 -0
- data/lib/net/http/persistent.rb +998 -0
- data/lib/net/http/persistent/ssl_reuse.rb +129 -0
- data/test/test_net_http_persistent.rb +1302 -0
- data/test/test_net_http_persistent_ssl_reuse.rb +101 -0
- metadata +103 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
##
|
2
|
+
# This Net::HTTP subclass adds SSL session reuse and Server Name Indication
|
3
|
+
# (SNI) RFC 3546.
|
4
|
+
#
|
5
|
+
# DO NOT DEPEND UPON THIS CLASS
|
6
|
+
#
|
7
|
+
# This class is an implementation detail and is subject to change or removal
|
8
|
+
# at any time.
|
9
|
+
|
10
|
+
class Net::HTTP::Persistent::SSLReuse < Net::HTTP
|
11
|
+
|
12
|
+
@is_proxy_class = false
|
13
|
+
@proxy_addr = nil
|
14
|
+
@proxy_port = nil
|
15
|
+
@proxy_user = nil
|
16
|
+
@proxy_pass = nil
|
17
|
+
|
18
|
+
def initialize address, port = nil # :nodoc:
|
19
|
+
super
|
20
|
+
|
21
|
+
@ssl_session = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# From ruby trunk r33086 including http://redmine.ruby-lang.org/issues/5341
|
26
|
+
|
27
|
+
def connect # :nodoc:
|
28
|
+
D "opening connection to #{conn_address()}..."
|
29
|
+
s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
|
30
|
+
D "opened"
|
31
|
+
if use_ssl?
|
32
|
+
ssl_parameters = Hash.new
|
33
|
+
iv_list = instance_variables
|
34
|
+
SSL_ATTRIBUTES.each do |name|
|
35
|
+
ivname = "@#{name}".intern
|
36
|
+
if iv_list.include?(ivname) and
|
37
|
+
value = instance_variable_get(ivname)
|
38
|
+
ssl_parameters[name] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
unless @ssl_context then
|
42
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
43
|
+
@ssl_context.set_params(ssl_parameters)
|
44
|
+
end
|
45
|
+
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
|
46
|
+
s.sync_close = true
|
47
|
+
end
|
48
|
+
@socket = Net::BufferedIO.new(s)
|
49
|
+
@socket.read_timeout = @read_timeout
|
50
|
+
@socket.continue_timeout = @continue_timeout if
|
51
|
+
@socket.respond_to? :continue_timeout
|
52
|
+
@socket.debug_output = @debug_output
|
53
|
+
if use_ssl?
|
54
|
+
begin
|
55
|
+
if proxy?
|
56
|
+
@socket.writeline sprintf('CONNECT %s:%s HTTP/%s',
|
57
|
+
@address, @port, HTTPVersion)
|
58
|
+
@socket.writeline "Host: #{@address}:#{@port}"
|
59
|
+
if proxy_user
|
60
|
+
credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
|
61
|
+
credential.delete!("\r\n")
|
62
|
+
@socket.writeline "Proxy-Authorization: Basic #{credential}"
|
63
|
+
end
|
64
|
+
@socket.writeline ''
|
65
|
+
Net::HTTPResponse.read_new(@socket).value
|
66
|
+
end
|
67
|
+
s.session = @ssl_session if @ssl_session
|
68
|
+
# Server Name Indication (SNI) RFC 3546
|
69
|
+
s.hostname = @address if s.respond_to? :hostname=
|
70
|
+
timeout(@open_timeout) { s.connect }
|
71
|
+
if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
|
72
|
+
s.post_connection_check(@address)
|
73
|
+
end
|
74
|
+
@ssl_session = s.session
|
75
|
+
rescue => exception
|
76
|
+
D "Conn close because of connect error #{exception}"
|
77
|
+
@socket.close if @socket and not @socket.closed?
|
78
|
+
raise exception
|
79
|
+
end
|
80
|
+
end
|
81
|
+
on_connect
|
82
|
+
end if RUBY_VERSION > '1.9'
|
83
|
+
|
84
|
+
##
|
85
|
+
# From ruby_1_8_7 branch r29865 including a modified
|
86
|
+
# http://redmine.ruby-lang.org/issues/5341
|
87
|
+
|
88
|
+
def connect # :nodoc:
|
89
|
+
D "opening connection to #{conn_address()}..."
|
90
|
+
s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
|
91
|
+
D "opened"
|
92
|
+
if use_ssl?
|
93
|
+
unless @ssl_context.verify_mode
|
94
|
+
warn "warning: peer certificate won't be verified in this SSL session"
|
95
|
+
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
96
|
+
end
|
97
|
+
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
|
98
|
+
s.sync_close = true
|
99
|
+
end
|
100
|
+
@socket = Net::BufferedIO.new(s)
|
101
|
+
@socket.read_timeout = @read_timeout
|
102
|
+
@socket.debug_output = @debug_output
|
103
|
+
if use_ssl?
|
104
|
+
if proxy?
|
105
|
+
@socket.writeline sprintf('CONNECT %s:%s HTTP/%s',
|
106
|
+
@address, @port, HTTPVersion)
|
107
|
+
@socket.writeline "Host: #{@address}:#{@port}"
|
108
|
+
if proxy_user
|
109
|
+
credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
|
110
|
+
credential.delete!("\r\n")
|
111
|
+
@socket.writeline "Proxy-Authorization: Basic #{credential}"
|
112
|
+
end
|
113
|
+
@socket.writeline ''
|
114
|
+
Net::HTTPResponse.read_new(@socket).value
|
115
|
+
end
|
116
|
+
s.session = @ssl_session if @ssl_session
|
117
|
+
s.connect
|
118
|
+
if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
|
119
|
+
s.post_connection_check(@address)
|
120
|
+
end
|
121
|
+
@ssl_session = s.session
|
122
|
+
end
|
123
|
+
on_connect
|
124
|
+
end if RUBY_VERSION < '1.9'
|
125
|
+
|
126
|
+
private :connect
|
127
|
+
|
128
|
+
end
|
129
|
+
|
@@ -0,0 +1,1302 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'net/http/persistent'
|
4
|
+
require 'openssl'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
class Net::HTTP::Persistent::SSLReuse
|
8
|
+
alias orig_connect connect
|
9
|
+
|
10
|
+
def test_connect
|
11
|
+
unless use_ssl? then
|
12
|
+
io = Object.new
|
13
|
+
def io.setsockopt(*a) @setsockopts ||= []; @setsockopts << a end
|
14
|
+
|
15
|
+
@socket = Net::BufferedIO.new io
|
16
|
+
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
io = open '/dev/null'
|
21
|
+
def io.setsockopt(*a) @setsockopts ||= []; @setsockopts << a end
|
22
|
+
|
23
|
+
@ssl_context ||= OpenSSL::SSL::SSLContext.new
|
24
|
+
|
25
|
+
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER unless
|
26
|
+
@ssl_context.verify_mode
|
27
|
+
|
28
|
+
s = OpenSSL::SSL::SSLSocket.new io, @ssl_context
|
29
|
+
|
30
|
+
@socket = Net::BufferedIO.new s
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.use_connect which
|
34
|
+
self.send :remove_method, :connect
|
35
|
+
self.send :alias_method, :connect, which
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class TestNetHttpPersistent < MiniTest::Unit::TestCase
|
40
|
+
|
41
|
+
def setup
|
42
|
+
@http = Net::HTTP::Persistent.new
|
43
|
+
@uri = URI.parse 'http://example.com/path'
|
44
|
+
|
45
|
+
ENV.delete 'http_proxy'
|
46
|
+
ENV.delete 'HTTP_PROXY'
|
47
|
+
ENV.delete 'http_proxy_user'
|
48
|
+
ENV.delete 'HTTP_PROXY_USER'
|
49
|
+
ENV.delete 'http_proxy_pass'
|
50
|
+
ENV.delete 'HTTP_PROXY_PASS'
|
51
|
+
|
52
|
+
Net::HTTP::Persistent::SSLReuse.use_connect :test_connect
|
53
|
+
end
|
54
|
+
|
55
|
+
def teardown
|
56
|
+
Thread.current.keys.each do |key|
|
57
|
+
Thread.current[key] = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
Net::HTTP::Persistent::SSLReuse.use_connect :orig_connect
|
61
|
+
end
|
62
|
+
|
63
|
+
class BasicConnection
|
64
|
+
attr_accessor :started, :finished, :address, :port
|
65
|
+
attr_reader :req
|
66
|
+
def initialize
|
67
|
+
@started, @finished = 0, 0
|
68
|
+
@address, @port = 'example.com', 80
|
69
|
+
end
|
70
|
+
def finish
|
71
|
+
@finished += 1
|
72
|
+
@socket = nil
|
73
|
+
end
|
74
|
+
def finished?
|
75
|
+
@finished >= 1
|
76
|
+
end
|
77
|
+
def pipeline requests, &block
|
78
|
+
requests.map { |r| r.path }
|
79
|
+
end
|
80
|
+
def reset?
|
81
|
+
@started == @finished + 1
|
82
|
+
end
|
83
|
+
def start
|
84
|
+
@started += 1
|
85
|
+
io = Object.new
|
86
|
+
def io.setsockopt(*a) @setsockopts ||= []; @setsockopts << a end
|
87
|
+
@socket = Net::BufferedIO.new io
|
88
|
+
end
|
89
|
+
def started?
|
90
|
+
@started >= 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def basic_connection
|
95
|
+
raise "#{@uri} is not HTTP" unless @uri.scheme.downcase == 'http'
|
96
|
+
|
97
|
+
c = BasicConnection.new
|
98
|
+
conns[0]["#{@uri.host}:#{@uri.port}"] = c
|
99
|
+
c
|
100
|
+
end
|
101
|
+
|
102
|
+
def connection
|
103
|
+
c = basic_connection
|
104
|
+
touts[c.object_id] = Time.now
|
105
|
+
|
106
|
+
def c.request(req)
|
107
|
+
@req = req
|
108
|
+
r = Net::HTTPResponse.allocate
|
109
|
+
r.instance_variable_set :@header, {}
|
110
|
+
def r.http_version() '1.1' end
|
111
|
+
def r.read_body() :read_body end
|
112
|
+
yield r if block_given?
|
113
|
+
r
|
114
|
+
end
|
115
|
+
|
116
|
+
c
|
117
|
+
end
|
118
|
+
|
119
|
+
def conns
|
120
|
+
Thread.current[@http.generation_key] ||= Hash.new { |h,k| h[k] = {} }
|
121
|
+
end
|
122
|
+
|
123
|
+
def reqs
|
124
|
+
Thread.current[@http.request_key] ||= Hash.new 0
|
125
|
+
end
|
126
|
+
|
127
|
+
def ssl_conns
|
128
|
+
Thread.current[@http.ssl_generation_key] ||= Hash.new { |h,k| h[k] = {} }
|
129
|
+
end
|
130
|
+
|
131
|
+
def ssl_connection generation = 0
|
132
|
+
raise "#{@uri} is not HTTPS" unless @uri.scheme.downcase == 'https'
|
133
|
+
c = BasicConnection.new
|
134
|
+
ssl_conns[generation]["#{@uri.host}:#{@uri.port}"] = c
|
135
|
+
c
|
136
|
+
end
|
137
|
+
|
138
|
+
def touts
|
139
|
+
Thread.current[@http.timeout_key] ||= Hash.new Net::HTTP::Persistent::EPOCH
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_initialize
|
143
|
+
assert_nil @http.proxy_uri
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_initialize_name
|
147
|
+
http = Net::HTTP::Persistent.new 'name'
|
148
|
+
assert_equal 'name', http.name
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_initialize_proxy
|
152
|
+
proxy_uri = URI.parse 'http://proxy.example'
|
153
|
+
|
154
|
+
http = Net::HTTP::Persistent.new nil, proxy_uri
|
155
|
+
|
156
|
+
assert_equal proxy_uri, http.proxy_uri
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_ca_file_equals
|
160
|
+
@http.ca_file = :ca_file
|
161
|
+
|
162
|
+
assert_equal :ca_file, @http.ca_file
|
163
|
+
assert_equal 1, @http.ssl_generation
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_cert_store_equals
|
167
|
+
@http.cert_store = :cert_store
|
168
|
+
|
169
|
+
assert_equal :cert_store, @http.cert_store
|
170
|
+
assert_equal 1, @http.ssl_generation
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_certificate_equals
|
174
|
+
@http.certificate = :cert
|
175
|
+
|
176
|
+
assert_equal :cert, @http.certificate
|
177
|
+
assert_equal 1, @http.ssl_generation
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_connection_for
|
181
|
+
@http.open_timeout = 123
|
182
|
+
@http.read_timeout = 321
|
183
|
+
c = @http.connection_for @uri
|
184
|
+
|
185
|
+
assert_kind_of Net::HTTP::Persistent::SSLReuse, c
|
186
|
+
|
187
|
+
assert c.started?
|
188
|
+
refute c.proxy?
|
189
|
+
|
190
|
+
assert_equal 123, c.open_timeout
|
191
|
+
assert_equal 321, c.read_timeout
|
192
|
+
|
193
|
+
assert_includes conns[0].keys, 'example.com:80'
|
194
|
+
assert_same c, conns[0]['example.com:80']
|
195
|
+
|
196
|
+
socket = c.instance_variable_get :@socket
|
197
|
+
expected = if Socket.const_defined? :TCP_NODELAY then
|
198
|
+
[[Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1]]
|
199
|
+
else
|
200
|
+
[]
|
201
|
+
end
|
202
|
+
|
203
|
+
assert_equal expected, socket.io.instance_variable_get(:@setsockopts)
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_connection_for_cached
|
207
|
+
cached = basic_connection
|
208
|
+
cached.start
|
209
|
+
conns[0]['example.com:80'] = cached
|
210
|
+
|
211
|
+
c = @http.connection_for @uri
|
212
|
+
|
213
|
+
assert c.started?
|
214
|
+
|
215
|
+
assert_same cached, c
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_connection_for_closed
|
219
|
+
cached = basic_connection
|
220
|
+
cached.start
|
221
|
+
if Socket.const_defined? :TCP_NODELAY then
|
222
|
+
io = Object.new
|
223
|
+
def io.setsockopt(*a) raise IOError, 'closed stream' end
|
224
|
+
cached.instance_variable_set :@socket, Net::BufferedIO.new(io)
|
225
|
+
end
|
226
|
+
conns['example.com:80'] = cached
|
227
|
+
|
228
|
+
c = @http.connection_for @uri
|
229
|
+
|
230
|
+
assert c.started?
|
231
|
+
|
232
|
+
assert_includes conns.keys, 'example.com:80'
|
233
|
+
assert_same c, conns[0]['example.com:80']
|
234
|
+
|
235
|
+
socket = c.instance_variable_get :@socket
|
236
|
+
|
237
|
+
refute_includes socket.io.instance_variables, :@setsockopt
|
238
|
+
refute_includes socket.io.instance_variables, '@setsockopt'
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_connection_for_debug_output
|
242
|
+
io = StringIO.new
|
243
|
+
@http.debug_output = io
|
244
|
+
|
245
|
+
c = @http.connection_for @uri
|
246
|
+
|
247
|
+
assert c.started?
|
248
|
+
assert_equal io, c.instance_variable_get(:@debug_output)
|
249
|
+
|
250
|
+
assert_includes conns[0].keys, 'example.com:80'
|
251
|
+
assert_same c, conns[0]['example.com:80']
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_connection_for_cached_expire_always
|
255
|
+
cached = basic_connection
|
256
|
+
cached.start
|
257
|
+
conns[0]['example.com:80'] = cached
|
258
|
+
reqs[cached.object_id] = 10
|
259
|
+
touts[cached.object_id] = Time.now # last used right now
|
260
|
+
|
261
|
+
@http.idle_timeout = 0
|
262
|
+
|
263
|
+
c = @http.connection_for @uri
|
264
|
+
|
265
|
+
assert c.started?
|
266
|
+
|
267
|
+
assert_same cached, c
|
268
|
+
|
269
|
+
assert_equal 0, reqs[cached.object_id],
|
270
|
+
'connection reset due to timeout'
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_connection_for_cached_expire_never
|
274
|
+
cached = basic_connection
|
275
|
+
cached.start
|
276
|
+
conns[0]['example.com:80'] = cached
|
277
|
+
reqs[cached.object_id] = 10
|
278
|
+
touts[cached.object_id] = Time.now # last used right now
|
279
|
+
|
280
|
+
@http.idle_timeout = nil
|
281
|
+
|
282
|
+
c = @http.connection_for @uri
|
283
|
+
|
284
|
+
assert c.started?
|
285
|
+
|
286
|
+
assert_same cached, c
|
287
|
+
|
288
|
+
assert_equal 10, reqs[cached.object_id],
|
289
|
+
'connection reset despite no timeout'
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_connection_for_cached_expired
|
293
|
+
cached = basic_connection
|
294
|
+
cached.start
|
295
|
+
conns[0]['example.com:80'] = cached
|
296
|
+
reqs[cached.object_id] = 10
|
297
|
+
touts[cached.object_id] = Time.now - 3600
|
298
|
+
|
299
|
+
c = @http.connection_for @uri
|
300
|
+
|
301
|
+
assert c.started?
|
302
|
+
|
303
|
+
assert_same cached, c
|
304
|
+
assert_equal 0, reqs[cached.object_id],
|
305
|
+
'connection not reset due to timeout'
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_connection_for_finished_ssl
|
309
|
+
uri = URI.parse 'https://example.com/path'
|
310
|
+
c = @http.connection_for uri
|
311
|
+
|
312
|
+
assert c.started?
|
313
|
+
assert c.use_ssl?
|
314
|
+
|
315
|
+
@http.finish c
|
316
|
+
|
317
|
+
refute c.started?
|
318
|
+
|
319
|
+
c2 = @http.connection_for uri
|
320
|
+
|
321
|
+
assert c2.started?
|
322
|
+
end
|
323
|
+
|
324
|
+
def test_connection_for_host_down
|
325
|
+
cached = basic_connection
|
326
|
+
def cached.start; raise Errno::EHOSTDOWN end
|
327
|
+
def cached.started?; false end
|
328
|
+
conns[0]['example.com:80'] = cached
|
329
|
+
|
330
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
331
|
+
@http.connection_for @uri
|
332
|
+
end
|
333
|
+
|
334
|
+
assert_match %r%host down%, e.message
|
335
|
+
end
|
336
|
+
|
337
|
+
def test_connection_for_http_class_with_fakeweb
|
338
|
+
Object.send :const_set, :FakeWeb, nil
|
339
|
+
c = @http.connection_for @uri
|
340
|
+
assert_instance_of Net::HTTP, c
|
341
|
+
ensure
|
342
|
+
if Object.const_defined?(:FakeWeb) then
|
343
|
+
Object.send :remove_const, :FakeWeb
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def test_connection_for_http_class_with_webmock
|
348
|
+
Object.send :const_set, :WebMock, nil
|
349
|
+
c = @http.connection_for @uri
|
350
|
+
assert_instance_of Net::HTTP, c
|
351
|
+
ensure
|
352
|
+
if Object.const_defined?(:WebMock) then
|
353
|
+
Object.send :remove_const, :WebMock
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def test_connection_for_name
|
358
|
+
http = Net::HTTP::Persistent.new 'name'
|
359
|
+
uri = URI.parse 'http://example/'
|
360
|
+
|
361
|
+
c = http.connection_for uri
|
362
|
+
|
363
|
+
assert c.started?
|
364
|
+
|
365
|
+
refute_includes conns.keys, 'example:80'
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_connection_for_no_ssl_reuse
|
369
|
+
@http.reuse_ssl_sessions = false
|
370
|
+
@http.open_timeout = 123
|
371
|
+
@http.read_timeout = 321
|
372
|
+
c = @http.connection_for @uri
|
373
|
+
|
374
|
+
assert_instance_of Net::HTTP, c
|
375
|
+
end
|
376
|
+
|
377
|
+
def test_connection_for_proxy
|
378
|
+
uri = URI.parse 'http://proxy.example'
|
379
|
+
uri.user = 'johndoe'
|
380
|
+
uri.password = 'muffins'
|
381
|
+
|
382
|
+
http = Net::HTTP::Persistent.new nil, uri
|
383
|
+
|
384
|
+
c = http.connection_for @uri
|
385
|
+
|
386
|
+
assert c.started?
|
387
|
+
assert c.proxy?
|
388
|
+
|
389
|
+
assert_includes conns[1].keys,
|
390
|
+
'example.com:80:proxy.example:80:johndoe:muffins'
|
391
|
+
assert_same c, conns[1]['example.com:80:proxy.example:80:johndoe:muffins']
|
392
|
+
end
|
393
|
+
|
394
|
+
def test_connection_for_refused
|
395
|
+
cached = basic_connection
|
396
|
+
def cached.start; raise Errno::ECONNREFUSED end
|
397
|
+
def cached.started?; false end
|
398
|
+
conns[0]['example.com:80'] = cached
|
399
|
+
|
400
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
401
|
+
@http.connection_for @uri
|
402
|
+
end
|
403
|
+
|
404
|
+
assert_match %r%connection refused%, e.message
|
405
|
+
end
|
406
|
+
|
407
|
+
def test_connection_for_socket_options
|
408
|
+
@http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
|
409
|
+
c = @http.connection_for @uri
|
410
|
+
|
411
|
+
socket = c.instance_variable_get :@socket
|
412
|
+
|
413
|
+
expected = []
|
414
|
+
expected << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
|
415
|
+
Socket.const_defined? :TCP_NODELAY
|
416
|
+
expected << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
|
417
|
+
|
418
|
+
assert_equal expected, socket.io.instance_variable_get(:@setsockopts)
|
419
|
+
end
|
420
|
+
|
421
|
+
def test_connection_for_ssl
|
422
|
+
uri = URI.parse 'https://example.com/path'
|
423
|
+
c = @http.connection_for uri
|
424
|
+
|
425
|
+
assert c.started?
|
426
|
+
assert c.use_ssl?
|
427
|
+
end
|
428
|
+
|
429
|
+
def test_connection_for_ssl_cached
|
430
|
+
@uri = URI.parse 'https://example.com/path'
|
431
|
+
|
432
|
+
cached = ssl_connection 0
|
433
|
+
|
434
|
+
c = @http.connection_for @uri
|
435
|
+
|
436
|
+
assert_same cached, c
|
437
|
+
end
|
438
|
+
|
439
|
+
def test_connection_for_ssl_cached_reconnect
|
440
|
+
@uri = URI.parse 'https://example.com/path'
|
441
|
+
|
442
|
+
cached = ssl_connection
|
443
|
+
|
444
|
+
@http.reconnect_ssl
|
445
|
+
|
446
|
+
c = @http.connection_for @uri
|
447
|
+
|
448
|
+
refute_same cached, c
|
449
|
+
end
|
450
|
+
|
451
|
+
def test_connection_for_ssl_case
|
452
|
+
uri = URI.parse 'HTTPS://example.com/path'
|
453
|
+
c = @http.connection_for uri
|
454
|
+
|
455
|
+
assert c.started?
|
456
|
+
assert c.use_ssl?
|
457
|
+
end
|
458
|
+
|
459
|
+
def test_connection_for_timeout
|
460
|
+
cached = basic_connection
|
461
|
+
cached.start
|
462
|
+
reqs[cached.object_id] = 10
|
463
|
+
touts[cached.object_id] = Time.now - 6
|
464
|
+
conns[0]['example.com:80'] = cached
|
465
|
+
|
466
|
+
c = @http.connection_for @uri
|
467
|
+
|
468
|
+
assert c.started?
|
469
|
+
assert_equal 0, reqs[c.object_id]
|
470
|
+
|
471
|
+
assert_same cached, c
|
472
|
+
end
|
473
|
+
|
474
|
+
def test_error_message
|
475
|
+
c = basic_connection
|
476
|
+
touts[c.object_id] = Time.now - 1
|
477
|
+
reqs[c.object_id] = 5
|
478
|
+
|
479
|
+
message = @http.error_message(c)
|
480
|
+
assert_match %r%after 4 requests on #{c.object_id}%, message
|
481
|
+
assert_match %r%, last used [\d.]+ seconds ago%, message
|
482
|
+
end
|
483
|
+
|
484
|
+
def test_escape
|
485
|
+
assert_nil @http.escape nil
|
486
|
+
|
487
|
+
assert_equal '+%3F', @http.escape(' ?')
|
488
|
+
end
|
489
|
+
|
490
|
+
def test_expired_eh
|
491
|
+
c = basic_connection
|
492
|
+
touts[c.object_id] = Time.now - 11
|
493
|
+
|
494
|
+
@http.idle_timeout = 0
|
495
|
+
assert @http.expired? c
|
496
|
+
|
497
|
+
@http.idle_timeout = 10
|
498
|
+
assert @http.expired? c
|
499
|
+
|
500
|
+
@http.idle_timeout = 11
|
501
|
+
assert @http.expired? c
|
502
|
+
|
503
|
+
@http.idle_timeout = 12
|
504
|
+
refute @http.expired? c
|
505
|
+
|
506
|
+
@http.idle_timeout = nil
|
507
|
+
refute @http.expired? c
|
508
|
+
end
|
509
|
+
|
510
|
+
def test_finish
|
511
|
+
c = basic_connection
|
512
|
+
reqs[c.object_id] = 5
|
513
|
+
|
514
|
+
@http.finish c
|
515
|
+
|
516
|
+
refute c.started?
|
517
|
+
assert c.finished?
|
518
|
+
assert_equal 0, reqs[c.object_id]
|
519
|
+
end
|
520
|
+
|
521
|
+
def test_finish_io_error
|
522
|
+
c = basic_connection
|
523
|
+
def c.finish; @finished += 1; raise IOError end
|
524
|
+
reqs[c.object_id] = 5
|
525
|
+
|
526
|
+
@http.finish c
|
527
|
+
|
528
|
+
refute c.started?
|
529
|
+
assert c.finished?
|
530
|
+
end
|
531
|
+
|
532
|
+
def test_http_version
|
533
|
+
assert_nil @http.http_version @uri
|
534
|
+
|
535
|
+
connection
|
536
|
+
|
537
|
+
@http.request @uri
|
538
|
+
|
539
|
+
assert_equal '1.1', @http.http_version(@uri)
|
540
|
+
end
|
541
|
+
|
542
|
+
def test_idempotent_eh
|
543
|
+
assert @http.idempotent? Net::HTTP::Delete.new '/'
|
544
|
+
assert @http.idempotent? Net::HTTP::Get.new '/'
|
545
|
+
assert @http.idempotent? Net::HTTP::Head.new '/'
|
546
|
+
assert @http.idempotent? Net::HTTP::Options.new '/'
|
547
|
+
assert @http.idempotent? Net::HTTP::Put.new '/'
|
548
|
+
assert @http.idempotent? Net::HTTP::Trace.new '/'
|
549
|
+
|
550
|
+
refute @http.idempotent? Net::HTTP::Post.new '/'
|
551
|
+
end
|
552
|
+
|
553
|
+
def test_max_age
|
554
|
+
assert_in_delta Time.now - 5, @http.max_age
|
555
|
+
|
556
|
+
@http.idle_timeout = nil
|
557
|
+
|
558
|
+
assert_in_delta Time.now + 1, @http.max_age
|
559
|
+
end
|
560
|
+
|
561
|
+
def test_normalize_uri
|
562
|
+
assert_equal 'http://example', @http.normalize_uri('example')
|
563
|
+
assert_equal 'http://example', @http.normalize_uri('http://example')
|
564
|
+
assert_equal 'https://example', @http.normalize_uri('https://example')
|
565
|
+
end
|
566
|
+
|
567
|
+
def test_override_haeders
|
568
|
+
assert_empty @http.override_headers
|
569
|
+
|
570
|
+
@http.override_headers['User-Agent'] = 'MyCustomAgent'
|
571
|
+
|
572
|
+
expected = { 'User-Agent' => 'MyCustomAgent' }
|
573
|
+
|
574
|
+
assert_equal expected, @http.override_headers
|
575
|
+
end
|
576
|
+
|
577
|
+
def test_pipeline
|
578
|
+
skip 'net-http-pipeline not installed' unless defined?(Net::HTTP::Pipeline)
|
579
|
+
|
580
|
+
cached = basic_connection
|
581
|
+
cached.start
|
582
|
+
conns['example.com:80'] = cached
|
583
|
+
|
584
|
+
requests = [
|
585
|
+
Net::HTTP::Get.new((@uri + '1').request_uri),
|
586
|
+
Net::HTTP::Get.new((@uri + '2').request_uri),
|
587
|
+
]
|
588
|
+
|
589
|
+
responses = @http.pipeline @uri, requests
|
590
|
+
|
591
|
+
assert_equal 2, responses.length
|
592
|
+
assert_equal '/1', responses.first
|
593
|
+
assert_equal '/2', responses.last
|
594
|
+
end
|
595
|
+
|
596
|
+
def test_private_key_equals
|
597
|
+
@http.private_key = :private_key
|
598
|
+
|
599
|
+
assert_equal :private_key, @http.private_key
|
600
|
+
assert_equal 1, @http.ssl_generation
|
601
|
+
end
|
602
|
+
|
603
|
+
def test_proxy_equals_env
|
604
|
+
ENV['HTTP_PROXY'] = 'proxy.example'
|
605
|
+
|
606
|
+
@http.proxy = :ENV
|
607
|
+
|
608
|
+
assert_equal URI.parse('http://proxy.example'), @http.proxy_uri
|
609
|
+
|
610
|
+
assert_equal 1, @http.generation, 'generation'
|
611
|
+
assert_equal 1, @http.ssl_generation, 'ssl_generation'
|
612
|
+
end
|
613
|
+
|
614
|
+
def test_proxy_equals_uri
|
615
|
+
proxy_uri = URI.parse 'http://proxy.example'
|
616
|
+
|
617
|
+
@http.proxy = proxy_uri
|
618
|
+
|
619
|
+
assert_equal proxy_uri, @http.proxy_uri
|
620
|
+
end
|
621
|
+
|
622
|
+
def test_proxy_from_env
|
623
|
+
ENV['HTTP_PROXY'] = 'proxy.example'
|
624
|
+
ENV['HTTP_PROXY_USER'] = 'johndoe'
|
625
|
+
ENV['HTTP_PROXY_PASS'] = 'muffins'
|
626
|
+
|
627
|
+
uri = @http.proxy_from_env
|
628
|
+
|
629
|
+
expected = URI.parse 'http://proxy.example'
|
630
|
+
expected.user = 'johndoe'
|
631
|
+
expected.password = 'muffins'
|
632
|
+
|
633
|
+
assert_equal expected, uri
|
634
|
+
end
|
635
|
+
|
636
|
+
def test_proxy_from_env_lower
|
637
|
+
ENV['http_proxy'] = 'proxy.example'
|
638
|
+
ENV['http_proxy_user'] = 'johndoe'
|
639
|
+
ENV['http_proxy_pass'] = 'muffins'
|
640
|
+
|
641
|
+
uri = @http.proxy_from_env
|
642
|
+
|
643
|
+
expected = URI.parse 'http://proxy.example'
|
644
|
+
expected.user = 'johndoe'
|
645
|
+
expected.password = 'muffins'
|
646
|
+
|
647
|
+
assert_equal expected, uri
|
648
|
+
end
|
649
|
+
|
650
|
+
def test_proxy_from_env_nil
|
651
|
+
uri = @http.proxy_from_env
|
652
|
+
|
653
|
+
assert_nil uri
|
654
|
+
|
655
|
+
ENV['HTTP_PROXY'] = ''
|
656
|
+
|
657
|
+
uri = @http.proxy_from_env
|
658
|
+
|
659
|
+
assert_nil uri
|
660
|
+
end
|
661
|
+
|
662
|
+
def test_reconnect
|
663
|
+
result = @http.reconnect
|
664
|
+
|
665
|
+
assert_equal 1, result
|
666
|
+
end
|
667
|
+
|
668
|
+
def test_reconnect_ssl
|
669
|
+
result = @http.reconnect_ssl
|
670
|
+
|
671
|
+
assert_equal 1, result
|
672
|
+
end
|
673
|
+
|
674
|
+
def test_request
|
675
|
+
@http.override_headers['user-agent'] = 'test ua'
|
676
|
+
@http.headers['accept'] = 'text/*'
|
677
|
+
c = connection
|
678
|
+
|
679
|
+
res = @http.request @uri
|
680
|
+
req = c.req
|
681
|
+
|
682
|
+
assert_kind_of Net::HTTPResponse, res
|
683
|
+
|
684
|
+
assert_kind_of Net::HTTP::Get, req
|
685
|
+
assert_equal '/path', req.path
|
686
|
+
|
687
|
+
assert_equal 'test ua', req['user-agent']
|
688
|
+
assert_match %r%text/\*%, req['accept']
|
689
|
+
|
690
|
+
assert_equal 'keep-alive', req['connection']
|
691
|
+
assert_equal '30', req['keep-alive']
|
692
|
+
|
693
|
+
assert_in_delta Time.now, touts[c.object_id]
|
694
|
+
|
695
|
+
assert_equal 1, reqs[c.object_id]
|
696
|
+
end
|
697
|
+
|
698
|
+
def test_request_bad_response
|
699
|
+
c = basic_connection
|
700
|
+
def c.request(*a) raise Net::HTTPBadResponse end
|
701
|
+
|
702
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
703
|
+
@http.request @uri
|
704
|
+
end
|
705
|
+
|
706
|
+
assert_equal 0, reqs[c.object_id]
|
707
|
+
assert_match %r%too many bad responses%, e.message
|
708
|
+
end
|
709
|
+
|
710
|
+
def test_request_bad_response_retry
|
711
|
+
c = basic_connection
|
712
|
+
def c.request(*a)
|
713
|
+
if defined? @called then
|
714
|
+
r = Net::HTTPResponse.allocate
|
715
|
+
r.instance_variable_set :@header, {}
|
716
|
+
def r.http_version() '1.1' end
|
717
|
+
r
|
718
|
+
else
|
719
|
+
@called = true
|
720
|
+
raise Net::HTTPBadResponse
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
@http.request @uri
|
725
|
+
|
726
|
+
assert c.finished?
|
727
|
+
end
|
728
|
+
|
729
|
+
def test_request_bad_response_unsafe
|
730
|
+
c = basic_connection
|
731
|
+
def c.request(*a)
|
732
|
+
if instance_variable_defined? :@request then
|
733
|
+
raise 'POST must not be retried'
|
734
|
+
else
|
735
|
+
@request = true
|
736
|
+
raise Net::HTTPBadResponse
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
741
|
+
@http.request @uri, Net::HTTP::Post.new(@uri.path)
|
742
|
+
end
|
743
|
+
|
744
|
+
assert_equal 0, reqs[c.object_id]
|
745
|
+
assert_match %r%too many bad responses%, e.message
|
746
|
+
end
|
747
|
+
|
748
|
+
def test_request_block
|
749
|
+
@http.headers['user-agent'] = 'test ua'
|
750
|
+
c = connection
|
751
|
+
body = nil
|
752
|
+
|
753
|
+
res = @http.request @uri do |r|
|
754
|
+
body = r.read_body
|
755
|
+
end
|
756
|
+
|
757
|
+
req = c.req
|
758
|
+
|
759
|
+
assert_kind_of Net::HTTPResponse, res
|
760
|
+
refute_nil body
|
761
|
+
|
762
|
+
assert_kind_of Net::HTTP::Get, req
|
763
|
+
assert_equal '/path', req.path
|
764
|
+
assert_equal 'keep-alive', req['connection']
|
765
|
+
assert_equal '30', req['keep-alive']
|
766
|
+
assert_match %r%test ua%, req['user-agent']
|
767
|
+
|
768
|
+
assert_equal 1, reqs[c.object_id]
|
769
|
+
end
|
770
|
+
|
771
|
+
def test_request_close_1_0
|
772
|
+
c = connection
|
773
|
+
|
774
|
+
class << c
|
775
|
+
remove_method :request
|
776
|
+
end
|
777
|
+
|
778
|
+
def c.request req
|
779
|
+
@req = req
|
780
|
+
r = Net::HTTPResponse.allocate
|
781
|
+
r.instance_variable_set :@header, {}
|
782
|
+
def r.http_version() '1.0' end
|
783
|
+
def r.read_body() :read_body end
|
784
|
+
yield r if block_given?
|
785
|
+
r
|
786
|
+
end
|
787
|
+
|
788
|
+
request = Net::HTTP::Get.new @uri.request_uri
|
789
|
+
|
790
|
+
res = @http.request @uri, request
|
791
|
+
req = c.req
|
792
|
+
|
793
|
+
assert_kind_of Net::HTTPResponse, res
|
794
|
+
|
795
|
+
assert_kind_of Net::HTTP::Get, req
|
796
|
+
assert_equal '/path', req.path
|
797
|
+
assert_equal 'keep-alive', req['connection']
|
798
|
+
assert_equal '30', req['keep-alive']
|
799
|
+
|
800
|
+
assert c.finished?
|
801
|
+
end
|
802
|
+
|
803
|
+
def test_request_connection_close_request
|
804
|
+
c = connection
|
805
|
+
|
806
|
+
request = Net::HTTP::Get.new @uri.request_uri
|
807
|
+
request['connection'] = 'close'
|
808
|
+
|
809
|
+
res = @http.request @uri, request
|
810
|
+
req = c.req
|
811
|
+
|
812
|
+
assert_kind_of Net::HTTPResponse, res
|
813
|
+
|
814
|
+
assert_kind_of Net::HTTP::Get, req
|
815
|
+
assert_equal '/path', req.path
|
816
|
+
assert_equal 'close', req['connection']
|
817
|
+
assert_equal nil, req['keep-alive']
|
818
|
+
|
819
|
+
assert c.finished?
|
820
|
+
end
|
821
|
+
|
822
|
+
def test_request_connection_close_response
|
823
|
+
c = connection
|
824
|
+
|
825
|
+
class << c
|
826
|
+
remove_method :request
|
827
|
+
end
|
828
|
+
|
829
|
+
def c.request req
|
830
|
+
@req = req
|
831
|
+
r = Net::HTTPResponse.allocate
|
832
|
+
r.instance_variable_set :@header, {}
|
833
|
+
r['connection'] = 'close'
|
834
|
+
def r.http_version() '1.1' end
|
835
|
+
def r.read_body() :read_body end
|
836
|
+
yield r if block_given?
|
837
|
+
r
|
838
|
+
end
|
839
|
+
|
840
|
+
request = Net::HTTP::Get.new @uri.request_uri
|
841
|
+
|
842
|
+
res = @http.request @uri, request
|
843
|
+
req = c.req
|
844
|
+
|
845
|
+
assert_kind_of Net::HTTPResponse, res
|
846
|
+
|
847
|
+
assert_kind_of Net::HTTP::Get, req
|
848
|
+
assert_equal '/path', req.path
|
849
|
+
assert_equal 'keep-alive', req['connection']
|
850
|
+
assert_equal '30', req['keep-alive']
|
851
|
+
|
852
|
+
assert c.finished?
|
853
|
+
end
|
854
|
+
|
855
|
+
def test_request_invalid
|
856
|
+
c = basic_connection
|
857
|
+
def c.request(*a) raise Errno::EINVAL, "write" end
|
858
|
+
|
859
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
860
|
+
@http.request @uri
|
861
|
+
end
|
862
|
+
|
863
|
+
assert_equal 0, reqs[c.object_id]
|
864
|
+
assert_match %r%too many connection resets%, e.message
|
865
|
+
end
|
866
|
+
|
867
|
+
def test_request_invalid_retry
|
868
|
+
c = basic_connection
|
869
|
+
touts[c.object_id] = Time.now
|
870
|
+
|
871
|
+
def c.request(*a)
|
872
|
+
if defined? @called then
|
873
|
+
r = Net::HTTPResponse.allocate
|
874
|
+
r.instance_variable_set :@header, {}
|
875
|
+
def r.http_version() '1.1' end
|
876
|
+
r
|
877
|
+
else
|
878
|
+
@called = true
|
879
|
+
raise Errno::EINVAL, "write"
|
880
|
+
end
|
881
|
+
end
|
882
|
+
|
883
|
+
@http.request @uri
|
884
|
+
|
885
|
+
assert c.reset?
|
886
|
+
assert c.finished?
|
887
|
+
end
|
888
|
+
|
889
|
+
def test_request_post
|
890
|
+
c = connection
|
891
|
+
|
892
|
+
post = Net::HTTP::Post.new @uri.path
|
893
|
+
|
894
|
+
@http.request @uri, post
|
895
|
+
req = c.req
|
896
|
+
|
897
|
+
assert_same post, req
|
898
|
+
end
|
899
|
+
|
900
|
+
def test_request_reset
|
901
|
+
c = basic_connection
|
902
|
+
def c.request(*a) raise Errno::ECONNRESET end
|
903
|
+
|
904
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
905
|
+
@http.request @uri
|
906
|
+
end
|
907
|
+
|
908
|
+
assert_equal 0, reqs[c.object_id]
|
909
|
+
assert_match %r%too many connection resets%, e.message
|
910
|
+
end
|
911
|
+
|
912
|
+
def test_request_reset_retry
|
913
|
+
c = basic_connection
|
914
|
+
touts[c.object_id] = Time.now
|
915
|
+
def c.request(*a)
|
916
|
+
if defined? @called then
|
917
|
+
r = Net::HTTPResponse.allocate
|
918
|
+
r.instance_variable_set :@header, {}
|
919
|
+
def r.http_version() '1.1' end
|
920
|
+
r
|
921
|
+
else
|
922
|
+
@called = true
|
923
|
+
raise Errno::ECONNRESET
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
@http.request @uri
|
928
|
+
|
929
|
+
assert c.reset?
|
930
|
+
assert c.finished?
|
931
|
+
end
|
932
|
+
|
933
|
+
def test_request_reset_unsafe
|
934
|
+
c = basic_connection
|
935
|
+
def c.request(*a)
|
936
|
+
if instance_variable_defined? :@request then
|
937
|
+
raise 'POST must not be retried'
|
938
|
+
else
|
939
|
+
@request = true
|
940
|
+
raise Errno::ECONNRESET
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
945
|
+
@http.request @uri, Net::HTTP::Post.new(@uri.path)
|
946
|
+
end
|
947
|
+
|
948
|
+
assert_equal 0, reqs[c.object_id]
|
949
|
+
assert_match %r%too many connection resets%, e.message
|
950
|
+
end
|
951
|
+
|
952
|
+
def test_request_ssl_error
|
953
|
+
uri = URI.parse 'https://example.com/path'
|
954
|
+
c = @http.connection_for uri
|
955
|
+
def c.request(*)
|
956
|
+
raise OpenSSL::SSL::SSLError, "SSL3_WRITE_PENDING:bad write retry"
|
957
|
+
end
|
958
|
+
|
959
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
960
|
+
@http.request uri
|
961
|
+
end
|
962
|
+
assert_match %r%bad write retry%, e.message
|
963
|
+
end
|
964
|
+
|
965
|
+
def test_reset
|
966
|
+
c = basic_connection
|
967
|
+
c.start
|
968
|
+
touts[c.object_id] = Time.now
|
969
|
+
reqs[c.object_id] = 5
|
970
|
+
|
971
|
+
@http.reset c
|
972
|
+
|
973
|
+
assert c.started?
|
974
|
+
assert c.finished?
|
975
|
+
assert c.reset?
|
976
|
+
assert_equal 0, reqs[c.object_id]
|
977
|
+
assert_equal Net::HTTP::Persistent::EPOCH, touts[c.object_id]
|
978
|
+
end
|
979
|
+
|
980
|
+
def test_reset_host_down
|
981
|
+
c = basic_connection
|
982
|
+
touts[c.object_id] = Time.now
|
983
|
+
def c.start; raise Errno::EHOSTDOWN end
|
984
|
+
reqs[c.object_id] = 5
|
985
|
+
|
986
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
987
|
+
@http.reset c
|
988
|
+
end
|
989
|
+
|
990
|
+
assert_match %r%host down%, e.message
|
991
|
+
end
|
992
|
+
|
993
|
+
def test_reset_io_error
|
994
|
+
c = basic_connection
|
995
|
+
touts[c.object_id] = Time.now
|
996
|
+
reqs[c.object_id] = 5
|
997
|
+
|
998
|
+
@http.reset c
|
999
|
+
|
1000
|
+
assert c.started?
|
1001
|
+
assert c.finished?
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
def test_reset_refused
|
1005
|
+
c = basic_connection
|
1006
|
+
touts[c.object_id] = Time.now
|
1007
|
+
def c.start; raise Errno::ECONNREFUSED end
|
1008
|
+
reqs[c.object_id] = 5
|
1009
|
+
|
1010
|
+
e = assert_raises Net::HTTP::Persistent::Error do
|
1011
|
+
@http.reset c
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
assert_match %r%connection refused%, e.message
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def test_retry_change_requests_equals
|
1018
|
+
get = Net::HTTP::Get.new('/')
|
1019
|
+
post = Net::HTTP::Post.new('/')
|
1020
|
+
|
1021
|
+
refute @http.retry_change_requests
|
1022
|
+
|
1023
|
+
assert @http.can_retry?(get)
|
1024
|
+
refute @http.can_retry?(post)
|
1025
|
+
|
1026
|
+
@http.retry_change_requests = true
|
1027
|
+
|
1028
|
+
assert @http.retry_change_requests
|
1029
|
+
|
1030
|
+
assert @http.can_retry?(get)
|
1031
|
+
assert @http.can_retry?(post)
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
def test_shutdown
|
1035
|
+
ssl_conns
|
1036
|
+
c = connection
|
1037
|
+
rs = reqs
|
1038
|
+
ts = touts
|
1039
|
+
|
1040
|
+
orig = @http
|
1041
|
+
@http = Net::HTTP::Persistent.new 'name'
|
1042
|
+
c2 = connection
|
1043
|
+
|
1044
|
+
orig.shutdown
|
1045
|
+
|
1046
|
+
@http = orig
|
1047
|
+
|
1048
|
+
assert c.finished?, 'last-generation connection must be finished'
|
1049
|
+
refute c2.finished?, 'present generation connection must not be finished'
|
1050
|
+
|
1051
|
+
refute_same rs, reqs
|
1052
|
+
refute_same ts, touts
|
1053
|
+
|
1054
|
+
assert_empty conns
|
1055
|
+
assert_empty ssl_conns
|
1056
|
+
|
1057
|
+
assert_empty reqs
|
1058
|
+
assert_empty touts
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def test_shutdown_in_all_threads
|
1062
|
+
conns
|
1063
|
+
ssl_conns
|
1064
|
+
|
1065
|
+
t = Thread.new do
|
1066
|
+
c = connection
|
1067
|
+
ssl_conns
|
1068
|
+
conns
|
1069
|
+
reqs
|
1070
|
+
|
1071
|
+
Thread.stop
|
1072
|
+
|
1073
|
+
c
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
Thread.pass until t.status == 'sleep'
|
1077
|
+
|
1078
|
+
c = connection
|
1079
|
+
|
1080
|
+
assert_nil @http.shutdown_in_all_threads
|
1081
|
+
|
1082
|
+
assert c.finished?, 'connection in same thread must be finished'
|
1083
|
+
|
1084
|
+
assert_empty Thread.current[@http.generation_key]
|
1085
|
+
|
1086
|
+
assert_nil Thread.current[@http.request_key]
|
1087
|
+
|
1088
|
+
t.run
|
1089
|
+
assert t.value.finished?, 'connection in other thread must be finished'
|
1090
|
+
|
1091
|
+
assert_empty t[@http.generation_key]
|
1092
|
+
|
1093
|
+
assert_nil t[@http.request_key]
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
def test_shutdown_no_connections
|
1097
|
+
conns
|
1098
|
+
ssl_conns
|
1099
|
+
|
1100
|
+
@http.shutdown
|
1101
|
+
|
1102
|
+
assert_empty Thread.current[@http.generation_key]
|
1103
|
+
assert_empty Thread.current[@http.ssl_generation_key]
|
1104
|
+
|
1105
|
+
assert_nil Thread.current[@http.request_key]
|
1106
|
+
assert_nil Thread.current[@http.timeout_key]
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def test_shutdown_not_started
|
1110
|
+
ssl_conns
|
1111
|
+
|
1112
|
+
c = basic_connection
|
1113
|
+
def c.finish() raise IOError end
|
1114
|
+
|
1115
|
+
conns[0]["#{@uri.host}:#{@uri.port}"] = c
|
1116
|
+
|
1117
|
+
@http.shutdown
|
1118
|
+
|
1119
|
+
assert_empty Thread.current[@http.generation_key]
|
1120
|
+
assert_empty Thread.current[@http.ssl_generation_key]
|
1121
|
+
|
1122
|
+
assert_nil Thread.current[@http.request_key]
|
1123
|
+
assert_nil Thread.current[@http.timeout_key]
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
def test_shutdown_ssl
|
1127
|
+
@uri = URI 'https://example'
|
1128
|
+
|
1129
|
+
@http.connection_for @uri
|
1130
|
+
|
1131
|
+
@http.shutdown
|
1132
|
+
|
1133
|
+
assert_empty ssl_conns
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
def test_shutdown_thread
|
1137
|
+
t = Thread.new do
|
1138
|
+
c = connection
|
1139
|
+
conns
|
1140
|
+
ssl_conns
|
1141
|
+
|
1142
|
+
reqs
|
1143
|
+
|
1144
|
+
Thread.stop
|
1145
|
+
|
1146
|
+
c
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
Thread.pass until t.status == 'sleep'
|
1150
|
+
|
1151
|
+
c = connection
|
1152
|
+
|
1153
|
+
@http.shutdown t
|
1154
|
+
|
1155
|
+
refute c.finished?
|
1156
|
+
|
1157
|
+
t.run
|
1158
|
+
assert t.value.finished?
|
1159
|
+
assert_empty t[@http.generation_key]
|
1160
|
+
assert_empty t[@http.ssl_generation_key]
|
1161
|
+
assert_nil t[@http.request_key]
|
1162
|
+
assert_nil t[@http.timeout_key]
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
def test_ssl
|
1166
|
+
@http.verify_callback = :callback
|
1167
|
+
c = Net::HTTP.new 'localhost', 80
|
1168
|
+
|
1169
|
+
@http.ssl c
|
1170
|
+
|
1171
|
+
assert c.use_ssl?
|
1172
|
+
assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode
|
1173
|
+
assert_kind_of OpenSSL::X509::Store, c.cert_store
|
1174
|
+
assert_nil c.verify_callback
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
def test_ssl_ca_file
|
1178
|
+
@http.ca_file = 'ca_file'
|
1179
|
+
@http.verify_callback = :callback
|
1180
|
+
c = Net::HTTP.new 'localhost', 80
|
1181
|
+
|
1182
|
+
@http.ssl c
|
1183
|
+
|
1184
|
+
assert c.use_ssl?
|
1185
|
+
assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode
|
1186
|
+
assert_equal :callback, c.verify_callback
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
def test_ssl_cert_store
|
1190
|
+
store = OpenSSL::X509::Store.new
|
1191
|
+
@http.cert_store = store
|
1192
|
+
|
1193
|
+
c = Net::HTTP.new 'localhost', 80
|
1194
|
+
|
1195
|
+
@http.ssl c
|
1196
|
+
|
1197
|
+
assert c.use_ssl?
|
1198
|
+
assert_equal store, c.cert_store
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
def test_ssl_cert_store_default
|
1202
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
1203
|
+
|
1204
|
+
c = Net::HTTP.new 'localhost', 80
|
1205
|
+
|
1206
|
+
@http.ssl c
|
1207
|
+
|
1208
|
+
assert c.use_ssl?
|
1209
|
+
assert c.cert_store
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
def test_ssl_certificate
|
1213
|
+
@http.certificate = :cert
|
1214
|
+
@http.private_key = :key
|
1215
|
+
c = Net::HTTP.new 'localhost', 80
|
1216
|
+
|
1217
|
+
@http.ssl c
|
1218
|
+
|
1219
|
+
assert c.use_ssl?
|
1220
|
+
assert_equal :cert, c.cert
|
1221
|
+
assert_equal :key, c.key
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
def test_ssl_verify_mode
|
1225
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
1226
|
+
c = Net::HTTP.new 'localhost', 80
|
1227
|
+
|
1228
|
+
@http.ssl c
|
1229
|
+
|
1230
|
+
assert c.use_ssl?
|
1231
|
+
assert_equal OpenSSL::SSL::VERIFY_NONE, c.verify_mode
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
def test_ssl_warning
|
1235
|
+
orig_verify_peer = OpenSSL::SSL::VERIFY_PEER
|
1236
|
+
OpenSSL::SSL.send :remove_const, :VERIFY_PEER
|
1237
|
+
OpenSSL::SSL.send :const_set, :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE
|
1238
|
+
|
1239
|
+
c = Net::HTTP.new 'localhost', 80
|
1240
|
+
|
1241
|
+
out, err = capture_io do
|
1242
|
+
@http.ssl c
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
assert_empty out
|
1246
|
+
|
1247
|
+
assert_match %r%localhost:80%, err
|
1248
|
+
assert_match %r%I_KNOW_THAT_OPENSSL%, err
|
1249
|
+
|
1250
|
+
Object.send :const_set, :I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG, nil
|
1251
|
+
|
1252
|
+
assert_silent do
|
1253
|
+
@http.ssl c
|
1254
|
+
end
|
1255
|
+
ensure
|
1256
|
+
OpenSSL::SSL.send :remove_const, :VERIFY_PEER
|
1257
|
+
OpenSSL::SSL.send :const_set, :VERIFY_PEER, orig_verify_peer
|
1258
|
+
if Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then
|
1259
|
+
Object.send :remove_const, :I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG
|
1260
|
+
end
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
def test_ssl_cleanup
|
1264
|
+
uri1 = URI.parse 'https://one.example'
|
1265
|
+
|
1266
|
+
c1 = @http.connection_for uri1
|
1267
|
+
|
1268
|
+
touts[c1.object_id] = Time.now
|
1269
|
+
reqs[c1.object_id] = 5
|
1270
|
+
|
1271
|
+
@http.reconnect_ssl
|
1272
|
+
|
1273
|
+
@http.ssl_cleanup @http.ssl_generation
|
1274
|
+
|
1275
|
+
assert_empty ssl_conns
|
1276
|
+
assert_empty touts
|
1277
|
+
assert_empty reqs # sanity check, performed by #finish
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
def test_ssl_version_equals
|
1281
|
+
@http.ssl_version = :ssl_version
|
1282
|
+
|
1283
|
+
assert_equal :ssl_version, @http.ssl_version
|
1284
|
+
assert_equal 1, @http.ssl_generation
|
1285
|
+
end if RUBY_VERSION > '1.9'
|
1286
|
+
|
1287
|
+
def test_verify_callback_equals
|
1288
|
+
@http.verify_callback = :verify_callback
|
1289
|
+
|
1290
|
+
assert_equal :verify_callback, @http.verify_callback
|
1291
|
+
assert_equal 1, @http.ssl_generation
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
def test_verify_mode_equals
|
1295
|
+
@http.verify_mode = :verify_mode
|
1296
|
+
|
1297
|
+
assert_equal :verify_mode, @http.verify_mode
|
1298
|
+
assert_equal 1, @http.ssl_generation
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
end
|
1302
|
+
|