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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1955b43fbf000cce65133a3015187ae3cb5e0c58
4
- data.tar.gz: 9a59c68c30a5fd7f544ff28ce45e348b185ee55e
3
+ metadata.gz: 9d6906da5ebe4657343ec1175664ee2f42b359eb
4
+ data.tar.gz: c816880d83f7d557390b00cbdfb5ed7c6a308512
5
5
  SHA512:
6
- metadata.gz: b87f0dfdf9a5662411bbde05c2c9ddb04ee74f0d7bd915a1d67db404165fd5398bcc0c4df28043846038571586ed2d842446da95170f437790e5c31e8e807840
7
- data.tar.gz: e3e3810825b828d2e474f6785f047f0ca64a301ce628fc5cfc0e696e22315b477a5413639bdc4b51634032a77d9a8b54c2c4ed6134db6fc92066cd96acce508d
6
+ metadata.gz: 5d1aafb91baf376f68f291d5d18bc9d5569522cc2b093baf337f525b8060d3e632f6799ee64d8da12fc4db533f01a29059ec63144403da29e0f9884cd0f1a623
7
+ data.tar.gz: 32605f07229206ee29216ccfb5a2ecc74fe9c8c6d32459b7ec973fcd31f4541ab4ca710f148e6804254838265af364737449265908407f227100a96b761950af
data/README.md CHANGED
@@ -1,43 +1,184 @@
1
1
  # net_tcp_client
