net_tcp_client 1.0.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +237 -41
- data/lib/net/tcp_client.rb +18 -2
- data/lib/net/tcp_client/address.rb +53 -0
- data/lib/net/tcp_client/exceptions.rb +5 -3
- data/lib/net/tcp_client/policy/base.rb +36 -0
- data/lib/net/tcp_client/policy/custom.rb +39 -0
- data/lib/net/tcp_client/policy/ordered.rb +14 -0
- data/lib/net/tcp_client/policy/random.rb +14 -0
- data/lib/net/tcp_client/tcp_client.rb +332 -209
- data/lib/net/tcp_client/version.rb +1 -1
- data/test/address_test.rb +91 -0
- data/test/policy/custom_policy_test.rb +42 -0
- data/test/policy/ordered_policy_test.rb +36 -0
- data/test/policy/random_policy_test.rb +46 -0
- data/test/simple_tcp_server.rb +36 -9
- data/test/ssl_files/ca.pem +19 -0
- data/test/ssl_files/localhost-server-key.pem +27 -0
- data/test/ssl_files/localhost-server.pem +18 -0
- data/test/tcp_client_test.rb +207 -143
- data/test/test_helper.rb +1 -2
- metadata +23 -5
- data/lib/net/tcp_client/logging.rb +0 -193
@@ -0,0 +1,91 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'ipaddr'
|
3
|
+
|
4
|
+
class Net::TCPClient::AddressTest < Minitest::Test
|
5
|
+
describe Net::TCPClient::Address do
|
6
|
+
describe '.ip_addresses' do
|
7
|
+
it 'returns the ip addresses for a known DNS' do
|
8
|
+
ips = Net::TCPClient::Address.ip_addresses('google.com')
|
9
|
+
assert ips.count > 0
|
10
|
+
ips.each do |ip|
|
11
|
+
# Validate IP Addresses
|
12
|
+
IPAddr.new(ip)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns an ip address' do
|
17
|
+
ips = Net::TCPClient::Address.ip_addresses('127.0.0.1')
|
18
|
+
assert_equal 1, ips.count
|
19
|
+
assert_equal '127.0.0.1', ips.first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.addresses' do
|
24
|
+
it 'returns one address for a known DNS' do
|
25
|
+
addresses = Net::TCPClient::Address.addresses('localhost', 80)
|
26
|
+
assert_equal 1, addresses.count, addresses.ai
|
27
|
+
address = addresses.first
|
28
|
+
assert_equal 80, address.port
|
29
|
+
assert_equal '127.0.0.1', address.ip_address
|
30
|
+
assert_equal 'localhost', address.host_name
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns addresses for a DNS with mutiple IPs' do
|
34
|
+
addresses = Net::TCPClient::Address.addresses('google.com', 80)
|
35
|
+
assert addresses.count > 0
|
36
|
+
addresses.each do |address|
|
37
|
+
# Validate IP Addresses
|
38
|
+
IPAddr.new(address.ip_address)
|
39
|
+
assert_equal 80, address.port
|
40
|
+
assert_equal 'google.com', address.host_name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns an ip address' do
|
45
|
+
addresses = Net::TCPClient::Address.addresses('127.0.0.1', 80)
|
46
|
+
assert_equal 1, addresses.count
|
47
|
+
address = addresses.first
|
48
|
+
assert_equal 80, address.port
|
49
|
+
assert_equal '127.0.0.1', address.ip_address
|
50
|
+
assert_equal '127.0.0.1', address.host_name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '.addresses_for_server_name' do
|
55
|
+
it 'returns addresses for server name' do
|
56
|
+
addresses = Net::TCPClient::Address.addresses_for_server_name('localhost:80')
|
57
|
+
assert_equal 1, addresses.count, addresses.ai
|
58
|
+
address = addresses.first
|
59
|
+
assert_equal 80, address.port
|
60
|
+
assert_equal '127.0.0.1', address.ip_address
|
61
|
+
assert_equal 'localhost', address.host_name
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns an ip address' do
|
65
|
+
addresses = Net::TCPClient::Address.addresses_for_server_name('127.0.0.1:80')
|
66
|
+
assert_equal 1, addresses.count
|
67
|
+
address = addresses.first
|
68
|
+
assert_equal 80, address.port
|
69
|
+
assert_equal '127.0.0.1', address.ip_address
|
70
|
+
assert_equal '127.0.0.1', address.host_name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '.new' do
|
75
|
+
it 'creates an address' do
|
76
|
+
address = Net::TCPClient::Address.new('host_name', 'ip_address', '2000')
|
77
|
+
assert_equal 'host_name', address.host_name
|
78
|
+
assert_equal 'ip_address', address.ip_address
|
79
|
+
assert_equal 2000, address.port
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#to_s' do
|
84
|
+
it 'returns a string of the address' do
|
85
|
+
address = Net::TCPClient::Address.new('host_name', 'ip_address', '2000')
|
86
|
+
assert_equal 'host_name[ip_address]:2000', address.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
class Net::TCPClient::Policy::CustomTest < Minitest::Test
|
3
|
+
describe Net::TCPClient::Policy::Custom do
|
4
|
+
describe '#each' do
|
5
|
+
before do
|
6
|
+
@proc = -> addresses, count do
|
7
|
+
addresses[count - 1]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'must return one server, once' do
|
12
|
+
servers = ['localhost:80']
|
13
|
+
policy = Net::TCPClient::Policy::Custom.new(servers, @proc)
|
14
|
+
collected = []
|
15
|
+
policy.each { |address| collected << address }
|
16
|
+
assert_equal 1, collected.size
|
17
|
+
address = collected.first
|
18
|
+
assert_equal 80, address.port
|
19
|
+
assert_equal 'localhost', address.host_name
|
20
|
+
assert_equal '127.0.0.1', address.ip_address
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'must return the servers in the supplied order' do
|
24
|
+
servers = %w(localhost:80 127.0.0.1:2000 lvh.me:2100)
|
25
|
+
policy = Net::TCPClient::Policy::Custom.new(servers, @proc)
|
26
|
+
names = []
|
27
|
+
policy.each { |address| names << address.host_name }
|
28
|
+
assert_equal %w(localhost 127.0.0.1 lvh.me), names
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'must handle an empty list of servers' do
|
32
|
+
servers = []
|
33
|
+
policy = Net::TCPClient::Policy::Custom.new(servers, @proc)
|
34
|
+
names = []
|
35
|
+
policy.each { |address| names << address.host_name }
|
36
|
+
assert_equal [], names
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
class Net::TCPClient::Policy::OrderedTest < Minitest::Test
|
3
|
+
describe Net::TCPClient::Policy::Ordered do
|
4
|
+
describe '#each' do
|
5
|
+
it 'must return one server, once' do
|
6
|
+
servers = ['localhost:80']
|
7
|
+
policy = Net::TCPClient::Policy::Ordered.new(servers)
|
8
|
+
collected = []
|
9
|
+
policy.each { |address| collected << address }
|
10
|
+
assert_equal 1, collected.size
|
11
|
+
address = collected.first
|
12
|
+
assert_equal 80, address.port
|
13
|
+
assert_equal 'localhost', address.host_name
|
14
|
+
assert_equal '127.0.0.1', address.ip_address
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'must return the servers in the supplied order' do
|
18
|
+
servers = %w(localhost:80 127.0.0.1:2000 lvh.me:2100)
|
19
|
+
policy = Net::TCPClient::Policy::Ordered.new(servers)
|
20
|
+
names = []
|
21
|
+
policy.each { |address| names << address.host_name }
|
22
|
+
assert_equal %w(localhost 127.0.0.1 lvh.me), names
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'must handle an empty list of servers' do
|
26
|
+
servers = []
|
27
|
+
policy = Net::TCPClient::Policy::Ordered.new(servers)
|
28
|
+
names = []
|
29
|
+
policy.each { |address| names << address.host_name }
|
30
|
+
assert_equal [], names
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
class Net::TCPClient::Policy::RandomTest < Minitest::Test
|
3
|
+
describe Net::TCPClient::Policy::Random do
|
4
|
+
describe '#each' do
|
5
|
+
it 'must return one server, once' do
|
6
|
+
servers = ['localhost:80']
|
7
|
+
policy = Net::TCPClient::Policy::Random.new(servers)
|
8
|
+
collected = []
|
9
|
+
policy.each { |address| collected << address }
|
10
|
+
assert_equal 1, collected.size
|
11
|
+
address = collected.first
|
12
|
+
assert_equal 80, address.port
|
13
|
+
assert_equal 'localhost', address.host_name
|
14
|
+
assert_equal '127.0.0.1', address.ip_address
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'must return the servers in random order' do
|
18
|
+
servers = %w(localhost:80 127.0.0.1:2000 lvh.me:2100)
|
19
|
+
policy = Net::TCPClient::Policy::Random.new(servers)
|
20
|
+
count = 0
|
21
|
+
|
22
|
+
names = []
|
23
|
+
# It is possible the random order is the supplied order.
|
24
|
+
# Keep retrying until the order is different.
|
25
|
+
3.times do
|
26
|
+
policy.each { |address| names << address.host_name }
|
27
|
+
break if names != %w(localhost:80 127.0.0.1:2000 lvh.me:2100)
|
28
|
+
names = []
|
29
|
+
end
|
30
|
+
|
31
|
+
refute_equal %w(localhost 127.0.0.1 lvh.me), names
|
32
|
+
assert_equal %w(localhost 127.0.0.1 lvh.me).sort, names.sort
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'must handle an empty list of servers' do
|
36
|
+
servers = []
|
37
|
+
policy = Net::TCPClient::Policy::Random.new(servers)
|
38
|
+
names = []
|
39
|
+
policy.each { |address| names << address.host_name }
|
40
|
+
assert_equal [], names
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
data/test/simple_tcp_server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'openssl'
|
2
3
|
require 'bson'
|
3
4
|
require 'semantic_logger'
|
4
5
|
|
@@ -18,19 +19,34 @@ def read_bson_document(io)
|
|
18
19
|
return BSON.deserialize(bytebuf)
|
19
20
|
end
|
20
21
|
|
22
|
+
def ssl_file_path(name)
|
23
|
+
File.join(File.dirname(__FILE__), 'ssl_files', name)
|
24
|
+
end
|
25
|
+
|
21
26
|
# Simple single threaded server for testing purposes using a local socket
|
22
27
|
# Sends and receives BSON Messages
|
23
28
|
class SimpleTCPServer
|
24
29
|
include SemanticLogger::Loggable
|
25
|
-
|
26
30
|
attr_accessor :thread, :server
|
31
|
+
attr_reader :port, :name, :ssl
|
27
32
|
|
28
|
-
def initialize(
|
29
|
-
|
33
|
+
def initialize(options = {})
|
34
|
+
@port = (options[:port] || 2000).to_i
|
35
|
+
@name = options[:name] || 'tcp'
|
36
|
+
@ssl = options[:ssl] || false
|
37
|
+
start
|
30
38
|
end
|
31
39
|
|
32
|
-
def start
|
33
|
-
|
40
|
+
def start
|
41
|
+
tcp_server = TCPServer.open(port)
|
42
|
+
|
43
|
+
if ssl
|
44
|
+
context = OpenSSL::SSL::SSLContext.new
|
45
|
+
context.set_params(ssl)
|
46
|
+
tcp_server = OpenSSL::SSL::SSLServer.new(tcp_server, context)
|
47
|
+
end
|
48
|
+
|
49
|
+
self.server = tcp_server
|
34
50
|
self.thread = Thread.new do
|
35
51
|
loop do
|
36
52
|
logger.debug 'Waiting for a client to connect'
|
@@ -59,6 +75,8 @@ class SimpleTCPServer
|
|
59
75
|
case message['action']
|
60
76
|
when 'test1'
|
61
77
|
{'result' => 'test1'}
|
78
|
+
when 'servername'
|
79
|
+
{'result' => @name}
|
62
80
|
when 'sleep'
|
63
81
|
sleep message['duration'] || 1
|
64
82
|
{'result' => 'sleep'}
|
@@ -91,9 +109,7 @@ class SimpleTCPServer
|
|
91
109
|
server.close
|
92
110
|
client.close
|
93
111
|
logger.debug 'Server closed'
|
94
|
-
|
95
|
-
logger.debug 'thread killed'
|
96
|
-
start(2000)
|
112
|
+
start
|
97
113
|
logger.debug 'Server Restarted'
|
98
114
|
break
|
99
115
|
end
|
@@ -108,6 +124,17 @@ end
|
|
108
124
|
if $0 == __FILE__
|
109
125
|
SemanticLogger.default_level = :trace
|
110
126
|
SemanticLogger.add_appender(STDOUT)
|
111
|
-
server = SimpleTCPServer.new(2000)
|
127
|
+
server = SimpleTCPServer.new(port: 2000)
|
128
|
+
|
129
|
+
# For SSL:
|
130
|
+
# server = SimpleTCPServer.new(
|
131
|
+
# port: 2000,
|
132
|
+
# ssl: {
|
133
|
+
# cert: ssl_file_path('localhost-server.pem'),
|
134
|
+
# key: ssl_file_path('localhost-server-key.pem'),
|
135
|
+
# ca_file: ssl_file_path('ca.pem')
|
136
|
+
# }
|
137
|
+
# )
|
138
|
+
|
112
139
|
server.thread.join
|
113
140
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDCzCCAfOgAwIBAgIJANF9v1dJ6jhOMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNV
|
3
|
+
BAMMEXRlc3QtdG1nYXJkbmVyLWNhMB4XDTE1MTEwNjA0NDk0NVoXDTE1MTIwNjA0
|
4
|
+
NDk0NVowHDEaMBgGA1UEAwwRdGVzdC10bWdhcmRuZXItY2EwggEiMA0GCSqGSIb3
|
5
|
+
DQEBAQUAA4IBDwAwggEKAoIBAQDIi3qxxegV2RYl8lEWu/iwb9tqsdEYQ1gAhdAS
|
6
|
+
OJAJk8SfgvjlKnyx0rV2LXhORpp13uLMB5QKXS1B+JByCYEZrC3q9b9Cl58Nl4am
|
7
|
+
ky6uI/AXvbEvzSAo2resea5JGpQDxl3UJC674QdVCGR7s7Wm7am/JEr8aami4V5O
|
8
|
+
+CmLfdr030jPaNVcqVS2Bf0AqrxNa3nWoHsPh1MG2VJRVpnqIuqPVL7yRi39/cu2
|
9
|
+
Kd1WjJPxpbrM8H2AClX2OY6fxa3RHUUyOaveOkX5AWYXtqoMs9Evv95m4hOVAogd
|
10
|
+
KzBjdtpTiIM+bdlpxY0y2Dpc9oibRFiLFqooB9Bb8VsmE/2JAgMBAAGjUDBOMB0G
|
11
|
+
A1UdDgQWBBQo+GvUH8j+YFYkWtwQl32d4fVoZTAfBgNVHSMEGDAWgBQo+GvUH8j+
|
12
|
+
YFYkWtwQl32d4fVoZTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAH
|
13
|
+
RvaXH9dN3fiRvMZ8+tXY7W57jspvYtQJjNxcVGU+Pn5fznrr0E3STPoSKy6+u9rQ
|
14
|
+
L0Jzq9e0tjbMmdbMugRjSOdVUGtXaki/YaGNkbKOsMLHWSgFHVZF3gJ83KdDuuFK
|
15
|
+
bMTqOmrQvCG+JECH3i/lh5GHLuZlCzOmBwhzWuorjQQy/MUxQHRkHPVk4Ik7pCXm
|
16
|
+
HsNcp9l2L5oKG8I4X54ElGXHoshdIXKUS6yKcnTJlhI2nt6cQCMzKoiVA5TxNnvz
|
17
|
+
jdCID4nezuHFyYI3KVxsR7PzqS2fmHcfC9qGqskn+QgJ4JcwvAhT3USj4muuu3mJ
|
18
|
+
/F9cJiBN2yeRQuknI6de
|
19
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEogIBAAKCAQEApAeEPJjQwT10hLgBl8550gJeBuP43R8sDO63H2Q2QsgN0sLg
|
3
|
+
G0fzjR8qNjYf62XwADMsCE9PwheaVSM+sREbEGu80G3IICTFP5HPuqm6ugiuYZpD
|
4
|
+
BtZgiSFM1A7ShSTR0gCWqusRaR+r/IOREftvQcjqeJP0hZxYjx4XO9jBufiQMpGq
|
5
|
+
qPaUQC3cXv4aDoLYbHAP+XH4FxwAbEz9/qBTeAiNorL2ruFJeqMHlKPIDt6123LF
|
6
|
+
kMZ8pxke8gqmggieKH549j8VHUSpwo2TbxSIqArM8P6wDRFrZik6ZMEIX13yuet2
|
7
|
+
XaFMyHLMknyh2+0vKz9sup533rk7fKUKhTtJ/QIDAQABAoIBAAnOYTt2L1S+Jc8h
|
8
|
+
aQb4UxQZDCIBUwl1KZ8ETnJT/WJ0r07gU6GN5aOUL2PaTII4L+bzKDi+9Re3bYSV
|
9
|
+
fNP9H88Vgc48IfC6AgjQ4MhaCU3B4xr2q/cmrdLE6ODsme1XzCtv2ZISR5IvUIri
|
10
|
+
GrQmgfo+1rWqsr2iITE9LUpopPxHJV29DIVc5EuP/c+r4TFarUFd+1JdthABE3v7
|
11
|
+
xMgyejnLs59Dy084RcL60mpCahvIBXdKEMY8nzhN3APp7VHx/++yTvsvcG+aNVrr
|
12
|
+
C/FixbUnQWIHCeu8lvFz6fb+mMt923ZE2mcbdmOzgZqjEQYaPxfERRDTHNz9NlPQ
|
13
|
+
2Jahz0ECgYEA2JGdlD26KsYYBvFtNCsxw78DfonLolwcWK0pwKDtXb/3h8RWjBlu
|
14
|
+
vmCmhE726nVl00nt2FUoGpHwcVDPwjdxFkStaSezGxEJQHEov9ZjSYJY9IdpDOhL
|
15
|
+
Q8nz2geDL8P2Ti6pOCKPqgwgbgOsIgTYwreFG1KemsHIH2HeQZxFllkCgYEAweUC
|
16
|
+
k3OjR1cLa5mgABLkcl8RDEBoPAFcNZg+gGgSMneEDvP2DqZ2h8CxzdG1d2VSpTHD
|
17
|
+
IC2LeF5EwNq7h6X7Har0j+e9h1/TESqvx60ph89AxecjsEqjOBKto5rBMTOaah9M
|
18
|
+
IXvk+fP/bwOY+6W7F4KJQk6BYw1i2FiCzivXZEUCgYAg5YWdNf8obizKKTQgX4tQ
|
19
|
+
o5xBRWckQ3+ezLbx5sAHpJhSDDXlVBupWX8Ry/jfxnNwM+OoH89WseJnJBJa+xb3
|
20
|
+
ffklZv1i2CSioE3DTiqIyP8ALe18I3EDXBLphIid4dNxLs9PkphmCS+H5pDoHfpb
|
21
|
+
IYtbiiJDeboPYktjhfxgCQKBgDW8dVlOPBtCaXzZp7k9gyibZksh8oFm0xpbZj8K
|
22
|
+
GLj53JSUUkY/Jix7YAutqgA8CYqU3wIk/TlPzvgv5rcybgUL4xma3TEOgp2IWg0Z
|
23
|
+
1Z+49bejVoW+ObwJmSv1cMNlDM+KevvwrUYEtG8c7SIZDV/3onjI7xz3kcRpy16+
|
24
|
+
UcSNAoGAe1sVhiyZczEZYgjKMedLvFoFaerw4Ik6PnGCi61nrQtsa3HpLd45M648
|
25
|
+
j8/HAq7/nF+7grkPFJPFBjrx1B44k6vRZNK2hC6XexaVBgGxFktfqCfytalpL3AX
|
26
|
+
QqQTsJcW63zEqaFJDhO0dv9oHJuSuYnHTERgVw7VwyLC+bEkYIk=
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,18 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIC+TCCAeGgAwIBAgIJANNH6Xe+/HxXMA0GCSqGSIb3DQEBBQUAMBwxGjAYBgNV
|
3
|
+
BAMMEXRlc3QtdG1nYXJkbmVyLWNhMB4XDTE1MTEwNjA0NTMwMloXDTE1MTIwNjA0
|
4
|
+
NTMwMlowKjEoMCYGA1UEAwwfdGVzdC10bWdhcmRuZXItbG9jYWxob3N0LXNlcnZl
|
5
|
+
cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKQHhDyY0ME9dIS4AZfO
|
6
|
+
edICXgbj+N0fLAzutx9kNkLIDdLC4BtH840fKjY2H+tl8AAzLAhPT8IXmlUjPrER
|
7
|
+
GxBrvNBtyCAkxT+Rz7qpuroIrmGaQwbWYIkhTNQO0oUk0dIAlqrrEWkfq/yDkRH7
|
8
|
+
b0HI6niT9IWcWI8eFzvYwbn4kDKRqqj2lEAt3F7+Gg6C2GxwD/lx+BccAGxM/f6g
|
9
|
+
U3gIjaKy9q7hSXqjB5SjyA7etdtyxZDGfKcZHvIKpoIInih+ePY/FR1EqcKNk28U
|
10
|
+
iKgKzPD+sA0Ra2YpOmTBCF9d8rnrdl2hTMhyzJJ8odvtLys/bLqed965O3ylCoU7
|
11
|
+
Sf0CAwEAAaMwMC4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwFAYDVR0RBA0wC4IJ
|
12
|
+
bG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4IBAQCR6ruCXgsTyeI+RtnhfVrWlsdF
|
13
|
+
69eQHxGeHLd882V7xN4XfIKtfpgMVp7UlXTS/Lgtw08x+2bPiPz8EPgXa0WoYRjg
|
14
|
+
i5M/4lrK++YPMzIY4o8nJY7jRcwoWGzw3rQemrc4cjj7e9YnNLOSG/pww0ticV4F
|
15
|
+
+85IjE9rRsO4m/yla+epJTZiqJ0lSnCCaeNyW4/f4bipgA6PfddmteaJFUt+c7wF
|
16
|
+
N992bWpb8+x0ZXGg5+A6a0NVFMu2856WeVRwMKbNwfXObDYxCdHSOufcv9hsxL4j
|
17
|
+
fiF/R2ts5QbxYSoG2gnRPYxmyn/fnNen3f1u6iY4f8PVmb+6SOu0DH4sDf49
|
18
|
+
-----END CERTIFICATE-----
|
data/test/tcp_client_test.rb
CHANGED
@@ -5,175 +5,239 @@ require_relative 'simple_tcp_server'
|
|
5
5
|
# Unit Test for Net::TCPClient
|
6
6
|
class TCPClientTest < Minitest::Test
|
7
7
|
describe Net::TCPClient do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
server = TCPServer.open(2001)
|
22
|
-
|
23
|
-
exception = assert_raises Net::TCPClient::ConnectionTimeout do
|
24
|
-
1000.times do
|
25
|
-
Net::TCPClient.new(
|
26
|
-
server: 'localhost:2001',
|
27
|
-
connect_timeout: 0.5,
|
28
|
-
connect_retry_count: 3
|
29
|
-
)
|
8
|
+
[false, true].each do |with_ssl|
|
9
|
+
describe (with_ssl ? 'ssl' : 'non-ssl') do
|
10
|
+
describe '#connect' do
|
11
|
+
it 'raises an exception when cannot reach server after 5 retries' do
|
12
|
+
exception = assert_raises Net::TCPClient::ConnectionFailure do
|
13
|
+
new_net_tcp_client(with_ssl,
|
14
|
+
server: 'localhost:3300',
|
15
|
+
connect_retry_interval: 0.1,
|
16
|
+
connect_retry_count: 5
|
17
|
+
)
|
18
|
+
end
|
19
|
+
assert_match(/Failed to connect to any of localhost:3300 after 5 retries/, exception.message)
|
20
|
+
assert_match Errno::ECONNREFUSED.to_s, exception.cause.class.to_s
|
30
21
|
end
|
31
|
-
end
|
32
|
-
assert_match /Timedout after/, exception.message
|
33
|
-
server.close
|
34
|
-
end
|
35
22
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# Need a custom client that does not auto close on error:
|
52
|
-
@client = Net::TCPClient.new(
|
53
|
-
server: @server_name,
|
54
|
-
read_timeout: @read_timeout,
|
55
|
-
close_on_error: false
|
56
|
-
)
|
57
|
-
|
58
|
-
request = {'action' => 'sleep', 'duration' => @read_timeout + 0.5}
|
59
|
-
@client.write(BSON.serialize(request))
|
60
|
-
|
61
|
-
exception = assert_raises Net::TCPClient::ReadTimeout do
|
62
|
-
# Read 4 bytes from server
|
63
|
-
@client.read(4)
|
23
|
+
it 'times out on connect' do
|
24
|
+
# Create a TCP Server, but do not respond to connections
|
25
|
+
server = TCPServer.open(2001)
|
26
|
+
|
27
|
+
exception = assert_raises Net::TCPClient::ConnectionFailure do
|
28
|
+
1000.times do
|
29
|
+
new_net_tcp_client(with_ssl,
|
30
|
+
server: 'localhost:2001',
|
31
|
+
connect_timeout: 0.5,
|
32
|
+
connect_retry_count: 3
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
assert_match(/Failed to connect to any of localhost:2001 after 3 retries/, exception.message)
|
37
|
+
server.close
|
64
38
|
end
|
65
|
-
assert_equal false, @client.close_on_error
|
66
|
-
assert @client.alive?, 'The client connection is not alive after the read timed out with close_on_error: false'
|
67
|
-
assert_match /Timedout after #{@read_timeout} seconds trying to read from #{@server_name}/, exception.message
|
68
|
-
reply = read_bson_document(@client)
|
69
|
-
assert_equal 'sleep', reply['result']
|
70
|
-
@client.close
|
71
39
|
end
|
72
40
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
41
|
+
describe 'with server' do
|
42
|
+
before do
|
43
|
+
options = {port: 2000}
|
44
|
+
if with_ssl
|
45
|
+
options[:ssl] = {
|
46
|
+
cert: OpenSSL::X509::Certificate.new(File.open(ssl_file_path('localhost-server.pem'))),
|
47
|
+
key: OpenSSL::PKey::RSA.new(File.open(ssl_file_path('localhost-server-key.pem'))),
|
48
|
+
ca_file: ssl_file_path('ca.pem')
|
49
|
+
}
|
50
|
+
end
|
51
|
+
count = 0
|
52
|
+
begin
|
53
|
+
@server = SimpleTCPServer.new(options)
|
54
|
+
rescue Errno::EADDRINUSE => exc
|
55
|
+
@server.stop if @server
|
56
|
+
# Give previous test server time to stop
|
57
|
+
count += 1
|
58
|
+
sleep 1
|
59
|
+
retry if count <= 30
|
60
|
+
raise exc
|
61
|
+
end
|
85
62
|
|
86
|
-
|
87
|
-
|
88
|
-
@read_timeout = 3.0
|
89
|
-
@client = Net::TCPClient.new(
|
90
|
-
server: @server_name,
|
91
|
-
read_timeout: @read_timeout
|
92
|
-
)
|
93
|
-
assert @client.alive?
|
94
|
-
assert_equal true, @client.close_on_error
|
95
|
-
end
|
63
|
+
@server_name = 'localhost:2000'
|
64
|
+
end
|
96
65
|
|
97
|
-
|
98
|
-
|
99
|
-
@
|
100
|
-
assert !@client.alive?
|
66
|
+
after do
|
67
|
+
@client.close if @client
|
68
|
+
@server.stop if @server
|
101
69
|
end
|
102
|
-
end
|
103
70
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
71
|
+
describe '#read' do
|
72
|
+
it 'read timeout, followed by successful read' do
|
73
|
+
@read_timeout = 3.0
|
74
|
+
# Need a custom client that does not auto close on error:
|
75
|
+
@client = new_net_tcp_client(with_ssl,
|
76
|
+
server: @server_name,
|
77
|
+
read_timeout: @read_timeout,
|
78
|
+
close_on_error: false
|
79
|
+
)
|
80
|
+
|
81
|
+
request = {'action' => 'sleep', 'duration' => @read_timeout + 0.5}
|
82
|
+
@client.write(BSON.serialize(request))
|
83
|
+
|
84
|
+
exception = assert_raises Net::TCPClient::ReadTimeout do
|
85
|
+
# Read 4 bytes from server
|
86
|
+
@client.read(4)
|
87
|
+
end
|
88
|
+
assert_equal false, @client.close_on_error
|
89
|
+
assert @client.alive?, 'The client connection is not alive after the read timed out with close_on_error: false'
|
90
|
+
assert_equal "Timed out after #{@read_timeout} seconds trying to read from localhost[127.0.0.1]:2000", exception.message
|
91
|
+
reply = read_bson_document(@client)
|
92
|
+
assert_equal 'sleep', reply['result']
|
93
|
+
@client.close
|
94
|
+
end
|
110
95
|
|
111
|
-
|
112
|
-
|
113
|
-
@
|
96
|
+
it 'infinite timeout' do
|
97
|
+
@client = new_net_tcp_client(with_ssl,
|
98
|
+
server: @server_name,
|
99
|
+
connect_timeout: -1
|
100
|
+
)
|
101
|
+
request = {'action' => 'test1'}
|
102
|
+
@client.write(BSON.serialize(request))
|
103
|
+
reply = read_bson_document(@client)
|
104
|
+
assert_equal 'test1', reply['result']
|
105
|
+
@client.close
|
106
|
+
end
|
107
|
+
end
|
114
108
|
|
115
|
-
|
116
|
-
|
117
|
-
|
109
|
+
describe '#connect' do
|
110
|
+
it 'calls on_connect after connection' do
|
111
|
+
@client = new_net_tcp_client(with_ssl,
|
112
|
+
server: @server_name,
|
113
|
+
read_timeout: 3,
|
114
|
+
on_connect: Proc.new do |socket|
|
115
|
+
# Reset user_data on each connection
|
116
|
+
socket.user_data = {sequence: 1}
|
117
|
+
end
|
118
|
+
)
|
119
|
+
assert_equal 'localhost[127.0.0.1]:2000', @client.address.to_s
|
120
|
+
assert_equal 1, @client.user_data[:sequence]
|
121
|
+
|
122
|
+
request = {'action' => 'test1'}
|
123
|
+
@client.write(BSON.serialize(request))
|
124
|
+
reply = read_bson_document(@client)
|
125
|
+
assert_equal 'test1', reply['result']
|
126
|
+
end
|
118
127
|
end
|
119
|
-
# Due to close_on_error: true, a timeout will close the connection
|
120
|
-
# to prevent use of a socket connection in an inconsistent state
|
121
|
-
assert_equal false, @client.alive?
|
122
|
-
assert_match /Timedout after #{@read_timeout} seconds trying to read from #{@server_name}/, exception.message
|
123
|
-
end
|
124
128
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
129
|
+
describe 'failover' do
|
130
|
+
it 'connects to second server when the first is down' do
|
131
|
+
@client = new_net_tcp_client(with_ssl,
|
132
|
+
servers: ['localhost:1999', @server_name],
|
133
|
+
read_timeout: 3
|
134
|
+
)
|
135
|
+
assert_equal 'localhost[127.0.0.1]:2000', @client.address.to_s
|
136
|
+
|
137
|
+
request = {'action' => 'test1'}
|
138
|
+
@client.write(BSON.serialize(request))
|
139
|
+
reply = read_bson_document(@client)
|
140
|
+
assert_equal 'test1', reply['result']
|
141
|
+
end
|
133
142
|
end
|
134
|
-
assert_equal 'fail', reply['result']
|
135
|
-
end
|
136
143
|
|
137
|
-
|
144
|
+
describe 'with client' do
|
145
|
+
before do
|
146
|
+
@read_timeout = 3.0
|
147
|
+
@client = new_net_tcp_client(with_ssl,
|
148
|
+
server: @server_name,
|
149
|
+
read_timeout: @read_timeout
|
150
|
+
)
|
151
|
+
assert @client.alive?, @client.ai
|
152
|
+
assert_equal true, @client.close_on_error
|
153
|
+
end
|
138
154
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
155
|
+
describe '#alive?' do
|
156
|
+
it 'returns false once the connection is closed' do
|
157
|
+
assert @client.alive?
|
158
|
+
@client.close
|
159
|
+
refute @client.alive?
|
160
|
+
end
|
161
|
+
end
|
146
162
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
163
|
+
describe '#closed?' do
|
164
|
+
it 'returns true once the connection is closed' do
|
165
|
+
refute @client.closed?
|
166
|
+
@client.close
|
167
|
+
assert @client.closed?
|
168
|
+
end
|
169
|
+
end
|
151
170
|
|
152
|
-
|
153
|
-
|
171
|
+
describe '#close' do
|
172
|
+
it 'closes the connection, repeatedly without error' do
|
173
|
+
@client.close
|
174
|
+
@client.close
|
175
|
+
end
|
176
|
+
end
|
154
177
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
# Reset user_data on each connection
|
161
|
-
socket.user_data = {sequence: 1}
|
178
|
+
describe '#write' do
|
179
|
+
it 'writes data' do
|
180
|
+
request = {'action' => 'test1'}
|
181
|
+
@client.write(BSON.serialize(request))
|
182
|
+
end
|
162
183
|
end
|
163
|
-
)
|
164
|
-
assert_equal @server_name, client.server
|
165
|
-
assert_equal 1, client.user_data[:sequence]
|
166
184
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
185
|
+
describe '#read' do
|
186
|
+
it 'reads a response' do
|
187
|
+
request = {'action' => 'test1'}
|
188
|
+
@client.write(BSON.serialize(request))
|
189
|
+
reply = read_bson_document(@client)
|
190
|
+
assert_equal 'test1', reply['result']
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'times out on receive' do
|
194
|
+
request = {'action' => 'sleep', 'duration' => @read_timeout + 0.5}
|
195
|
+
@client.write(BSON.serialize(request))
|
196
|
+
|
197
|
+
exception = assert_raises Net::TCPClient::ReadTimeout do
|
198
|
+
# Read 4 bytes from server
|
199
|
+
@client.read(4)
|
200
|
+
end
|
201
|
+
# Due to close_on_error: true, a timeout will close the connection
|
202
|
+
# to prevent use of a socket connection in an inconsistent state
|
203
|
+
assert_equal false, @client.alive?
|
204
|
+
assert_equal "Timed out after #{@read_timeout} seconds trying to read from localhost[127.0.0.1]:2000", exception.message
|
205
|
+
end
|
206
|
+
end
|
171
207
|
|
172
|
-
|
208
|
+
describe '#retry_on_connection_failure' do
|
209
|
+
it 'retries on connection failure' do
|
210
|
+
attempt = 0
|
211
|
+
reply = @client.retry_on_connection_failure do
|
212
|
+
request = {'action' => 'fail', 'attempt' => (attempt+=1)}
|
213
|
+
@client.write(BSON.serialize(request))
|
214
|
+
read_bson_document(@client)
|
215
|
+
end
|
216
|
+
assert_equal 'fail', reply['result']
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
173
220
|
end
|
174
221
|
end
|
175
222
|
|
176
223
|
end
|
177
224
|
|
225
|
+
def ssl_file_path(name)
|
226
|
+
File.join(File.dirname(__FILE__), 'ssl_files', name)
|
227
|
+
end
|
228
|
+
|
229
|
+
def new_net_tcp_client(with_ssl, params)
|
230
|
+
params = params.dup
|
231
|
+
if with_ssl
|
232
|
+
params.merge!(
|
233
|
+
ssl: {
|
234
|
+
ca_file: ssl_file_path('ca.pem'),
|
235
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
236
|
+
}
|
237
|
+
)
|
238
|
+
end
|
239
|
+
Net::TCPClient.new(params)
|
240
|
+
end
|
241
|
+
|
178
242
|
end
|
179
243
|
end
|