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.
@@ -1,5 +1,5 @@
1
1
  module Net
2
2
  class TCPClient #:nodoc
3
- VERSION = '1.0.2'
3
+ VERSION = '2.0.0'
4
4
  end
5
5
  end
@@ -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
+
@@ -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(port = 2000)
29
- start(port)
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(port)
33
- self.server = TCPServer.open(port)
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
- #thread.kill
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-----
@@ -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
- describe 'without server' do
9
- it 'raises an exception when cannot reach server after 5 retries' do
10
- exception = assert_raises Net::TCPClient::ConnectionFailure do
11
- Net::TCPClient.new(
12
- server: 'localhost:3300',
13
- connect_retry_interval: 0.1,
14
- connect_retry_count: 5)
15
- end
16
- assert_match /After 5 connection attempts to host 'localhost:3300': Errno::ECONNREFUSED/, exception.message
17
- end
18
-
19
- it 'times out on connect' do
20
- # Create a TCP Server, but do not respond to connections
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
- end
37
-
38
- describe "with server" do
39
- before do
40
- @server = SimpleTCPServer.new(2000)
41
- @server_name = 'localhost:2000'
42
- end
43
-
44
- after do
45
- @server.stop if @server
46
- end
47
-
48
- describe 'without client connection' do
49
- it 'times out on first receive and then successfully reads the response' do
50
- @read_timeout = 3.0
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
- it 'support infinite timeout' do
74
- @client = Net::TCPClient.new(
75
- server: @server_name,
76
- connect_timeout: -1
77
- )
78
- request = {'action' => 'test1'}
79
- @client.write(BSON.serialize(request))
80
- reply = read_bson_document(@client)
81
- assert_equal 'test1', reply['result']
82
- @client.close
83
- end
84
- end
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
- describe 'with client connection' do
87
- before do
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
- def after
98
- if @client
99
- @client.close
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
- it 'sends and receives data' do
105
- request = {'action' => 'test1'}
106
- @client.write(BSON.serialize(request))
107
- reply = read_bson_document(@client)
108
- assert_equal 'test1', reply['result']
109
- end
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
- it 'timeouts on receive' do
112
- request = {'action' => 'sleep', 'duration' => @read_timeout + 0.5}
113
- @client.write(BSON.serialize(request))
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
- exception = assert_raises Net::TCPClient::ReadTimeout do
116
- # Read 4 bytes from server
117
- @client.read(4)
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
- it 'retries on connection failure' do
126
- attempt = 0
127
- reply = @client.retry_on_connection_failure do
128
- request = {'action' => 'fail', 'attempt' => (attempt+=1)}
129
- @client.write(BSON.serialize(request))
130
- # Note: Do not put the read in this block if it never sends the
131
- # same request twice to the server
132
- read_bson_document(@client)
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
- end
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
- describe 'without client connection' do
140
- it 'connects to second server when the first is down' do
141
- client = Net::TCPClient.new(
142
- servers: ['localhost:1999', @server_name],
143
- read_timeout: 3
144
- )
145
- assert_equal @server_name, client.server
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
- request = {'action' => 'test1'}
148
- client.write(BSON.serialize(request))
149
- reply = read_bson_document(client)
150
- assert_equal 'test1', reply['result']
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
- client.close
153
- end
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
- it 'calls on_connect after connection' do
156
- client = Net::TCPClient.new(
157
- server: @server_name,
158
- read_timeout: 3,
159
- on_connect: Proc.new do |socket|
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
- request = {'action' => 'test1'}
168
- client.write(BSON.serialize(request))
169
- reply = read_bson_document(client)
170
- assert_equal 'test1', reply['result']
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
- client.close
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