tcp-client 0.9.0 → 0.9.4
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/.yardopts +5 -0
- data/README.md +6 -0
- data/gems.rb +2 -1
- data/lib/tcp-client/address.rb +30 -14
- data/lib/tcp-client/configuration.rb +134 -98
- data/lib/tcp-client/deadline.rb +1 -3
- data/lib/tcp-client/default_configuration.rb +20 -8
- data/lib/tcp-client/errors.rb +34 -14
- data/lib/tcp-client/version.rb +1 -4
- data/lib/tcp-client.rb +121 -88
- data/rakefile.rb +1 -2
- data/spec/tcp-client/address_spec.rb +15 -28
- data/spec/tcp-client/configuration_spec.rb +7 -6
- data/tcp-client.gemspec +9 -9
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 114da301d59fc9cf9c3d3bda508f4d23b5b4991877a5dc7504d4da97114f8e7f
|
4
|
+
data.tar.gz: 9d0815501de340b485cba157aff05097597948d023a7eca6c45a8ead3a4e3d0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a560644d578cedccaf1e2665f518b3a6a4e225886cfc561a8b62087420d93b958ee4741d8efde3b456fa15ce24fd5ee2f6a1121b55855205ffae5abf17becf97
|
7
|
+
data.tar.gz: f1f0ed0b87f8ee9c9b376b6b567c45aecb204c5b3de044c97f6ba080071c3c8e70da8bca8cca2484d5ddec06df181ee4b007e9ed826045e205d77df38eefbbbd
|
data/.yardopts
ADDED
data/README.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
A TCP client implementation with working timeout support.
|
4
4
|
|
5
|
+
- Gem: [rubygems.org](https://rubygems.org/gems/tcp-client)
|
6
|
+
- Source: [github.com](https://github.com/mblumtritt/tcp-client)
|
7
|
+
- Help: [rubydoc.info](https://rubydoc.info/github/mblumtritt/tcp-client/main/index)
|
8
|
+
|
5
9
|
## Description
|
6
10
|
|
7
11
|
This Gem implements a TCP client with (optional) SSL support. It is an easy to use, versatile configurable client that can correctly handle time limits. Unlike other implementations, this client respects predefined/configurable time limits for each method (`connect`, `read`, `write`). Deadlines for a sequence of read/write actions can also be monitored.
|
@@ -30,6 +34,8 @@ TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
|
|
30
34
|
end
|
31
35
|
```
|
32
36
|
|
37
|
+
For more samples see [the samples dir](https://github.com/mblumtritt/tcp-client/tree/main/sample)
|
38
|
+
|
33
39
|
## Installation
|
34
40
|
|
35
41
|
Use [Bundler](http://gembundler.com/) to use TCPClient in your own project:
|
data/gems.rb
CHANGED
data/lib/tcp-client/address.rb
CHANGED
@@ -21,24 +21,31 @@ class TCPClient
|
|
21
21
|
# Initializes an address
|
22
22
|
# @overload initialize(addr)
|
23
23
|
# The addr can be specified as
|
24
|
-
# - a valid named address containing the port like +my.host.test:80+
|
25
|
-
# - a valid TCPv4 address like +142.250.181.206:80+
|
26
|
-
# - a valid TCPv6 address like +[2001:16b8:5093:3500:ad77:abe6:eb88:47b6]:80+
|
27
24
|
#
|
28
|
-
#
|
25
|
+
# - a valid named address containing the port like "my.host.test:80"
|
26
|
+
# - a valid TCPv4 address like "142.250.181.206:80"
|
27
|
+
# - a valid TCPv6 address like
|
28
|
+
# "[2001:16b8:5093:3500:ad77:abe6:eb88:47b6]:80"
|
29
29
|
#
|
30
|
-
#
|
31
|
-
#
|
30
|
+
# @example create an Address instance with a host name and port
|
31
|
+
# Address.new('www.google.com:80')
|
32
|
+
#
|
33
|
+
# @param addr [String] address containing host and port name
|
32
34
|
#
|
33
|
-
# @param address [Address]
|
34
35
|
#
|
35
36
|
# @overload initialize(addrinfo)
|
36
37
|
#
|
38
|
+
# @example create an Address with an Addrinfo
|
39
|
+
# Address.new(Addrinfo.tcp('www.google.com', 'http'))
|
40
|
+
#
|
37
41
|
# @param addrinfo [Addrinfo] containing the addressed host and port
|
38
42
|
#
|
39
43
|
# @overload initialize(port)
|
40
44
|
# Adresses the port on the local machine.
|
41
45
|
#
|
46
|
+
# @example create an Address for localhost on port 80
|
47
|
+
# Address.new(80)
|
48
|
+
#
|
42
49
|
# @param port [Integer] the addressed port
|
43
50
|
#
|
44
51
|
def initialize(addr)
|
@@ -56,18 +63,27 @@ class TCPClient
|
|
56
63
|
end
|
57
64
|
|
58
65
|
#
|
59
|
-
# @
|
66
|
+
# @attribute [r] port
|
67
|
+
# @return [Integer] the port number
|
68
|
+
#
|
69
|
+
def port
|
70
|
+
@addrinfo.ip_port
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# @return [String] text representation of self as "host:port"
|
60
75
|
#
|
61
76
|
def to_s
|
62
|
-
|
63
|
-
"#{@hostname}:#{@addrinfo.ip_port}"
|
77
|
+
hostname.index(':') ? "[#{hostname}]:#{port}" : "#{hostname}:#{port}"
|
64
78
|
end
|
65
79
|
|
66
80
|
#
|
67
|
-
#
|
81
|
+
# Convert `self` to a Hash containing host and port attribute.
|
82
|
+
#
|
83
|
+
# @return [Hash] host and port
|
68
84
|
#
|
69
85
|
def to_h
|
70
|
-
{ host:
|
86
|
+
{ host: hostname, port: port }
|
71
87
|
end
|
72
88
|
|
73
89
|
# @!visibility private
|
@@ -89,7 +105,7 @@ class TCPClient
|
|
89
105
|
end
|
90
106
|
|
91
107
|
def init_from_addrinfo(addrinfo)
|
92
|
-
@hostname
|
108
|
+
@hostname = addrinfo.getnameinfo(Socket::NI_NUMERICSERV).first
|
93
109
|
@addrinfo = addrinfo
|
94
110
|
end
|
95
111
|
|
@@ -102,7 +118,7 @@ class TCPClient
|
|
102
118
|
def from_string(str)
|
103
119
|
idx = str.rindex(':') or return nil, str.to_i
|
104
120
|
name = str[0, idx].delete_prefix('[').delete_suffix(']')
|
105
|
-
[name, str[idx + 1, str.size - idx].to_i]
|
121
|
+
[name.empty? ? nil : name, str[idx + 1, str.size - idx].to_i]
|
106
122
|
end
|
107
123
|
end
|
108
124
|
end
|
@@ -6,55 +6,56 @@ class TCPClient
|
|
6
6
|
#
|
7
7
|
# A Configuration is used to configure the behavior of a {TCPClient} instance.
|
8
8
|
#
|
9
|
-
# It allows to specify
|
9
|
+
# It allows to specify the monitor timeout, how to handle exceptions, if SSL
|
10
10
|
# should be used and to setup the underlying Socket.
|
11
11
|
#
|
12
12
|
class Configuration
|
13
13
|
#
|
14
|
-
#
|
15
|
-
# Shorthand to create a new configuration with given options.
|
16
|
-
#
|
17
|
-
# @example
|
18
|
-
# config = TCPClient::Configuration.create(buffered: false)
|
19
|
-
#
|
20
|
-
# @param options [Hash] see {#initialize} for details
|
21
|
-
#
|
22
|
-
# @return [Configuration] the initialized configuration
|
23
|
-
#
|
24
|
-
# @overload create(&block)
|
25
|
-
# Shorthand to initialize a new configuration.
|
14
|
+
# Shorthand to create a new configuration.
|
26
15
|
#
|
16
|
+
# @overload create()
|
27
17
|
# @example
|
28
18
|
# config = TCPClient::Configuration.create do |cfg|
|
29
19
|
# cfg.buffered = false
|
30
20
|
# cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
|
31
21
|
# end
|
32
22
|
#
|
33
|
-
# @yieldparam
|
23
|
+
# @yieldparam configuration {Configuration}
|
34
24
|
#
|
35
|
-
#
|
25
|
+
# @overload create(options)
|
26
|
+
# @example
|
27
|
+
# config = TCPClient::Configuration.create(buffered: false)
|
28
|
+
#
|
29
|
+
# @param options [Hash<Symbol,Object>] see {#initialize} for details
|
30
|
+
#
|
31
|
+
# @return [Configuration] the initialized configuration
|
36
32
|
#
|
37
33
|
def self.create(options = {})
|
38
|
-
|
39
|
-
yield(
|
40
|
-
|
34
|
+
configuration = new(options)
|
35
|
+
yield(configuration) if block_given?
|
36
|
+
configuration
|
41
37
|
end
|
42
38
|
|
43
39
|
#
|
44
40
|
# Intializes the instance with given options.
|
45
41
|
#
|
46
|
-
# @param options [Hash]
|
42
|
+
# @param options [Hash<Symbol,Object>]
|
47
43
|
# @option options [Boolean] :buffered, see {#buffered}
|
48
44
|
# @option options [Boolean] :keep_alive, see {#keep_alive}
|
49
45
|
# @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
|
50
|
-
# @option options [
|
46
|
+
# @option options [Hash<Symbol, Object>] :ssl_params, see {#ssl_params}
|
51
47
|
# @option options [Numeric] :connect_timeout, see {#connect_timeout}
|
52
|
-
# @option options [Exception] :connect_timeout_error, see
|
48
|
+
# @option options [Class<Exception>] :connect_timeout_error, see
|
49
|
+
# {#connect_timeout_error}
|
53
50
|
# @option options [Numeric] :read_timeout, see {#read_timeout}
|
54
|
-
# @option options [Exception] :read_timeout_error, see
|
51
|
+
# @option options [Class<Exception>] :read_timeout_error, see
|
52
|
+
# {#read_timeout_error}
|
55
53
|
# @option options [Numeric] :write_timeout, see {#write_timeout}
|
56
|
-
# @option options [Exception] :write_timeout_error, see
|
57
|
-
#
|
54
|
+
# @option options [Class<Exception>] :write_timeout_error, see
|
55
|
+
# {#write_timeout_error}
|
56
|
+
# @option options [Boolean] :normalize_network_errors, see
|
57
|
+
# {#normalize_network_errors}
|
58
|
+
#
|
58
59
|
#
|
59
60
|
def initialize(options = {})
|
60
61
|
@buffered = @keep_alive = @reverse_lookup = true
|
@@ -66,11 +67,13 @@ class TCPClient
|
|
66
67
|
options.each_pair { |attribute, value| set(attribute, value) }
|
67
68
|
end
|
68
69
|
|
70
|
+
# @!group Instance Attributes Socket Level
|
71
|
+
|
69
72
|
#
|
70
|
-
# Enables/disables use of Socket-level
|
73
|
+
# Enables/disables use of Socket-level buffering
|
71
74
|
#
|
72
|
-
# @return [
|
73
|
-
#
|
75
|
+
# @return [Boolean] wheter the connection is allowed to use internal buffers
|
76
|
+
# (default) or not
|
74
77
|
#
|
75
78
|
attr_reader :buffered
|
76
79
|
|
@@ -81,8 +84,8 @@ class TCPClient
|
|
81
84
|
#
|
82
85
|
# Enables/disables use of Socket-level keep alive handling.
|
83
86
|
#
|
84
|
-
# @return [
|
85
|
-
#
|
87
|
+
# @return [Boolean] wheter the connection is allowed to use keep alive
|
88
|
+
# signals (default) or not
|
86
89
|
#
|
87
90
|
attr_reader :keep_alive
|
88
91
|
|
@@ -93,8 +96,8 @@ class TCPClient
|
|
93
96
|
#
|
94
97
|
# Enables/disables address lookup.
|
95
98
|
#
|
96
|
-
# @return [
|
97
|
-
#
|
99
|
+
# @return [Boolean] wheter the connection is allowed to lookup the address
|
100
|
+
# (default) or not
|
98
101
|
#
|
99
102
|
attr_reader :reverse_lookup
|
100
103
|
|
@@ -103,55 +106,45 @@ class TCPClient
|
|
103
106
|
end
|
104
107
|
|
105
108
|
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
# @return [true] if all network exceptions should be raised as {NetworkError}
|
109
|
-
# @return [false] if socket/system errors should not be normalzed (default)
|
109
|
+
# @!parse attr_reader :ssl?
|
110
|
+
# @return [Boolean] wheter SSL is configured, see {#ssl_params}
|
110
111
|
#
|
111
|
-
|
112
|
-
|
113
|
-
def normalize_network_errors=(value)
|
114
|
-
@normalize_network_errors = value ? true : false
|
112
|
+
def ssl?
|
113
|
+
@ssl_params ? true : false
|
115
114
|
end
|
116
115
|
|
117
116
|
#
|
118
|
-
#
|
119
|
-
#
|
117
|
+
# Parameters used to initialize a SSL context. SSL/TLS will only be used if
|
118
|
+
# this attribute is not `nil`.
|
120
119
|
#
|
121
|
-
# @return [
|
122
|
-
# @return [nil] if
|
123
|
-
#
|
124
|
-
# @see #connect_timeout
|
125
|
-
# @see #read_timeout
|
126
|
-
# @see #write_timeout
|
120
|
+
# @return [Hash<Symbol, Object>] SSL parameters for the SSL context
|
121
|
+
# @return [nil] if no SSL should be used (default)
|
127
122
|
#
|
128
|
-
|
129
|
-
@connect_timeout = @write_timeout = @read_timeout = seconds(value)
|
130
|
-
end
|
123
|
+
attr_reader :ssl_params
|
131
124
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
# @see #read_timeout_error
|
142
|
-
# @see #write_timeout_error
|
143
|
-
#
|
144
|
-
def timeout_error=(value)
|
145
|
-
raise(NotAnExceptionError, value) unless exception_class?(value)
|
146
|
-
@connect_timeout_error =
|
147
|
-
@read_timeout_error = @write_timeout_error = value
|
125
|
+
def ssl_params=(value)
|
126
|
+
@ssl_params =
|
127
|
+
if value.respond_to?(:to_hash)
|
128
|
+
Hash[value.to_hash]
|
129
|
+
elsif value.respond_to?(:to_h)
|
130
|
+
value.nil? ? nil : Hash[value.to_h]
|
131
|
+
else
|
132
|
+
value ? {} : nil
|
133
|
+
end
|
148
134
|
end
|
135
|
+
alias ssl= ssl_params=
|
136
|
+
|
137
|
+
# @!endgroup
|
138
|
+
|
139
|
+
# @!group Instance Attributes Timeout Monitoring
|
149
140
|
|
150
141
|
#
|
151
|
-
#
|
142
|
+
# The maximum time in seconds to establish a connection.
|
152
143
|
#
|
153
|
-
# @return [Numeric] maximum time in seconds
|
154
|
-
# @return [nil] if the connect time should not be
|
144
|
+
# @return [Numeric] maximum time in seconds
|
145
|
+
# @return [nil] if the connect time should not be monitored (default)
|
146
|
+
#
|
147
|
+
# @see TCPClient#connect
|
155
148
|
#
|
156
149
|
attr_reader :connect_timeout
|
157
150
|
|
@@ -160,7 +153,10 @@ class TCPClient
|
|
160
153
|
end
|
161
154
|
|
162
155
|
#
|
163
|
-
#
|
156
|
+
# The exception class which will be raised if {TCPClient#connect} can not
|
157
|
+
# be finished in time.
|
158
|
+
#
|
159
|
+
# @return [Class<Exception>] exception class raised
|
164
160
|
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
165
161
|
#
|
166
162
|
attr_reader :connect_timeout_error
|
@@ -171,10 +167,12 @@ class TCPClient
|
|
171
167
|
end
|
172
168
|
|
173
169
|
#
|
174
|
-
#
|
170
|
+
# The maximum time in seconds to read from a connection.
|
175
171
|
#
|
176
|
-
# @return [Numeric] maximum time in seconds
|
177
|
-
# @return [nil] if the read time should not be
|
172
|
+
# @return [Numeric] maximum time in seconds
|
173
|
+
# @return [nil] if the read time should not be monitored (default)
|
174
|
+
#
|
175
|
+
# @see TCPClient#read
|
178
176
|
#
|
179
177
|
attr_reader :read_timeout
|
180
178
|
|
@@ -183,7 +181,10 @@ class TCPClient
|
|
183
181
|
end
|
184
182
|
|
185
183
|
#
|
186
|
-
#
|
184
|
+
# The exception class which will be raised if {TCPClient#read} can not be
|
185
|
+
# finished in time.
|
186
|
+
#
|
187
|
+
# @return [Class<Exception>] exception class raised
|
187
188
|
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
188
189
|
#
|
189
190
|
attr_reader :read_timeout_error
|
@@ -194,10 +195,12 @@ class TCPClient
|
|
194
195
|
end
|
195
196
|
|
196
197
|
#
|
197
|
-
#
|
198
|
+
# The maximum time in seconds to write to a connection.
|
198
199
|
#
|
199
|
-
# @return [Numeric] maximum time in seconds
|
200
|
-
# @return [nil] if the write time should not be
|
200
|
+
# @return [Numeric] maximum time in seconds
|
201
|
+
# @return [nil] if the write time should not be monitored (default)
|
202
|
+
#
|
203
|
+
# @see TCPClient#write
|
201
204
|
#
|
202
205
|
attr_reader :write_timeout
|
203
206
|
|
@@ -206,7 +209,10 @@ class TCPClient
|
|
206
209
|
end
|
207
210
|
|
208
211
|
#
|
209
|
-
#
|
212
|
+
# The exception class which will be raised if {TCPClient#write} can not be
|
213
|
+
# finished in time.
|
214
|
+
#
|
215
|
+
# @return [Class<Exception>] exception class raised
|
210
216
|
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
211
217
|
#
|
212
218
|
attr_reader :write_timeout_error
|
@@ -217,48 +223,78 @@ class TCPClient
|
|
217
223
|
end
|
218
224
|
|
219
225
|
#
|
220
|
-
# @attribute
|
221
|
-
#
|
226
|
+
# @attribute [w] timeout
|
227
|
+
# Shorthand to set maximum time in seconds for all timeout monitoring.
|
222
228
|
#
|
223
|
-
|
224
|
-
|
229
|
+
# @return [Numeric] maximum time in seconds for any actwion
|
230
|
+
# @return [nil] if all timeout monitoring should be disabled (default)
|
231
|
+
#
|
232
|
+
# @see #connect_timeout
|
233
|
+
# @see #read_timeout
|
234
|
+
# @see #write_timeout
|
235
|
+
#
|
236
|
+
def timeout=(value)
|
237
|
+
@connect_timeout = @write_timeout = @read_timeout = seconds(value)
|
225
238
|
end
|
226
239
|
|
227
240
|
#
|
228
|
-
#
|
241
|
+
# @attribute [w] timeout_error
|
242
|
+
# Shorthand to set the exception class wich will by raised by any reached
|
243
|
+
# timeout.
|
229
244
|
#
|
230
|
-
# @return [
|
231
|
-
# @return [nil] if no SSL should be used (default)
|
245
|
+
# @return [Class<Exception>] exception class raised
|
232
246
|
#
|
233
|
-
|
247
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
248
|
+
#
|
249
|
+
# @see #connect_timeout_error
|
250
|
+
# @see #read_timeout_error
|
251
|
+
# @see #write_timeout_error
|
252
|
+
#
|
253
|
+
def timeout_error=(value)
|
254
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
255
|
+
@connect_timeout_error =
|
256
|
+
@read_timeout_error = @write_timeout_error = value
|
257
|
+
end
|
234
258
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
259
|
+
# @!endgroup
|
260
|
+
|
261
|
+
#
|
262
|
+
# Enables/disables if network exceptions should be raised as {NetworkError}.
|
263
|
+
#
|
264
|
+
# This allows to handle all network/socket related exceptions like
|
265
|
+
# `SocketError`, `OpenSSL::SSL::SSLError`, `IOError`, etc. in a uniform
|
266
|
+
# manner. If this option is set to true all these error cases are raised as
|
267
|
+
# {NetworkError} and can be easily captured.
|
268
|
+
#
|
269
|
+
# @return [Boolean] wheter all network exceptions should be raised as
|
270
|
+
# {NetworkError}, or not (default)
|
271
|
+
#
|
272
|
+
attr_reader :normalize_network_errors
|
273
|
+
|
274
|
+
def normalize_network_errors=(value)
|
275
|
+
@normalize_network_errors = value ? true : false
|
244
276
|
end
|
245
|
-
alias ssl= ssl_params=
|
246
277
|
|
247
278
|
#
|
248
|
-
#
|
279
|
+
# Convert `self` to a Hash containing all attributes.
|
280
|
+
#
|
281
|
+
# @return [Hash<Symbol, Object>]
|
282
|
+
#
|
283
|
+
# @see #initialize
|
249
284
|
#
|
250
285
|
def to_h
|
251
286
|
{
|
252
287
|
buffered: @buffered,
|
253
288
|
keep_alive: @keep_alive,
|
254
289
|
reverse_lookup: @reverse_lookup,
|
290
|
+
ssl_params: @ssl_params,
|
255
291
|
connect_timeout: @connect_timeout,
|
256
292
|
connect_timeout_error: @connect_timeout_error,
|
257
293
|
read_timeout: @read_timeout,
|
258
294
|
read_timeout_error: @read_timeout_error,
|
259
295
|
write_timeout: @write_timeout,
|
260
296
|
write_timeout_error: @write_timeout_error,
|
261
|
-
|
297
|
+
normalize_network_errors: @normalize_network_errors
|
262
298
|
}
|
263
299
|
end
|
264
300
|
|
data/lib/tcp-client/deadline.rb
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
class TCPClient
|
4
4
|
class Deadline
|
5
|
-
MONOTONIC = defined?(Process::CLOCK_MONOTONIC) ? true : false
|
6
|
-
|
7
5
|
def initialize(timeout)
|
8
6
|
timeout = timeout&.to_f
|
9
7
|
@deadline = timeout&.positive? ? now + timeout : 0
|
@@ -19,7 +17,7 @@ class TCPClient
|
|
19
17
|
|
20
18
|
private
|
21
19
|
|
22
|
-
if
|
20
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
23
21
|
def now
|
24
22
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
25
23
|
end
|
@@ -7,13 +7,17 @@ class TCPClient
|
|
7
7
|
|
8
8
|
class << self
|
9
9
|
#
|
10
|
-
#
|
10
|
+
# The default configuration.
|
11
|
+
# This is used by default if no dedicated configuration was specified to
|
12
|
+
# {.open} or {#connect}.
|
13
|
+
#
|
14
|
+
# @return [Configuration]
|
11
15
|
#
|
12
16
|
attr_reader :default_configuration
|
13
17
|
|
14
18
|
#
|
15
|
-
# Configure the
|
16
|
-
# configuration was specified.
|
19
|
+
# Configure the {.default_configuration} which is used if no dedicated
|
20
|
+
# configuration was specified to {.open} or {#connect}.
|
17
21
|
#
|
18
22
|
# @example
|
19
23
|
# TCPClient.configure do |cfg|
|
@@ -33,11 +37,19 @@ class TCPClient
|
|
33
37
|
end
|
34
38
|
|
35
39
|
class Configuration
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
class << self
|
41
|
+
#
|
42
|
+
# @!parse attr_reader :default
|
43
|
+
# @return [Configuration] used by default if no dedicated configuration
|
44
|
+
# was specified
|
45
|
+
#
|
46
|
+
# @see TCPClient.open
|
47
|
+
# @see TCPClient.with_deadline
|
48
|
+
# @see TCPClient#connect
|
49
|
+
#
|
50
|
+
def default
|
51
|
+
TCPClient.default_configuration
|
52
|
+
end
|
41
53
|
end
|
42
54
|
end
|
43
55
|
end
|
data/lib/tcp-client/errors.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
class TCPClient
|
4
4
|
#
|
5
|
-
# Raised when a SSL connection should be establshed but the OpenSSL gem is
|
5
|
+
# Raised when a SSL connection should be establshed but the OpenSSL gem is
|
6
|
+
# not available.
|
6
7
|
#
|
7
8
|
class NoOpenSSLError < RuntimeError
|
8
9
|
def initialize
|
@@ -11,7 +12,8 @@ class TCPClient
|
|
11
12
|
end
|
12
13
|
|
13
14
|
#
|
14
|
-
# Raised when a method requires a callback block but no such block is
|
15
|
+
# Raised when a method requires a callback block but no such block is
|
16
|
+
# specified.
|
15
17
|
#
|
16
18
|
class NoBlockGivenError < ArgumentError
|
17
19
|
def initialize
|
@@ -20,9 +22,12 @@ class TCPClient
|
|
20
22
|
end
|
21
23
|
|
22
24
|
#
|
23
|
-
# Raised when
|
25
|
+
# Raised when an invalid timeout value was specified.
|
24
26
|
#
|
25
27
|
class InvalidDeadLineError < ArgumentError
|
28
|
+
#
|
29
|
+
# @param timeout [Object] the invalid value
|
30
|
+
#
|
26
31
|
def initialize(timeout)
|
27
32
|
super("invalid deadline - #{timeout}")
|
28
33
|
end
|
@@ -32,6 +37,9 @@ class TCPClient
|
|
32
37
|
# Raised by {Configuration} when an undefined attribute should be set.
|
33
38
|
#
|
34
39
|
class UnknownAttributeError < ArgumentError
|
40
|
+
#
|
41
|
+
# @param attribute [Object] the undefined atttribute
|
42
|
+
#
|
35
43
|
def initialize(attribute)
|
36
44
|
super("unknown attribute - #{attribute}")
|
37
45
|
end
|
@@ -41,6 +49,9 @@ class TCPClient
|
|
41
49
|
# Raised when a given timeout exception parameter is not an exception class.
|
42
50
|
#
|
43
51
|
class NotAnExceptionError < TypeError
|
52
|
+
#
|
53
|
+
# @param object [Object] the invalid object
|
54
|
+
#
|
44
55
|
def initialize(object)
|
45
56
|
super("exception class required - #{object.inspect}")
|
46
57
|
end
|
@@ -49,15 +60,18 @@ class TCPClient
|
|
49
60
|
#
|
50
61
|
# Base exception class for all network related errors.
|
51
62
|
#
|
52
|
-
# Will be raised for any system level network error when
|
63
|
+
# Will be raised for any system level network error when
|
64
|
+
# {Configuration.normalize_network_errors} is configured.
|
53
65
|
#
|
54
|
-
# You should catch this exception class when you like to handle any relevant
|
66
|
+
# You should catch this exception class when you like to handle any relevant
|
67
|
+
# {TCPClient} error.
|
55
68
|
#
|
56
69
|
class NetworkError < StandardError
|
57
70
|
end
|
58
71
|
|
59
72
|
#
|
60
|
-
# Raised when a {TCPClient} instance should read/write from/to the network
|
73
|
+
# Raised when a {TCPClient} instance should read/write from/to the network
|
74
|
+
# but is not connected.
|
61
75
|
#
|
62
76
|
class NotConnectedError < NetworkError
|
63
77
|
def initialize
|
@@ -68,23 +82,26 @@ class TCPClient
|
|
68
82
|
#
|
69
83
|
# Base exception class for a detected timeout.
|
70
84
|
#
|
71
|
-
# You should catch this exception class when you like to handle any timeout
|
85
|
+
# You should catch this exception class when you like to handle any timeout
|
86
|
+
# error.
|
72
87
|
#
|
73
88
|
class TimeoutError < NetworkError
|
74
89
|
#
|
75
90
|
# Initializes the instance with an optional message.
|
76
91
|
#
|
77
|
-
#
|
92
|
+
# The message will be generated from {#action} when not specified.
|
93
|
+
#
|
78
94
|
# @overload initialize
|
79
95
|
# @overload initialize(message)
|
80
96
|
#
|
81
|
-
# @param message [
|
97
|
+
# @param message [#to_s] the error message
|
82
98
|
#
|
83
99
|
def initialize(message = nil)
|
84
100
|
super(message || "unable to #{action} in time")
|
85
101
|
end
|
86
102
|
|
87
103
|
#
|
104
|
+
# @attribute [r] action
|
88
105
|
# @return [Symbol] the action which timed out
|
89
106
|
#
|
90
107
|
def action
|
@@ -97,7 +114,8 @@ class TCPClient
|
|
97
114
|
#
|
98
115
|
class ConnectTimeoutError < TimeoutError
|
99
116
|
#
|
100
|
-
# @
|
117
|
+
# @attribute [r] action
|
118
|
+
# @return [Symbol] the action which timed out: `:connect`
|
101
119
|
#
|
102
120
|
def action
|
103
121
|
:connect
|
@@ -105,11 +123,12 @@ class TCPClient
|
|
105
123
|
end
|
106
124
|
|
107
125
|
#
|
108
|
-
# Raised by default whenever a {TCPClient
|
126
|
+
# Raised by default whenever a {TCPClient#read} timed out.
|
109
127
|
#
|
110
128
|
class ReadTimeoutError < TimeoutError
|
111
129
|
#
|
112
|
-
# @
|
130
|
+
# @attribute [r] action
|
131
|
+
# @return [Symbol] the action which timed out: :read`
|
113
132
|
#
|
114
133
|
def action
|
115
134
|
:read
|
@@ -117,11 +136,12 @@ class TCPClient
|
|
117
136
|
end
|
118
137
|
|
119
138
|
#
|
120
|
-
# Raised by default whenever a {TCPClient
|
139
|
+
# Raised by default whenever a {TCPClient#write} timed out.
|
121
140
|
#
|
122
141
|
class WriteTimeoutError < TimeoutError
|
123
142
|
#
|
124
|
-
# @
|
143
|
+
# @attribute [r] action
|
144
|
+
# @return [Symbol] the action which timed out: `:write`
|
125
145
|
#
|
126
146
|
def action
|
127
147
|
:write
|
data/lib/tcp-client/version.rb
CHANGED
data/lib/tcp-client.rb
CHANGED
@@ -15,7 +15,7 @@ require_relative 'tcp-client/version'
|
|
15
15
|
# All connect/read/write actions can be monitored to ensure that all actions
|
16
16
|
# terminate before given time limits - or raise an exception.
|
17
17
|
#
|
18
|
-
# @example
|
18
|
+
# @example request to Google.com and limit network interactions to 1.5 seconds
|
19
19
|
# TCPClient.with_deadline(1.5, 'www.google.com:443') do |client|
|
20
20
|
# client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
21
21
|
# client.read(12)
|
@@ -26,7 +26,17 @@ require_relative 'tcp-client/version'
|
|
26
26
|
class TCPClient
|
27
27
|
#
|
28
28
|
# Creates a new instance which is connected to the server on the given
|
29
|
-
# address
|
29
|
+
# `address`.
|
30
|
+
#
|
31
|
+
# If no `configuration` is given, the {.default_configuration} will be used.
|
32
|
+
#
|
33
|
+
# @overload open(address, configuration = nil)
|
34
|
+
# @yieldparam client [TCPClient] the connected client
|
35
|
+
#
|
36
|
+
# @return [Object] the block result
|
37
|
+
#
|
38
|
+
# @overload open(address, configuration = nil)
|
39
|
+
# @return [TCPClient] the connected client
|
30
40
|
#
|
31
41
|
# If an optional block is given, then the block's result is returned and the
|
32
42
|
# connection will be closed when the block execution ends.
|
@@ -36,13 +46,10 @@ class TCPClient
|
|
36
46
|
# If no block is giiven the connected client instance is returned.
|
37
47
|
# This can be used as a shorthand to create & connect a client.
|
38
48
|
#
|
39
|
-
# @param address [Address, String, Addrinfo, Integer] the address
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# @yieldreturn [Object] any result
|
44
|
-
#
|
45
|
-
# @return [Object, TCPClient] the block result or the connected client
|
49
|
+
# @param address [Address, String, Addrinfo, Integer] the target address see
|
50
|
+
# {Address#initialize} for valid formats
|
51
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
52
|
+
# the new instance
|
46
53
|
#
|
47
54
|
# @see #connect
|
48
55
|
#
|
@@ -55,24 +62,29 @@ class TCPClient
|
|
55
62
|
end
|
56
63
|
|
57
64
|
#
|
58
|
-
# Yields
|
59
|
-
# address and
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
65
|
+
# Yields an instance which is connected to the server on the given
|
66
|
+
# `address`. It limits all {#read} and {#write} actions within the block to
|
67
|
+
# the given time.
|
68
|
+
#
|
69
|
+
# It ensures to close the connection when the block execution ends and returns
|
70
|
+
# the block`s result.
|
63
71
|
#
|
64
72
|
# This can be used to create an ad-hoc connection which is garanteed to be
|
65
|
-
# closed and which read/write
|
66
|
-
#
|
73
|
+
# closed and which {#read}/{#write} call sequence should not last longer than
|
74
|
+
# the `timeout` seconds.
|
75
|
+
#
|
76
|
+
# If no `configuration` is given, the {.default_configuration} will be used.
|
67
77
|
#
|
68
|
-
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
69
|
-
#
|
70
|
-
# @param
|
78
|
+
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
79
|
+
# {#write} calls within the block
|
80
|
+
# @param address [Address, String, Addrinfo, Integer] the target address see
|
81
|
+
# {Address#initialize} for valid formats
|
82
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
83
|
+
# the instance
|
71
84
|
#
|
72
85
|
# @yieldparam client [TCPClient] the connected client
|
73
|
-
# @yieldreturn [Object] any result
|
74
86
|
#
|
75
|
-
# @return [Object] the block result
|
87
|
+
# @return [Object] the block's result
|
76
88
|
#
|
77
89
|
# @see #with_deadline
|
78
90
|
#
|
@@ -89,89 +101,135 @@ class TCPClient
|
|
89
101
|
end
|
90
102
|
|
91
103
|
#
|
92
|
-
# @return [Address] the address used
|
104
|
+
# @return [Address] the address used by this client instance
|
93
105
|
#
|
94
106
|
attr_reader :address
|
95
107
|
|
96
108
|
#
|
97
|
-
# @return [Configuration] the configuration used by this client
|
109
|
+
# @return [Configuration] the configuration used by this client instance
|
98
110
|
#
|
99
111
|
attr_reader :configuration
|
100
112
|
|
101
113
|
#
|
102
|
-
#
|
103
|
-
# @return [Boolean]
|
114
|
+
# @!parse attr_reader :closed?
|
115
|
+
# @return [Boolean] wheter the connection is closed
|
104
116
|
#
|
105
117
|
def closed?
|
106
118
|
@socket.nil? || @socket.closed?
|
107
119
|
end
|
108
120
|
|
109
121
|
#
|
110
|
-
#
|
122
|
+
# Close the current connection if connected.
|
111
123
|
#
|
112
|
-
# @
|
124
|
+
# @return [self]
|
113
125
|
#
|
114
|
-
def
|
115
|
-
@
|
126
|
+
def close
|
127
|
+
@socket&.close
|
128
|
+
self
|
129
|
+
rescue *NETWORK_ERRORS
|
130
|
+
self
|
131
|
+
ensure
|
132
|
+
@socket = @deadline = nil
|
116
133
|
end
|
117
134
|
|
118
135
|
#
|
119
|
-
# Establishes a new connection to a given address
|
136
|
+
# Establishes a new connection to a given `address`.
|
120
137
|
#
|
121
|
-
# It accepts a connection-specific configuration or uses the
|
122
|
-
#
|
123
|
-
# configure the behavior per connection.
|
138
|
+
# It accepts a connection-specific `configuration` or uses the
|
139
|
+
# {.default_configuration}.
|
124
140
|
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
# @param
|
141
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
142
|
+
# `connect_timeout` and `connect_timeout_error` values.
|
143
|
+
#
|
144
|
+
# @param address [Address, String, Addrinfo, Integer] the target address see
|
145
|
+
# {Address#initialize} for valid formats
|
146
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
147
|
+
# this instance
|
148
|
+
# @param timeout [Numeric] maximum time in seconds to connect
|
149
|
+
# @param exception [Class<Exception>] exception class to be used when the
|
150
|
+
# connect timeout reached
|
129
151
|
#
|
130
152
|
# @return [self]
|
131
153
|
#
|
132
154
|
# @raise {NoOpenSSLError} if SSL should be used but OpenSSL is not avail
|
133
155
|
#
|
156
|
+
# @see NetworkError
|
157
|
+
#
|
134
158
|
def connect(address, configuration = nil, timeout: nil, exception: nil)
|
135
159
|
close if @socket
|
136
|
-
raise(NoOpenSSLError) if configuration.ssl? && !defined?(SSLSocket)
|
137
160
|
@address = Address.new(address)
|
138
161
|
@configuration = (configuration || Configuration.default).dup
|
162
|
+
raise(NoOpenSSLError) if @configuration.ssl? && !defined?(SSLSocket)
|
139
163
|
@socket = create_socket(timeout, exception)
|
140
164
|
self
|
141
165
|
end
|
142
166
|
|
143
167
|
#
|
144
|
-
#
|
168
|
+
# Flushes all internal buffers (write all through).
|
145
169
|
#
|
146
170
|
# @return [self]
|
147
171
|
#
|
148
|
-
def
|
149
|
-
@socket&.
|
150
|
-
self
|
151
|
-
rescue *NETWORK_ERRORS
|
172
|
+
def flush
|
173
|
+
stem_errors { @socket&.flush }
|
152
174
|
self
|
153
|
-
ensure
|
154
|
-
@socket = @deadline = nil
|
155
175
|
end
|
156
176
|
|
157
177
|
#
|
158
|
-
#
|
178
|
+
# Read the given `nbytes` or the next available buffer from server.
|
179
|
+
#
|
180
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
181
|
+
# `read_timeout` and `read_timeout_error` values of the used {#configuration}.
|
182
|
+
#
|
183
|
+
# @param nbytes [Integer] the number of bytes to read
|
184
|
+
# @param timeout [Numeric] maximum time in seconds to read
|
185
|
+
# @param exception [Class<Exception>] exception class to be used when the
|
186
|
+
# read timeout reached
|
187
|
+
#
|
188
|
+
# @return [String] the read buffer
|
189
|
+
#
|
190
|
+
# @raise [NotConnectedError] if {#connect} was not called before
|
191
|
+
#
|
192
|
+
# @see NetworkError
|
193
|
+
#
|
194
|
+
def read(nbytes = nil, timeout: nil, exception: nil)
|
195
|
+
raise(NotConnectedError) if closed?
|
196
|
+
deadline = create_deadline(timeout, configuration.read_timeout)
|
197
|
+
return stem_errors { @socket.read(nbytes) } unless deadline.valid?
|
198
|
+
exception ||= configuration.read_timeout_error
|
199
|
+
stem_errors(exception) do
|
200
|
+
@socket.read_with_deadline(nbytes, deadline, exception)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
#
|
205
|
+
# @return [String] the currently used address as text.
|
159
206
|
#
|
160
|
-
#
|
161
|
-
# with the server is finished before a given amount of time you can use this
|
162
|
-
# method to define such a deadline.
|
207
|
+
# @see Address#to_s
|
163
208
|
#
|
164
|
-
|
209
|
+
def to_s
|
210
|
+
@address&.to_s || ''
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Executes a block with a given overall time limit.
|
215
|
+
#
|
216
|
+
# When you like to ensure that a complete {#read}/{#write} communication
|
217
|
+
# sequence with the server is finished before a given amount of time you use
|
218
|
+
# this method.
|
219
|
+
#
|
220
|
+
# @example ensure to send SMTP welcome message and receive a 4 byte answer
|
165
221
|
# answer = client.with_deadline(2.5) do
|
166
|
-
# client.write('
|
167
|
-
# client.read(
|
222
|
+
# client.write('HELO')
|
223
|
+
# client.read(4)
|
168
224
|
# end
|
225
|
+
# # answer is EHLO when server speaks fluent SMPT
|
169
226
|
#
|
170
|
-
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
227
|
+
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
228
|
+
# {#write} calls within the block
|
171
229
|
#
|
172
230
|
# @yieldparam client [TCPClient] self
|
173
231
|
#
|
174
|
-
# @return [Object]
|
232
|
+
# @return [Object] the block`s result
|
175
233
|
#
|
176
234
|
# @raise [NoBlockGivenError] if the block is missing
|
177
235
|
#
|
@@ -186,32 +244,16 @@ class TCPClient
|
|
186
244
|
end
|
187
245
|
|
188
246
|
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
# @param nbytes [Integer] the number of bytes to read
|
192
|
-
# @param timeout [Numeric] maximum time in seconds to read; used to override the configuration's +read_timeout+.
|
193
|
-
# @param exception [Class] exception class to be used when the read timeout reached; used to override the configuration's +read_timeout_error+.
|
194
|
-
#
|
195
|
-
# @return [String] buffer read
|
196
|
-
#
|
197
|
-
# @raise [NotConnectedError] if {#connect} was not called before
|
247
|
+
# Writes the given `messages` to the server.
|
198
248
|
#
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
return stem_errors { @socket.read(nbytes) } unless deadline.valid?
|
203
|
-
exception ||= configuration.read_timeout_error
|
204
|
-
stem_errors(exception) do
|
205
|
-
@socket.read_with_deadline(nbytes, deadline, exception)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
249
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
250
|
+
# `write_timeout` and `write_timeout_error` values of the used
|
251
|
+
# {#configuration}.
|
209
252
|
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
# @param
|
213
|
-
#
|
214
|
-
# @param exception [Class] exception class to be used when the read timeout reached; used to override the configuration's +write_timeout_error+.
|
253
|
+
# @param messages [Array<String>] one or more messages to write
|
254
|
+
# @param timeout [Numeric] maximum time in seconds to write
|
255
|
+
# @param exception [Class<Exception>] exception class to be used when the
|
256
|
+
# write timeout reached
|
215
257
|
#
|
216
258
|
# @return [Integer] bytes written
|
217
259
|
#
|
@@ -229,16 +271,6 @@ class TCPClient
|
|
229
271
|
end
|
230
272
|
end
|
231
273
|
|
232
|
-
#
|
233
|
-
# Flush all internal buffers (write all through).
|
234
|
-
#
|
235
|
-
# @return [self]
|
236
|
-
#
|
237
|
-
def flush
|
238
|
-
stem_errors { @socket&.flush }
|
239
|
-
self
|
240
|
-
end
|
241
|
-
|
242
274
|
private
|
243
275
|
|
244
276
|
def create_deadline(timeout, default)
|
@@ -277,4 +309,5 @@ class TCPClient
|
|
277
309
|
].tap do |errors|
|
278
310
|
errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
|
279
311
|
end.freeze
|
312
|
+
private_constant(:NETWORK_ERRORS)
|
280
313
|
end
|
data/rakefile.rb
CHANGED
@@ -6,8 +6,7 @@ require 'rspec/core/rake_task'
|
|
6
6
|
require 'yard'
|
7
7
|
|
8
8
|
$stdout.sync = $stderr.sync = true
|
9
|
-
|
10
|
-
CLOBBER << 'prj' << 'doc'
|
9
|
+
CLOBBER << 'prj' << 'doc' << '.yardoc'
|
11
10
|
task(:default) { exec('rake --tasks') }
|
12
11
|
RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
|
13
12
|
YARD::Rake::YardocTask.new { |task| task.stats_options = %w[--list-undoc] }
|
@@ -9,8 +9,8 @@ RSpec.describe TCPClient::Address do
|
|
9
9
|
|
10
10
|
it 'points to the given port on localhost' do
|
11
11
|
expect(address.hostname).to eq 'localhost'
|
12
|
+
expect(address.port).to be 42
|
12
13
|
expect(address.to_s).to eq 'localhost:42'
|
13
|
-
expect(address.addrinfo.ip_port).to be 42
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'uses IPv6' do
|
@@ -29,8 +29,9 @@ RSpec.describe TCPClient::Address do
|
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'points to the given host and port' do
|
32
|
-
expect(address.hostname).to eq
|
33
|
-
expect(address.
|
32
|
+
expect(address.hostname).to eq 'localhost'
|
33
|
+
expect(address.port).to be 42
|
34
|
+
expect(address.to_s).to eq 'localhost:42'
|
34
35
|
end
|
35
36
|
|
36
37
|
it 'uses IPv6' do
|
@@ -46,30 +47,21 @@ RSpec.describe TCPClient::Address do
|
|
46
47
|
|
47
48
|
it 'points to the given host and port' do
|
48
49
|
expect(address.hostname).to eq 'localhost'
|
50
|
+
expect(address.port).to be 42
|
49
51
|
expect(address.to_s).to eq 'localhost:42'
|
50
|
-
expect(address.addrinfo.ip_port).to be 42
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'uses IPv6' do
|
54
52
|
expect(address.addrinfo.ip?).to be true
|
55
|
-
expect(address.addrinfo.ipv6?).to be true
|
56
|
-
expect(address.addrinfo.ipv4?).to be false
|
57
53
|
end
|
54
|
+
|
58
55
|
end
|
59
56
|
|
60
57
|
context 'when only a port is provided' do
|
61
|
-
subject(:address) { TCPClient::Address.new(':
|
58
|
+
subject(:address) { TCPClient::Address.new(':42') }
|
62
59
|
|
63
60
|
it 'points to the given port on localhost' do
|
64
|
-
expect(address.hostname).to eq ''
|
65
|
-
expect(address.
|
66
|
-
expect(address.
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'uses IPv4' do
|
61
|
+
expect(address.hostname).to eq 'localhost'
|
62
|
+
expect(address.port).to be 42
|
63
|
+
expect(address.to_s).to eq 'localhost:42'
|
70
64
|
expect(address.addrinfo.ip?).to be true
|
71
|
-
expect(address.addrinfo.ipv6?).to be false
|
72
|
-
expect(address.addrinfo.ipv4?).to be true
|
73
65
|
end
|
74
66
|
end
|
75
67
|
|
@@ -78,14 +70,9 @@ RSpec.describe TCPClient::Address do
|
|
78
70
|
|
79
71
|
it 'points to the given port on localhost' do
|
80
72
|
expect(address.hostname).to eq '::1'
|
73
|
+
expect(address.port).to be 42
|
81
74
|
expect(address.to_s).to eq '[::1]:42'
|
82
|
-
expect(address.addrinfo.ip_port).to be 42
|
83
|
-
end
|
84
|
-
|
85
|
-
it 'uses IPv6' do
|
86
75
|
expect(address.addrinfo.ip?).to be true
|
87
|
-
expect(address.addrinfo.ipv6?).to be true
|
88
|
-
expect(address.addrinfo.ipv4?).to be false
|
89
76
|
end
|
90
77
|
end
|
91
78
|
end
|
@@ -108,13 +95,13 @@ RSpec.describe TCPClient::Address do
|
|
108
95
|
expect(address_a).to eq address_b
|
109
96
|
end
|
110
97
|
|
111
|
-
context 'using the ==
|
98
|
+
context 'using the == operator' do
|
112
99
|
it 'compares to equal' do
|
113
100
|
expect(address_a == address_b).to be true
|
114
101
|
end
|
115
102
|
end
|
116
103
|
|
117
|
-
context 'using the ===
|
104
|
+
context 'using the === operator' do
|
118
105
|
it 'compares to equal' do
|
119
106
|
expect(address_a === address_b).to be true
|
120
107
|
end
|
@@ -129,13 +116,13 @@ RSpec.describe TCPClient::Address do
|
|
129
116
|
expect(address_a).not_to eq address_b
|
130
117
|
end
|
131
118
|
|
132
|
-
context 'using the ==
|
119
|
+
context 'using the == operator' do
|
133
120
|
it 'compares not to equal' do
|
134
121
|
expect(address_a == address_b).to be false
|
135
122
|
end
|
136
123
|
end
|
137
124
|
|
138
|
-
context 'using the ===
|
125
|
+
context 'using the === operator' do
|
139
126
|
it 'compares not to equal' do
|
140
127
|
expect(address_a === address_b).to be false
|
141
128
|
end
|
@@ -82,7 +82,7 @@ RSpec.describe TCPClient::Configuration do
|
|
82
82
|
expect(configuration.keep_alive).to be false
|
83
83
|
end
|
84
84
|
|
85
|
-
it 'allows to configure reverse address
|
85
|
+
it 'allows to configure reverse address lookup' do
|
86
86
|
expect(configuration.reverse_lookup).to be false
|
87
87
|
end
|
88
88
|
|
@@ -148,7 +148,7 @@ RSpec.describe TCPClient::Configuration do
|
|
148
148
|
end
|
149
149
|
end
|
150
150
|
|
151
|
-
context 'with invalid
|
151
|
+
context 'with invalid attribute' do
|
152
152
|
it 'raises an error' do
|
153
153
|
expect { TCPClient::Configuration.new(invalid: :value) }.to raise_error(
|
154
154
|
TCPClient::UnknownAttributeError
|
@@ -182,6 +182,7 @@ RSpec.describe TCPClient::Configuration do
|
|
182
182
|
read_timeout_error: TCPClient::ReadTimeoutError,
|
183
183
|
write_timeout: 3,
|
184
184
|
write_timeout_error: TCPClient::WriteTimeoutError,
|
185
|
+
normalize_network_errors: false,
|
185
186
|
ssl_params: {
|
186
187
|
min_version: :TLS1_2,
|
187
188
|
max_version: :TLS1_3
|
@@ -232,13 +233,13 @@ RSpec.describe TCPClient::Configuration do
|
|
232
233
|
expect(config_a).to eq config_b
|
233
234
|
end
|
234
235
|
|
235
|
-
context 'using the ==
|
236
|
+
context 'using the == operator' do
|
236
237
|
it 'compares to equal' do
|
237
238
|
expect(config_a == config_b).to be true
|
238
239
|
end
|
239
240
|
end
|
240
241
|
|
241
|
-
context 'using the ===
|
242
|
+
context 'using the === operator' do
|
242
243
|
it 'compares to equal' do
|
243
244
|
expect(config_a === config_b).to be true
|
244
245
|
end
|
@@ -253,13 +254,13 @@ RSpec.describe TCPClient::Configuration do
|
|
253
254
|
expect(config_a).not_to eq config_b
|
254
255
|
end
|
255
256
|
|
256
|
-
context 'using the ==
|
257
|
+
context 'using the == operator' do
|
257
258
|
it 'compares not to equal' do
|
258
259
|
expect(config_a == config_b).to be false
|
259
260
|
end
|
260
261
|
end
|
261
262
|
|
262
|
-
context 'using the ===
|
263
|
+
context 'using the === operator' do
|
263
264
|
it 'compares not to equal' do
|
264
265
|
expect(config_a === config_b).to be false
|
265
266
|
end
|
data/tcp-client.gemspec
CHANGED
@@ -5,12 +5,11 @@ require_relative './lib/tcp-client/version'
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'tcp-client'
|
7
7
|
spec.version = TCPClient::VERSION
|
8
|
-
spec.author = 'Mike Blumtritt'
|
9
|
-
|
10
8
|
spec.required_ruby_version = '>= 2.7.0'
|
11
9
|
|
10
|
+
spec.author = 'Mike Blumtritt'
|
12
11
|
spec.summary = 'A TCP client implementation with working timeout support.'
|
13
|
-
spec.description = <<~
|
12
|
+
spec.description = <<~description
|
14
13
|
This Gem implements a TCP client with (optional) SSL support.
|
15
14
|
It is an easy to use, versatile configurable client that can correctly
|
16
15
|
handle time limits.
|
@@ -18,13 +17,15 @@ Gem::Specification.new do |spec|
|
|
18
17
|
predefined/configurable time limits for each method
|
19
18
|
(`connect`, `read`, `write`). Deadlines for a sequence of read/write
|
20
19
|
actions can also be monitored.
|
21
|
-
|
20
|
+
description
|
21
|
+
|
22
22
|
spec.homepage = 'https://github.com/mblumtritt/tcp-client'
|
23
23
|
spec.license = 'BSD-3-Clause'
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
'https://
|
24
|
+
spec.metadata.merge!(
|
25
|
+
'source_code_uri' => 'https://github.com/mblumtritt/tcp-client',
|
26
|
+
'bug_tracker_uri' => 'https://github.com/mblumtritt/tcp-client/issues',
|
27
|
+
'documentation_uri' => 'https://rubydoc.info/github/mblumtritt/tcp-client'
|
28
|
+
)
|
28
29
|
|
29
30
|
spec.add_development_dependency 'bundler'
|
30
31
|
spec.add_development_dependency 'rake'
|
@@ -34,6 +35,5 @@ Gem::Specification.new do |spec|
|
|
34
35
|
all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
|
35
36
|
spec.test_files = all_files.grep(%r{^spec/})
|
36
37
|
spec.files = all_files - spec.test_files
|
37
|
-
|
38
38
|
spec.extra_rdoc_files = %w[README.md LICENSE]
|
39
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tcp-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Blumtritt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
11
|
+
date: 2021-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -82,6 +82,7 @@ extra_rdoc_files:
|
|
82
82
|
- LICENSE
|
83
83
|
files:
|
84
84
|
- ".gitignore"
|
85
|
+
- ".yardopts"
|
85
86
|
- LICENSE
|
86
87
|
- README.md
|
87
88
|
- gems.rb
|
@@ -112,6 +113,7 @@ licenses:
|
|
112
113
|
metadata:
|
113
114
|
source_code_uri: https://github.com/mblumtritt/tcp-client
|
114
115
|
bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
|
116
|
+
documentation_uri: https://rubydoc.info/github/mblumtritt/tcp-client
|
115
117
|
post_install_message:
|
116
118
|
rdoc_options: []
|
117
119
|
require_paths:
|
@@ -127,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
129
|
- !ruby/object:Gem::Version
|
128
130
|
version: '0'
|
129
131
|
requirements: []
|
130
|
-
rubygems_version: 3.2.
|
132
|
+
rubygems_version: 3.2.32
|
131
133
|
signing_key:
|
132
134
|
specification_version: 4
|
133
135
|
summary: A TCP client implementation with working timeout support.
|