net_tcp_client 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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