tcp-client 0.9.1 → 0.9.2
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 +4 -5
- data/gems.rb +2 -1
- data/lib/tcp-client/address.rb +14 -7
- data/lib/tcp-client/configuration.rb +121 -80
- data/lib/tcp-client/default_configuration.rb +20 -8
- data/lib/tcp-client/errors.rb +22 -8
- data/lib/tcp-client/version.rb +1 -4
- data/lib/tcp-client.rb +110 -77
- data/rakefile.rb +1 -2
- data/spec/tcp-client/configuration_spec.rb +1 -0
- data/tcp-client.gemspec +9 -11
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d29104bd490679a8fcb517f949e021cd1d2da263f3a60d622790bc2349faffab
|
4
|
+
data.tar.gz: c7ebaf4f9f83eaf68b51bc47bb91fc5be5d446ef3e80b009bc93bca9d3508ef5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e8c282afbefecb35973f7c563cb07782f8dd0e5df760c89d79aa42424784818c5c0e056c40139554642da8b24823aa9fbb7149e0ed7234f40cc51ea5a7a8165
|
7
|
+
data.tar.gz: 2281b62117529f4bbeff55e297b67f8142235772b112d30f9530113e23ab4012242d5eb2fed4e8293a40836455259d37c91403f50714f292ee6e531250e4cc76
|
data/.yardopts
ADDED
data/README.md
CHANGED
@@ -2,16 +2,15 @@
|
|
2
2
|
|
3
3
|
A TCP client implementation with working timeout support.
|
4
4
|
|
5
|
-
|
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
|
+
|
6
9
|
|
7
10
|
## Description
|
8
11
|
|
9
12
|
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.
|
10
13
|
|
11
|
-
## Help
|
12
|
-
|
13
|
-
The latest help can be found at [rubydoc.info](https://rubydoc.info/github/mblumtritt/tcp-client/main/index)
|
14
|
-
|
15
14
|
## Sample
|
16
15
|
|
17
16
|
```ruby
|
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)
|
@@ -22,7 +22,7 @@ class TCPClient
|
|
22
22
|
# @return [Configuration] the initialized configuration
|
23
23
|
#
|
24
24
|
# @overload create(&block)
|
25
|
-
# Shorthand to
|
25
|
+
# Shorthand to create a new configuration within a code block.
|
26
26
|
#
|
27
27
|
# @example
|
28
28
|
# config = TCPClient::Configuration.create do |cfg|
|
@@ -30,14 +30,14 @@ class TCPClient
|
|
30
30
|
# cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
|
31
31
|
# end
|
32
32
|
#
|
33
|
-
# @yieldparam
|
33
|
+
# @yieldparam configuration {Configuration}
|
34
34
|
#
|
35
35
|
# @return [Configuration] the initialized configuration
|
36
36
|
#
|
37
37
|
def self.create(options = {})
|
38
|
-
|
39
|
-
yield(
|
40
|
-
|
38
|
+
configuration = new(options)
|
39
|
+
yield(configuration) if block_given?
|
40
|
+
configuration
|
41
41
|
end
|
42
42
|
|
43
43
|
#
|
@@ -47,14 +47,18 @@ class TCPClient
|
|
47
47
|
# @option options [Boolean] :buffered, see {#buffered}
|
48
48
|
# @option options [Boolean] :keep_alive, see {#keep_alive}
|
49
49
|
# @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
|
50
|
-
# @option options [
|
50
|
+
# @option options [Hash<Symbol, Object>] :ssl_params, see {#ssl_params}
|
51
51
|
# @option options [Numeric] :connect_timeout, see {#connect_timeout}
|
52
|
-
# @option options [Exception] :connect_timeout_error, see
|
52
|
+
# @option options [Exception] :connect_timeout_error, see
|
53
|
+
# {#connect_timeout_error}
|
53
54
|
# @option options [Numeric] :read_timeout, see {#read_timeout}
|
54
55
|
# @option options [Exception] :read_timeout_error, see {#read_timeout_error}
|
55
56
|
# @option options [Numeric] :write_timeout, see {#write_timeout}
|
56
|
-
# @option options [Exception] :write_timeout_error, see
|
57
|
-
#
|
57
|
+
# @option options [Exception] :write_timeout_error, see
|
58
|
+
# {#write_timeout_error}
|
59
|
+
# @option options [Boolean] :normalize_network_errors, see
|
60
|
+
# {#normalize_network_errors}
|
61
|
+
#
|
58
62
|
#
|
59
63
|
def initialize(options = {})
|
60
64
|
@buffered = @keep_alive = @reverse_lookup = true
|
@@ -66,10 +70,13 @@ class TCPClient
|
|
66
70
|
options.each_pair { |attribute, value| set(attribute, value) }
|
67
71
|
end
|
68
72
|
|
73
|
+
# @!group Instance Attributes Socket Level
|
74
|
+
|
69
75
|
#
|
70
|
-
# Enables/disables use of Socket-level
|
76
|
+
# Enables/disables use of Socket-level buffering
|
71
77
|
#
|
72
|
-
# @return [true] if the connection is allowed to use internal buffers
|
78
|
+
# @return [true] if the connection is allowed to use internal buffers
|
79
|
+
# (default)
|
73
80
|
# @return [false] if buffering is not allowed
|
74
81
|
#
|
75
82
|
attr_reader :buffered
|
@@ -81,7 +88,8 @@ class TCPClient
|
|
81
88
|
#
|
82
89
|
# Enables/disables use of Socket-level keep alive handling.
|
83
90
|
#
|
84
|
-
# @return [true] if the connection is allowed to use keep alive signals
|
91
|
+
# @return [true] if the connection is allowed to use keep alive signals
|
92
|
+
# (default)
|
85
93
|
# @return [false] if the connection should not check keep alive
|
86
94
|
#
|
87
95
|
attr_reader :keep_alive
|
@@ -93,7 +101,8 @@ class TCPClient
|
|
93
101
|
#
|
94
102
|
# Enables/disables address lookup.
|
95
103
|
#
|
96
|
-
# @return [true] if the connection is allowed to lookup the address
|
104
|
+
# @return [true] if the connection is allowed to lookup the address
|
105
|
+
# (default)
|
97
106
|
# @return [false] if the address lookup is not required
|
98
107
|
#
|
99
108
|
attr_reader :reverse_lookup
|
@@ -103,55 +112,44 @@ class TCPClient
|
|
103
112
|
end
|
104
113
|
|
105
114
|
#
|
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)
|
115
|
+
# @!parse attr_reader :ssl?
|
116
|
+
# @return [Boolean] wheter SSL is configured, see {#ssl_params}
|
110
117
|
#
|
111
|
-
|
112
|
-
|
113
|
-
def normalize_network_errors=(value)
|
114
|
-
@normalize_network_errors = value ? true : false
|
118
|
+
def ssl?
|
119
|
+
@ssl_params ? true : false
|
115
120
|
end
|
116
121
|
|
117
122
|
#
|
118
|
-
#
|
119
|
-
# Shorthand to set timeout value for connect, read and write at once or to disable any timeout monitoring
|
123
|
+
# Parameters used to initialize a SSL context.
|
120
124
|
#
|
121
|
-
# @return [
|
122
|
-
# @return [nil] if
|
123
|
-
#
|
124
|
-
# @see #connect_timeout
|
125
|
-
# @see #read_timeout
|
126
|
-
# @see #write_timeout
|
125
|
+
# @return [Hash<Symbol, Object>] SSL parameters for the SSL context
|
126
|
+
# @return [nil] if no SSL should be used (default)
|
127
127
|
#
|
128
|
-
|
129
|
-
@connect_timeout = @write_timeout = @read_timeout = seconds(value)
|
130
|
-
end
|
128
|
+
attr_reader :ssl_params
|
131
129
|
|
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
|
130
|
+
def ssl_params=(value)
|
131
|
+
@ssl_params =
|
132
|
+
if value.respond_to?(:to_hash)
|
133
|
+
Hash[value.to_hash]
|
134
|
+
elsif value.respond_to?(:to_h)
|
135
|
+
Hash[value.to_h]
|
136
|
+
else
|
137
|
+
value ? {} : nil
|
138
|
+
end
|
148
139
|
end
|
140
|
+
alias ssl= ssl_params=
|
141
|
+
|
142
|
+
# @!endgroup
|
149
143
|
|
144
|
+
# @!group Instance Attributes Timeout Monitoring
|
145
|
+
|
146
|
+
#
|
147
|
+
# The maximum time in seconds to establish a connection.
|
150
148
|
#
|
151
|
-
#
|
149
|
+
# @return [Numeric] maximum time in seconds
|
150
|
+
# @return [nil] if the connect time should not be monitored (default)
|
152
151
|
#
|
153
|
-
# @
|
154
|
-
# @return [nil] if the connect time should not be checked (default)
|
152
|
+
# @see TCPClient#connect
|
155
153
|
#
|
156
154
|
attr_reader :connect_timeout
|
157
155
|
|
@@ -160,7 +158,10 @@ class TCPClient
|
|
160
158
|
end
|
161
159
|
|
162
160
|
#
|
163
|
-
#
|
161
|
+
# The exception class which will be raised if {TCPClient#connect} can not
|
162
|
+
# be finished in time.
|
163
|
+
#
|
164
|
+
# @return [Class] exception class raised
|
164
165
|
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
165
166
|
#
|
166
167
|
attr_reader :connect_timeout_error
|
@@ -171,10 +172,12 @@ class TCPClient
|
|
171
172
|
end
|
172
173
|
|
173
174
|
#
|
174
|
-
#
|
175
|
+
# The maximum time in seconds to read from a connection.
|
176
|
+
#
|
177
|
+
# @return [Numeric] maximum time in seconds
|
178
|
+
# @return [nil] if the read time should not be monitored (default)
|
175
179
|
#
|
176
|
-
# @
|
177
|
-
# @return [nil] if the read time should not be checked (default)
|
180
|
+
# @see TCPClient#read
|
178
181
|
#
|
179
182
|
attr_reader :read_timeout
|
180
183
|
|
@@ -183,7 +186,10 @@ class TCPClient
|
|
183
186
|
end
|
184
187
|
|
185
188
|
#
|
186
|
-
#
|
189
|
+
# The exception class which will be raised if {TCPClient#read} can not be
|
190
|
+
# finished in time.
|
191
|
+
#
|
192
|
+
# @return [Class] exception class raised
|
187
193
|
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
188
194
|
#
|
189
195
|
attr_reader :read_timeout_error
|
@@ -194,10 +200,12 @@ class TCPClient
|
|
194
200
|
end
|
195
201
|
|
196
202
|
#
|
197
|
-
#
|
203
|
+
# The maximum time in seconds to write to a connection.
|
198
204
|
#
|
199
|
-
# @return [Numeric] maximum time in seconds
|
200
|
-
# @return [nil] if the write time should not be
|
205
|
+
# @return [Numeric] maximum time in seconds
|
206
|
+
# @return [nil] if the write time should not be monitored (default)
|
207
|
+
#
|
208
|
+
# @see TCPClient#write
|
201
209
|
#
|
202
210
|
attr_reader :write_timeout
|
203
211
|
|
@@ -206,7 +214,10 @@ class TCPClient
|
|
206
214
|
end
|
207
215
|
|
208
216
|
#
|
209
|
-
#
|
217
|
+
# The exception class which will be raised if {TCPClient#write} can not be
|
218
|
+
# finished in time.
|
219
|
+
#
|
220
|
+
# @return [Class] exception class raised
|
210
221
|
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
211
222
|
#
|
212
223
|
attr_reader :write_timeout_error
|
@@ -217,48 +228,78 @@ class TCPClient
|
|
217
228
|
end
|
218
229
|
|
219
230
|
#
|
220
|
-
# @attribute
|
221
|
-
#
|
231
|
+
# @attribute [w] timeout
|
232
|
+
# Shorthand to set maximum time in seconds for all timeut monitoring.
|
222
233
|
#
|
223
|
-
|
224
|
-
|
234
|
+
# @return [Numeric] maximum time in seconds for any actwion
|
235
|
+
# @return [nil] if all timeout monitoring should be disabled (default)
|
236
|
+
#
|
237
|
+
# @see #connect_timeout
|
238
|
+
# @see #read_timeout
|
239
|
+
# @see #write_timeout
|
240
|
+
#
|
241
|
+
def timeout=(value)
|
242
|
+
@connect_timeout = @write_timeout = @read_timeout = seconds(value)
|
225
243
|
end
|
226
244
|
|
227
245
|
#
|
228
|
-
#
|
246
|
+
# @attribute [w] timeout_error
|
247
|
+
# Shorthand to set the exception class wich will by raised by any timeut.
|
229
248
|
#
|
230
|
-
# @return [
|
231
|
-
# @return [nil] if no SSL should be used (default)
|
249
|
+
# @return [Class] exception class raised
|
232
250
|
#
|
233
|
-
|
251
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
252
|
+
#
|
253
|
+
# @see #connect_timeout_error
|
254
|
+
# @see #read_timeout_error
|
255
|
+
# @see #write_timeout_error
|
256
|
+
#
|
257
|
+
def timeout_error=(value)
|
258
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
259
|
+
@connect_timeout_error =
|
260
|
+
@read_timeout_error = @write_timeout_error = value
|
261
|
+
end
|
234
262
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
263
|
+
# @!endgroup
|
264
|
+
|
265
|
+
#
|
266
|
+
# Enables/disables if network exceptions should be raised as {NetworkError}.
|
267
|
+
#
|
268
|
+
# This allows to handle all network/socket related exceptions like
|
269
|
+
# `SocketError`, `OpenSSL::SSL::SSLError`, `IOError`, etc. in a uniform
|
270
|
+
# manner. If this option is set to true all these error cases are raised as
|
271
|
+
# {NetworkError} and can be easily captured.
|
272
|
+
#
|
273
|
+
# @return [true] if all network exceptions should be raised as
|
274
|
+
# {NetworkError}
|
275
|
+
# @return [false] if socket/system errors should not be normalzed (default)
|
276
|
+
#
|
277
|
+
attr_reader :normalize_network_errors
|
278
|
+
|
279
|
+
def normalize_network_errors=(value)
|
280
|
+
@normalize_network_errors = value ? true : false
|
244
281
|
end
|
245
|
-
alias ssl= ssl_params=
|
246
282
|
|
247
283
|
#
|
248
|
-
#
|
284
|
+
# Convert `self` to a Hash containing all attributes.
|
285
|
+
#
|
286
|
+
# @return [Hash<Symbol, Object>]
|
287
|
+
#
|
288
|
+
# @see #initialize
|
249
289
|
#
|
250
290
|
def to_h
|
251
291
|
{
|
252
292
|
buffered: @buffered,
|
253
293
|
keep_alive: @keep_alive,
|
254
294
|
reverse_lookup: @reverse_lookup,
|
295
|
+
ssl_params: @ssl_params,
|
255
296
|
connect_timeout: @connect_timeout,
|
256
297
|
connect_timeout_error: @connect_timeout_error,
|
257
298
|
read_timeout: @read_timeout,
|
258
299
|
read_timeout_error: @read_timeout_error,
|
259
300
|
write_timeout: @write_timeout,
|
260
301
|
write_timeout_error: @write_timeout_error,
|
261
|
-
|
302
|
+
normalize_network_errors: @normalize_network_errors
|
262
303
|
}
|
263
304
|
end
|
264
305
|
|
@@ -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
@@ -20,9 +20,12 @@ class TCPClient
|
|
20
20
|
end
|
21
21
|
|
22
22
|
#
|
23
|
-
# Raised when
|
23
|
+
# Raised when an invalid timeout value was specified.
|
24
24
|
#
|
25
25
|
class InvalidDeadLineError < ArgumentError
|
26
|
+
#
|
27
|
+
# @param timeout [Object] the invalid value
|
28
|
+
#
|
26
29
|
def initialize(timeout)
|
27
30
|
super("invalid deadline - #{timeout}")
|
28
31
|
end
|
@@ -32,6 +35,9 @@ class TCPClient
|
|
32
35
|
# Raised by {Configuration} when an undefined attribute should be set.
|
33
36
|
#
|
34
37
|
class UnknownAttributeError < ArgumentError
|
38
|
+
#
|
39
|
+
# @param attribute [Object] the undefined atttribute
|
40
|
+
#
|
35
41
|
def initialize(attribute)
|
36
42
|
super("unknown attribute - #{attribute}")
|
37
43
|
end
|
@@ -41,6 +47,9 @@ class TCPClient
|
|
41
47
|
# Raised when a given timeout exception parameter is not an exception class.
|
42
48
|
#
|
43
49
|
class NotAnExceptionError < TypeError
|
50
|
+
#
|
51
|
+
# @param object [Object] the invalid object
|
52
|
+
#
|
44
53
|
def initialize(object)
|
45
54
|
super("exception class required - #{object.inspect}")
|
46
55
|
end
|
@@ -74,17 +83,19 @@ class TCPClient
|
|
74
83
|
#
|
75
84
|
# Initializes the instance with an optional message.
|
76
85
|
#
|
77
|
-
#
|
86
|
+
# The message will be generated from {#action} when not specified.
|
87
|
+
#
|
78
88
|
# @overload initialize
|
79
89
|
# @overload initialize(message)
|
80
90
|
#
|
81
|
-
# @param message [
|
91
|
+
# @param message [#to_s] the error message
|
82
92
|
#
|
83
93
|
def initialize(message = nil)
|
84
94
|
super(message || "unable to #{action} in time")
|
85
95
|
end
|
86
96
|
|
87
97
|
#
|
98
|
+
# @attribute [r] action
|
88
99
|
# @return [Symbol] the action which timed out
|
89
100
|
#
|
90
101
|
def action
|
@@ -97,7 +108,8 @@ class TCPClient
|
|
97
108
|
#
|
98
109
|
class ConnectTimeoutError < TimeoutError
|
99
110
|
#
|
100
|
-
# @
|
111
|
+
# @attribute [r] action
|
112
|
+
# @return [Symbol] the action which timed out: `:connect`
|
101
113
|
#
|
102
114
|
def action
|
103
115
|
:connect
|
@@ -105,11 +117,12 @@ class TCPClient
|
|
105
117
|
end
|
106
118
|
|
107
119
|
#
|
108
|
-
# Raised by default whenever a {TCPClient
|
120
|
+
# Raised by default whenever a {TCPClient#read} timed out.
|
109
121
|
#
|
110
122
|
class ReadTimeoutError < TimeoutError
|
111
123
|
#
|
112
|
-
# @
|
124
|
+
# @attribute [r] action
|
125
|
+
# @return [Symbol] the action which timed out: :read`
|
113
126
|
#
|
114
127
|
def action
|
115
128
|
:read
|
@@ -117,11 +130,12 @@ class TCPClient
|
|
117
130
|
end
|
118
131
|
|
119
132
|
#
|
120
|
-
# Raised by default whenever a {TCPClient
|
133
|
+
# Raised by default whenever a {TCPClient#write} timed out.
|
121
134
|
#
|
122
135
|
class WriteTimeoutError < TimeoutError
|
123
136
|
#
|
124
|
-
# @
|
137
|
+
# @attribute [r] action
|
138
|
+
# @return [Symbol] the action which timed out: `:write`
|
125
139
|
#
|
126
140
|
def action
|
127
141
|
: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,9 @@ 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.
|
30
32
|
#
|
31
33
|
# If an optional block is given, then the block's result is returned and the
|
32
34
|
# connection will be closed when the block execution ends.
|
@@ -36,8 +38,10 @@ class TCPClient
|
|
36
38
|
# If no block is giiven the connected client instance is returned.
|
37
39
|
# This can be used as a shorthand to create & connect a client.
|
38
40
|
#
|
39
|
-
# @param address [Address, String, Addrinfo, Integer] the address to connect
|
40
|
-
#
|
41
|
+
# @param address [Address, String, Addrinfo, Integer] the address to connect
|
42
|
+
# to, see {Address#initialize} for valid formats
|
43
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
44
|
+
# this instance
|
41
45
|
#
|
42
46
|
# @yieldparam client [TCPClient] the connected client
|
43
47
|
# @yieldreturn [Object] any result
|
@@ -56,23 +60,29 @@ class TCPClient
|
|
56
60
|
|
57
61
|
#
|
58
62
|
# Yields a new instance which is connected to the server on the given
|
59
|
-
# address and
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
+
# `address`.It limits all {#read} and {#write} actions within the block to
|
64
|
+
# the given time.
|
65
|
+
#
|
66
|
+
# It ensures to close the connection when the block execution ends and returns
|
67
|
+
# the block`s result.
|
63
68
|
#
|
64
69
|
# This can be used to create an ad-hoc connection which is garanteed to be
|
65
|
-
# closed and which read/write
|
66
|
-
#
|
70
|
+
# closed and which {#read}/{#write} call sequence should not last longer than
|
71
|
+
# the `timeout`.
|
72
|
+
#
|
73
|
+
# If no `configuration` is given, the {.default_configuration} will be used.
|
67
74
|
#
|
68
|
-
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
69
|
-
#
|
70
|
-
# @param
|
75
|
+
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
76
|
+
# {#write} calls within the block
|
77
|
+
# @param address [Address, String, Addrinfo, Integer] the address to connect
|
78
|
+
# to, see {Address#initialize} for valid formats
|
79
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
80
|
+
# this instance
|
71
81
|
#
|
72
82
|
# @yieldparam client [TCPClient] the connected client
|
73
83
|
# @yieldreturn [Object] any result
|
74
84
|
#
|
75
|
-
# @return [Object] the block result
|
85
|
+
# @return [Object] the block's result
|
76
86
|
#
|
77
87
|
# @see #with_deadline
|
78
88
|
#
|
@@ -89,17 +99,17 @@ class TCPClient
|
|
89
99
|
end
|
90
100
|
|
91
101
|
#
|
92
|
-
# @return [Address] the address used
|
102
|
+
# @return [Address] the address used by this client instance
|
93
103
|
#
|
94
104
|
attr_reader :address
|
95
105
|
|
96
106
|
#
|
97
|
-
# @return [Configuration] the configuration used by this client
|
107
|
+
# @return [Configuration] the configuration used by this client instance
|
98
108
|
#
|
99
109
|
attr_reader :configuration
|
100
110
|
|
101
111
|
#
|
102
|
-
#
|
112
|
+
# @!parse attr_reader :closed?
|
103
113
|
# @return [Boolean] true when the connection is closed, false when connected
|
104
114
|
#
|
105
115
|
def closed?
|
@@ -107,30 +117,44 @@ class TCPClient
|
|
107
117
|
end
|
108
118
|
|
109
119
|
#
|
110
|
-
#
|
120
|
+
# Close the current connection if connected.
|
111
121
|
#
|
112
|
-
# @
|
122
|
+
# @return [self]
|
113
123
|
#
|
114
|
-
def
|
115
|
-
@
|
124
|
+
def close
|
125
|
+
@socket&.close
|
126
|
+
self
|
127
|
+
rescue *NETWORK_ERRORS
|
128
|
+
self
|
129
|
+
ensure
|
130
|
+
@socket = @deadline = nil
|
116
131
|
end
|
117
132
|
|
118
133
|
#
|
119
|
-
# Establishes a new connection to a given address
|
134
|
+
# Establishes a new connection to a given `address`.
|
120
135
|
#
|
121
|
-
# It accepts a connection-specific configuration or uses the
|
136
|
+
# It accepts a connection-specific configuration or uses the
|
137
|
+
# {.default_configuration}. The {#configuration} used by this instance will
|
122
138
|
# be a copy of the configuration used for this method call. This allows to
|
123
139
|
# configure the behavior per connection.
|
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 address to connect
|
145
|
+
# to, see {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 class to be used when the connect timeout
|
150
|
+
# 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
160
|
@address = Address.new(address)
|
@@ -141,37 +165,72 @@ class TCPClient
|
|
141
165
|
end
|
142
166
|
|
143
167
|
#
|
144
|
-
#
|
168
|
+
# Flush 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 class to be used when the read timeout
|
186
|
+
# reached
|
187
|
+
#
|
188
|
+
# @return [String] the read buffer
|
189
|
+
#
|
190
|
+
# @raise [NotConnectedError] if {#connect} was not called before
|
159
191
|
#
|
160
|
-
#
|
161
|
-
# with the server is finished before a given amount of time you can use this
|
162
|
-
# method to define such a deadline.
|
192
|
+
# @see NetworkError
|
163
193
|
#
|
164
|
-
|
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.
|
206
|
+
#
|
207
|
+
# @see Address#to_s
|
208
|
+
#
|
209
|
+
def to_s
|
210
|
+
@address&.to_s || ''
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Execute 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
|
231
|
+
# @yieldreturn [Object] any result
|
173
232
|
#
|
174
|
-
# @return [Object]
|
233
|
+
# @return [Object] the block`s result
|
175
234
|
#
|
176
235
|
# @raise [NoBlockGivenError] if the block is missing
|
177
236
|
#
|
@@ -186,32 +245,16 @@ class TCPClient
|
|
186
245
|
end
|
187
246
|
|
188
247
|
#
|
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
|
198
|
-
#
|
199
|
-
def read(nbytes = nil, timeout: nil, exception: nil)
|
200
|
-
raise(NotConnectedError) if closed?
|
201
|
-
deadline = create_deadline(timeout, configuration.read_timeout)
|
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
|
-
|
248
|
+
# Write the given `messages` to the server.
|
209
249
|
#
|
210
|
-
#
|
250
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
251
|
+
# `write_timeout` and `write_timeout_error` values of the used
|
252
|
+
# {#configuration}.
|
211
253
|
#
|
212
|
-
# @param messages [
|
213
|
-
# @param timeout [Numeric] maximum time in seconds to
|
214
|
-
# @param exception [Class] exception class to be used when the
|
254
|
+
# @param messages [String] one or more messages to write
|
255
|
+
# @param timeout [Numeric] maximum time in seconds to write
|
256
|
+
# @param exception [Class] exception class to be used when the write timeout
|
257
|
+
# reached
|
215
258
|
#
|
216
259
|
# @return [Integer] bytes written
|
217
260
|
#
|
@@ -229,16 +272,6 @@ class TCPClient
|
|
229
272
|
end
|
230
273
|
end
|
231
274
|
|
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
275
|
private
|
243
276
|
|
244
277
|
def create_deadline(timeout, default)
|
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] }
|
@@ -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
|
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,15 +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://rubydoc.info/github/mblumtritt/tcp-client'
|
28
|
-
|
29
|
-
'https://github.com/mblumtritt/tcp-client/issues'
|
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
|
+
)
|
30
29
|
|
31
30
|
spec.add_development_dependency 'bundler'
|
32
31
|
spec.add_development_dependency 'rake'
|
@@ -36,6 +35,5 @@ Gem::Specification.new do |spec|
|
|
36
35
|
all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
|
37
36
|
spec.test_files = all_files.grep(%r{^spec/})
|
38
37
|
spec.files = all_files - spec.test_files
|
39
|
-
|
40
38
|
spec.extra_rdoc_files = %w[README.md LICENSE]
|
41
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.2
|
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-04 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
|
@@ -111,8 +112,8 @@ licenses:
|
|
111
112
|
- BSD-3-Clause
|
112
113
|
metadata:
|
113
114
|
source_code_uri: https://github.com/mblumtritt/tcp-client
|
114
|
-
documentation_uri: https://rubydoc.info/github/mblumtritt/tcp-client
|
115
115
|
bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
|
116
|
+
documentation_uri: https://rubydoc.info/github/mblumtritt/tcp-client
|
116
117
|
post_install_message:
|
117
118
|
rdoc_options: []
|
118
119
|
require_paths:
|