net_tcp_client 1.0.2 → 2.0.0
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.
- 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
|