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.
@@ -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