2
- ![](https://img.shields.io/gem/v/net_tcp_client.svg) ![](https://img.shields.io/travis/rocketjob/net_tcp_client.svg) ![](https://img.shields.io/gem/dt/net_tcp_client.svg) ![](https://img.shields.io/badge/status-production%20ready-blue.svg)
2
+ [![Gem Version](https://img.shields.io/gem/v/net_tcp_client.svg)](https://rubygems.org/gems/net_tcp_client) [![Build Status](https://travis-ci.org/rocketjob/net_tcp_client.svg?branch=master)](https://travis-ci.org/rocketjob/net_tcp_client) [![Downloads](https://img.shields.io/gem/dt/net_tcp_client.svg)](https://rubygems.org/gems/net_tcp_client) [![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](http://opensource.org/licenses/Apache-2.0) ![](https://img.shields.io/badge/status-Production%20Ready-blue.svg) [![Gitter chat](https://img.shields.io/badge/IRC%20(gitter)-Support-brightgreen.svg)](https://gitter.im/rocketjob/support)
3
3
 
4
- Net::TCPClient is a TCP Socket Client with built-in timeouts, retries, and logging
4
+ Net::TCPClient is a TCP Socket Client with automated failover, load balancing, retries and built-in timeouts.
5
5
 
6
6
  * http://github.com/rocketjob/net_tcp_client
7
7
 
8
8
  ## Introduction
9
9
 
10
- Net::TCPClient implements resilience features that many developers wish was
10
+ Net::TCPClient implements high availability and resilience features that many developers wish was
11
11
  already included in the standard Ruby libraries.
12
12
 
13
- With so many "client" libraries to servers such us memcache, MongoDB, Redis, etc.
14
- their focus is on the communication formats and messaging interactions. As a result
15
- adding resilience is usually an after thought.
16
-
17
- More importantly the way that each client implements connection failure handling
18
- varies dramatically. The purpose of this library is to extract the best
19
- of all the socket error handling out there and create a consistent way of dealing
20
- with connection failures.
21
-
22
13
  Another important feature is that the _connect_ and _read_ API's use timeout's to
23
14
  prevent a network issue from "hanging" the client program.
24
15
 
25
- ## Net::TCPClient API
16
+ ## Features
17
+
18
+ * Automated failover to another server.
19
+ * Load balancing across multiple servers.
20
+ * SSL and non-ssl connections.
21
+ * Connect Timeout.
22
+ * Read Timeout.
23
+ * Write Timeout.
24
+ * Fails over / load balances across all servers under a single DNS entry.
25
+ * Logging.
26
+ * Optional trace level logging of all data sent or received.
27
+ * Uses non blocking timeouts, instead of using threads such as used by the Timeout class.
28
+ * Additional exceptions to distinguish between connection failures and timeouts.
29
+ * Handshake callbacks.
30
+ * After a new connection has been established callbacks can be used
31
+ for handshakes such as authentication before data is sent.
32
+
33
+ ### Example
34
+
35
+ ~~~ruby
36
+ require 'net/tcp_client'
37
+
38
+ Net::TCPClient.connect(server: 'mydomain:3300') do |client|
39
+ client.send('Update the database')
40
+ response = client.read(20)
41
+ puts "Received: #{response}"
42
+ end
43
+ ~~~
44
+
45
+ Enable SSL encryption:
46
+
47
+ ~~~ruby
48
+ require 'net/tcp_client'
26
49
 
27
- Net::TCPClient is a drop in replacement for TCPSocket when used as a client.
50
+ Net::TCPClient.connect(server: 'mydomain:3300', ssl: true) do |client|
51
+ client.send('Update the database')
52
+ response = client.read(20)
53
+ puts "Received: #{response}"
54
+ end
55
+ ~~~
28
56
 
29
- The initializer is the only deviation since it accepts several new options
30
- that support automatic failover, re-connect and messaging retries.
57
+ ## High Availability
31
58
 
32
- ## Example
59
+ Net::TCPClient automatically tries each server in turn, should it fail to connect, or
60
+ if the connection is lost the next server is tried immediately.
33
61
 
34
- Connect to a server at address `localhost`, and on port `3300`.
62
+ Net::TCPClient detects DNS entries that have multiple IP Addresses associated with them and
63
+ adds each of the ip addresses for the single DNS name to the list of servers to try to connect to.
35
64
 
36
- Specify a custom retry interval and retry counts during connection.
65
+ If a server is unavailable, cannot connect, or the connection is lost, the next server is immediately
66
+ tried. Once all servers have been exhausted, it will keep trying to connect, starting with the
67
+ first server again.
37
68
 
38
- ```ruby
39
- require 'net/tcp_client'
69
+ When a connection is first established, and every time a connection is lost, Net::TCPClient
70
+ uses connection policies to determine which server to connect to.
71
+
72
+ ## Load Balancing
73
+
74
+ Using the connection policies client TCP connections can be balanced across multiple servers.
75
+
76
+ ## Connection Policies
77
+
78
+ #### Ordered
79
+
80
+ Servers are tried in the order they were supplied.
81
+
82
+ ~~~ruby
83
+ tcp_client = Net::TCPClient.new(
84
+ servers: ['server1:3300', 'server2:3300', 'server3:3600']
85
+ )
86
+ ~~~
87
+
88
+ The servers will tried in the following order:
89
+ `server1`, `server2`, `server3`
90
+
91
+ `:ordered` is the default, but can be explicitly defined follows:
92
+
93
+ ~~~ruby
94
+ tcp_client = Net::TCPClient.new(
95
+ servers: ['server1:3300', 'server2:3300', 'server3:3600'],
96
+ policy: :ordered
97
+ )
98
+ ~~~
99
+
100
+ #### Random
101
+
102
+ Servers are tried in a Random order.
103
+
104
+ ~~~ruby
105
+ tcp_client = Net::TCPClient.new(
106
+ servers: ['server1:3300', 'server2:3300', 'server3:3600'],
107
+ policy: :ordered
108
+ )
109
+ ~~~
110
+
111
+ No server is tried again until all of the others have been tried first.
112
+
113
+ Example run, the servers could be tried in the following order:
114
+ `server3`, `server1`, `server2`
115
+
116
+ #### Custom defined order
117
+
118
+ Supply your own custom order / load balancing algorithm for connecting to servers:
119
+
120
+ Example:
121
+
122
+ ~~~ruby
123
+ tcp_client = Net::TCPClient.new(
124
+ servers: ['server1:3300', 'server2:3300', 'server3:3600'],
125
+ policy: -> addresses, count do
126
+ # Return nil after the last address has been tried so that retry logic can take over
127
+ if count <= address.size
128
+ addresses.sample
129
+ end
130
+ end
131
+ )
132
+ ~~~
133
+
134
+ The above example returns addresses in random order without checking if a host name has been used before.
135
+
136
+ It is important to check the count so that once all servers have been tried, it should return nil so that
137
+ the retry logic can take over. Otherwise it will constantly try to connect to the servers without
138
+ the retry delays etc.
139
+
140
+ Example run, the servers could be tried in the following order:
141
+ `server3`, `server1`, `server3`
40
142
 
143
+ ### Automatic Retry
144
+
145
+ If a connection cannot be established to any servers in the list Net::TCPClient will retry from the
146
+ first server. This retry behavior can be controlled using the following options:
147
+
148
+ * `connect_retry_count` [Fixnum]
149
+ * Number of times to retry connecting when a connection fails
150
+ * Default: 10
151
+
152
+ * `connect_retry_interval` [Float]
153
+ * Number of seconds between connection retry attempts after the first failed attempt
154
+ * Default: 0.5
155
+
156
+ * `retry_count` [Fixnum]
157
+ * Number of times to retry when calling #retry_on_connection_failure
158
+ * This is independent of :connect_retry_count which still applies with
159
+ * connection failures. This retry controls upto how many times to retry the
160
+ * supplied block should a connection failure occur during the block
161
+ * Default: 3
162
+
163
+ #### Note
164
+
165
+ A server will only be retried again using the retry controls above once all other servers in the
166
+ list have been exhausted.
167
+
168
+ This means that if a connection is lost to a server that it will try to connect to a different server,
169
+ not the same server unless it is the only server in the list.
170
+
171
+ ## Tuning
172
+
173
+ If there are multiple servers in the list it is important to keep the `connect_timeout` low otherwise
174
+ it can take a long time to find the next available server.
175
+
176
+ ## Retry on connection loss
177
+
178
+ To transparently handle when a connection is lost after it has been established
179
+ wrap calls that can be retried with `retry_on_connection_failure`.
180
+
181
+ ~~~ruby
41
182
  Net::TCPClient.connect(
42
183
  server: 'localhost:3300',
43
184
  connect_retry_interval: 0.1,
@@ -45,12 +186,57 @@ Net::TCPClient.connect(
45
186
  ) do |client|
46
187
  # If the connection is lost, create a new one and retry the send
47
188
  client.retry_on_connection_failure do
48
- client.send('Update the database')
189
+ client.send('How many users available?')
190
+ response = client.read(20)
191
+ puts "Received: #{response}"
49
192
  end
50
- response = client.read(20)
51
- puts "Received: #{response}"
52
193
  end
53
- ```
194
+ ~~~
195
+
196
+ If the connection is lost during either the `send` or the `read` above the
197
+ entire block will be re-tried once the connection has been re-stablished.
198
+
199
+ ## Callbacks
200
+
201
+ Any time a connection has been established a callback can be called to handle activities such as:
202
+
203
+ * Initialize per connection session sequence numbers.
204
+ * Pass authentication information to the server.
205
+ * Perform a handshake with the server.
206
+
207
+ #### Authentication example:
208
+
209
+ ~~~ruby
210
+ tcp_client = Net::TCPClient.new(
211
+ servers: ['server1:3300', 'server2:3300', 'server3:3600'],
212
+ on_connect: -> do |client|
213
+ client.send('My username and password')
214
+ result = client.read(2)
215
+ raise "Authentication failed" if result != 'OK'
216
+ end
217
+ )
218
+ ~~~
219
+
220
+ #### Per connection sequence number example:
221
+
222
+ ~~~ruby
223
+ tcp_client = Net::TCPClient.new(
224
+ servers: ['server1:3300', 'server2:3300', 'server3:3600'],
225
+ on_connect: -> do |client|
226
+ # Set the sequence number to 0
227
+ user_data = 0
228
+ end
229
+ )
230
+
231
+ tcp_client.retry_on_connection_failure do
232
+ # Send with the sequence number
233
+ tcp_client.send("#{tcp_client.user_data} hello")
234
+ result = tcp_client.receive(30)
235
+
236
+ # Increment sequence number after every call to the server
237
+ tcp_client.user_data += 1
238
+ end
239
+ ~~~
54
240
 
55
241
  ## Project Status
56
242
 
@@ -64,34 +250,46 @@ test on a daily basis, including connections over the internet between remote da
64
250
 
65
251
  gem install net_tcp_client
66
252
 
67
- Although not required, it is recommended to use [Semantic Logger](http://rocketjob.github.io/semantic_logger) for logging purposes:
253
+ To enable logging add [Semantic Logger](http://rocketjob.github.io/semantic_logger):
68
254
 
69
255
  gem install semantic_logger
70
256
 
71
257
  Or, add the following lines to you `Gemfile`:
72
258
 
73
- ```ruby
74
- gem 'semantic_logger'
75
- gem 'net_tcp_client'
76
- ```
259
+ ~~~ruby
260
+ gem 'semantic_logger'
261
+ gem 'net_tcp_client'
262
+ ~~~
77
263
 
78
264
  To configure a stand-alone application for Semantic Logger:
79
265
 
80
- ```ruby
266
+ ~~~ruby
81
267
  require 'semantic_logger'
82
268
 
83
269
  # Set the global default log level
84
270
  SemanticLogger.default_level = :trace
85
271
 
86
272
  # Log to a file, and use the colorized formatter
87
- SemanticLogger.add_appender('development.log', &SemanticLogger::Appender::Base.colorized_formatter)
88
- ```
273
+ SemanticLogger.add_appender(file_name: 'development.log', formatter: :color)
274
+ ~~~
89
275
 
90
276
  If running Rails, see: [Semantic Logger Rails](http://rocketjob.github.io/semantic_logger/rails.html)
91
277
 
92
- Without Semantic Logger present a Ruby logger can be passed into Net::TCPClient.
278
+ ### Support
93
279
 
94
- ### Upgrading from ResilientSocket
280
+ Join the [Gitter chat session](https://gitter.im/rocketjob/support) if you have any questions.
281
+
282
+ Issues / bugs can be reported via [Github issues](https://github.com/rocketjob/net_tcp_client/issues).
283
+
284
+ ### Upgrading to V2
285
+
286
+ The following breaking changes have been made with V2:
287
+ * The Connection timeout default is now 10 seconds, was 30 seconds.
288
+ * To enable logging, add gem semantic_logger.
289
+ * The :logger option has been removed.
290
+ * Deprecated option and attribute :server_selector has been removed.
291
+
292
+ ### Upgrading from ResilientSocket ![](https://img.shields.io/gem/dt/resilient_socket.svg)
95
293
 
96
294
  ResilientSocket::TCPClient has been renamed to Net::TCPClient.
97
295
  The API is exactly the same, just with a new namespace. Please upgrade to the new
@@ -101,8 +299,8 @@ with `Net::TCPClient` in your code.
101
299
  ## Supports
102
300
 
103
301
  Tested and supported on the following Ruby platforms:
104
- - Ruby 1.9.3, 2.0, 2.1, 2.2 and above
105
- - JRuby 1.7, 9.0 and above
302
+ - Ruby 2.1, 2.2, 2.3 and above
303
+ - JRuby 1.7.23, 9.0 and above
106
304
  - Rubinius 2.5 and above
107
305
 
108
306
  There is a soft dependency on [Semantic Logger](http://github.com/rocketjob/semantic_logger). It will use SemanticLogger only if
@@ -112,14 +310,12 @@ it is already available, otherwise any other standard Ruby logger can be used.
112
310
 
113
311
  Be sure to place the `semantic_logger` gem dependency before `net_tcp_client` in your Gemfile.
114
312
 
115
- ## Versioning
116
-
117
- This project adheres to [Semantic Versioning](http://semver.org/).
118
-
119
313
  ## Author
120
314
 
121
315
  [Reid Morrison](https://github.com/reidmorrison)
122
316
 
317
+ [Contributors](https://github.com/rocketjob/net_tcp_client/graphs/contributors)
318
+
123
319
  ## Versioning
124
320
 
125
321
  This project uses [Semantic Versioning](http://semver.org/).
@@ -1,6 +1,22 @@
1
1
  require 'socket'
2
+ # Load SemanticLogger if available
3
+ begin
4
+ require 'semantic_logger'
5
+ rescue LoadError
6
+ end
2
7
  require 'net/tcp_client/version'
8
+ require 'net/tcp_client/address'
3
9
  require 'net/tcp_client/exceptions'
4
10
  require 'net/tcp_client/tcp_client'
5
- # Use the builtin logger only if semantic logger is not already loaded
6
- require 'net/tcp_client/logging'
11
+
12
+ # @formatter:off
13
+ module Net
14
+ class TCPClient
15
+ module Policy
16
+ autoload :Base, 'net/tcp_client/policy/base.rb'
17
+ autoload :Custom, 'net/tcp_client/policy/custom.rb'
18
+ autoload :Ordered, 'net/tcp_client/policy/ordered.rb'
19
+ autoload :Random, 'net/tcp_client/policy/random.rb'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ require 'socket'
2
+ require 'ipaddr'
3
+ module Net
4
+ class TCPClient
5
+ # Host name, ip address and port to connect to
6
+ class Address
7
+ attr_accessor :host_name, :ip_address, :port
8
+
9
+ # Returns [Array<String>] ip addresses for the supplied DNS entry
10
+ # Returns dns_name if it is already an IP Address
11
+ def self.ip_addresses(dns_name)
12
+ ips = []
13
+ Socket.getaddrinfo(dns_name, nil, Socket::AF_INET, Socket::SOCK_STREAM).each do |s|
14
+ ips << s[3] if s[0] == 'AF_INET'
15
+ end
16
+ ips.uniq
17
+ end
18
+
19
+ # Returns [Array<Net::TCPClient::Address>] addresses for a given DNS / host name.
20
+ # The Addresses will contain the resolved ip address, host name, and port number.
21
+ #
22
+ # Note:
23
+ # Multiple ip addresses will be returned when a DNS entry has multiple ip addresses associated with it.
24
+ def self.addresses(dns_name, port)
25
+ ip_addresses(dns_name).collect { |ip| new(dns_name, ip, port) }
26
+ end
27
+
28
+ # Returns [Array<Net::TCPClient::Address>] addresses for a list of DNS / host name's
29
+ # that are paired with their numbers
30
+ #
31
+ # server_name should be either a host_name, or ip address combined with a port:
32
+ # "host_name:1234"
33
+ # "192.168.1.10:80"
34
+ def self.addresses_for_server_name(server_name)
35
+ dns_name, port = server_name.split(':')
36
+ port = port.to_i
37
+ raise(ArgumentError, "Invalid host_name: #{server_name.inspect}. Must be formatted as 'host_name:1234' or '192.168.1.10:80'") unless dns_name && (port > 0)
38
+ addresses(dns_name, port)
39
+ end
40
+
41
+ def initialize(host_name, ip_address, port)
42
+ @host_name = host_name
43
+ @ip_address = ip_address
44
+ @port = port.to_i
45
+ end
46
+
47
+ def to_s
48
+ "#{host_name}[#{ip_address}]:#{port}"
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -1,14 +1,16 @@
1
1
  module Net
2
2
  class TCPClient
3
3
 
4
- class ConnectionTimeout < ::SocketError;
4
+ class ConnectionTimeout < ::SocketError
5
5
  end
6
- class ReadTimeout < ::SocketError;
6
+ class ReadTimeout < ::SocketError
7
+ end
8
+ class WriteTimeout < ::SocketError
7
9
  end
8
10
 
9
11
  # Raised by ResilientSocket whenever a Socket connection failure has occurred
10
12
  class ConnectionFailure < ::SocketError
11
- # Returns the hostname and port against which the connection failure occurred
13
+ # Returns the host name and port against which the connection failure occurred
12
14
  attr_reader :server
13
15
 
14
16
  # Returns the original exception that caused the connection failure