tcp-client 0.8.0 → 0.9.3
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/.gitignore +3 -1
- data/.yardopts +5 -0
- data/README.md +7 -1
- data/gems.rb +2 -1
- data/lib/tcp-client/address.rb +53 -7
- data/lib/tcp-client/configuration.rb +250 -60
- data/lib/tcp-client/default_configuration.rb +36 -2
- data/lib/tcp-client/errors.rb +82 -7
- data/lib/tcp-client/mixin/io_with_deadline.rb +2 -1
- data/lib/tcp-client/version.rb +1 -1
- data/lib/tcp-client.rb +207 -29
- data/rakefile.rb +3 -4
- data/spec/tcp-client/address_spec.rb +0 -13
- data/spec/tcp-client/configuration_spec.rb +3 -41
- data/tcp-client.gemspec +10 -9
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75811fed2b0735c2cac6a4e9671e138df88e6b8ada2be03a0d86b4b83b37b863
|
4
|
+
data.tar.gz: 9c715081912711178a0038d9af9832cf6c45e2620c2cff6dad899565de86be02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2842f5bd4fa2806c15bb94d6c1da9a897be40959a5db9033f404f1c980648a997daee0d49ad1fb75e4cf680644f72dfb04e880eb0c9e9ba9207d6ee3dd75240
|
7
|
+
data.tar.gz: 174878bab1414b1ef49bd67a1f296d21e463bd7d817442340192aace8af88e56d941b647ad43ff0ed5126e2c2ab16e0159c0467dd7a08289d93c423ca520e2cb
|
data/.gitignore
CHANGED
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,7 +34,9 @@ TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
|
|
30
34
|
end
|
31
35
|
```
|
32
36
|
|
33
|
-
|
37
|
+
For more samples see [the samples dir](https://github.com/mblumtritt/tcp-client/tree/main/sample)
|
38
|
+
|
39
|
+
## Installation
|
34
40
|
|
35
41
|
Use [Bundler](http://gembundler.com/) to use TCPClient in your own project:
|
36
42
|
|
data/gems.rb
CHANGED
data/lib/tcp-client/address.rb
CHANGED
@@ -3,9 +3,51 @@
|
|
3
3
|
require 'socket'
|
4
4
|
|
5
5
|
class TCPClient
|
6
|
+
#
|
7
|
+
# The address used by a TCPClient
|
8
|
+
#
|
6
9
|
class Address
|
7
|
-
|
10
|
+
#
|
11
|
+
# @return [String] the host name
|
12
|
+
#
|
13
|
+
attr_reader :hostname
|
8
14
|
|
15
|
+
#
|
16
|
+
# @return [Addrinfo] the address info
|
17
|
+
#
|
18
|
+
attr_reader :addrinfo
|
19
|
+
|
20
|
+
#
|
21
|
+
# Initializes an address
|
22
|
+
# @overload initialize(addr)
|
23
|
+
# The addr can be specified as
|
24
|
+
#
|
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
|
+
#
|
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
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# @overload initialize(addrinfo)
|
37
|
+
#
|
38
|
+
# @example create an Address with an Addrinfo
|
39
|
+
# Address.new(Addrinfo.tcp('www.google.com', 'http'))
|
40
|
+
#
|
41
|
+
# @param addrinfo [Addrinfo] containing the addressed host and port
|
42
|
+
#
|
43
|
+
# @overload initialize(port)
|
44
|
+
# Adresses the port on the local machine.
|
45
|
+
#
|
46
|
+
# @example create an Address for localhost on port 80
|
47
|
+
# Address.new(80)
|
48
|
+
#
|
49
|
+
# @param port [Integer] the addressed port
|
50
|
+
#
|
9
51
|
def initialize(addr)
|
10
52
|
case addr
|
11
53
|
when self.class
|
@@ -20,24 +62,28 @@ class TCPClient
|
|
20
62
|
@addrinfo.freeze
|
21
63
|
end
|
22
64
|
|
65
|
+
#
|
66
|
+
# @return [String] text representation of self as "host:port"
|
67
|
+
#
|
23
68
|
def to_s
|
24
69
|
return "[#{@hostname}]:#{@addrinfo.ip_port}" if @hostname.index(':') # IP6
|
25
70
|
"#{@hostname}:#{@addrinfo.ip_port}"
|
26
71
|
end
|
27
72
|
|
28
|
-
|
73
|
+
#
|
74
|
+
# @return [Hash] containing the host and port
|
75
|
+
#
|
76
|
+
def to_h
|
29
77
|
{ host: @hostname, port: @addrinfo.ip_port }
|
30
78
|
end
|
31
79
|
|
32
|
-
|
33
|
-
args.empty? ? to_hash : to_hash.slice(*args)
|
34
|
-
end
|
35
|
-
|
80
|
+
# @!visibility private
|
36
81
|
def ==(other)
|
37
|
-
|
82
|
+
to_h == other.to_h
|
38
83
|
end
|
39
84
|
alias eql? ==
|
40
85
|
|
86
|
+
# @!visibility private
|
41
87
|
def equal?(other)
|
42
88
|
self.class == other.class && self == other
|
43
89
|
end
|
@@ -3,25 +3,63 @@
|
|
3
3
|
require_relative 'errors'
|
4
4
|
|
5
5
|
class TCPClient
|
6
|
+
#
|
7
|
+
# A Configuration is used to configure the behavior of a {TCPClient} instance.
|
8
|
+
#
|
9
|
+
# It allows to specify to monitor timeout, how to handle exceptions, if SSL
|
10
|
+
# should be used and to setup the underlying Socket.
|
11
|
+
#
|
6
12
|
class Configuration
|
13
|
+
#
|
14
|
+
# @overload create(options)
|
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 create a new configuration within a code block.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# config = TCPClient::Configuration.create do |cfg|
|
29
|
+
# cfg.buffered = false
|
30
|
+
# cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @yieldparam configuration {Configuration}
|
34
|
+
#
|
35
|
+
# @return [Configuration] the initialized configuration
|
36
|
+
#
|
7
37
|
def self.create(options = {})
|
8
|
-
|
9
|
-
yield(
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
attr_reader :buffered,
|
14
|
-
:keep_alive,
|
15
|
-
:reverse_lookup,
|
16
|
-
:normalize_network_errors,
|
17
|
-
:connect_timeout,
|
18
|
-
:read_timeout,
|
19
|
-
:write_timeout,
|
20
|
-
:connect_timeout_error,
|
21
|
-
:read_timeout_error,
|
22
|
-
:write_timeout_error
|
23
|
-
attr_accessor :ssl_params
|
38
|
+
configuration = new(options)
|
39
|
+
yield(configuration) if block_given?
|
40
|
+
configuration
|
41
|
+
end
|
24
42
|
|
43
|
+
#
|
44
|
+
# Intializes the instance with given options.
|
45
|
+
#
|
46
|
+
# @param options [Hash]
|
47
|
+
# @option options [Boolean] :buffered, see {#buffered}
|
48
|
+
# @option options [Boolean] :keep_alive, see {#keep_alive}
|
49
|
+
# @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
|
50
|
+
# @option options [Hash<Symbol, Object>] :ssl_params, see {#ssl_params}
|
51
|
+
# @option options [Numeric] :connect_timeout, see {#connect_timeout}
|
52
|
+
# @option options [Exception] :connect_timeout_error, see
|
53
|
+
# {#connect_timeout_error}
|
54
|
+
# @option options [Numeric] :read_timeout, see {#read_timeout}
|
55
|
+
# @option options [Exception] :read_timeout_error, see {#read_timeout_error}
|
56
|
+
# @option options [Numeric] :write_timeout, see {#write_timeout}
|
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
|
+
#
|
62
|
+
#
|
25
63
|
def initialize(options = {})
|
26
64
|
@buffered = @keep_alive = @reverse_lookup = true
|
27
65
|
self.timeout = @ssl_params = nil
|
@@ -32,107 +70,259 @@ class TCPClient
|
|
32
70
|
options.each_pair { |attribute, value| set(attribute, value) }
|
33
71
|
end
|
34
72
|
|
35
|
-
|
36
|
-
|
37
|
-
|
73
|
+
# @!group Instance Attributes Socket Level
|
74
|
+
|
75
|
+
#
|
76
|
+
# Enables/disables use of Socket-level buffering
|
77
|
+
#
|
78
|
+
# @return [true] if the connection is allowed to use internal buffers
|
79
|
+
# (default)
|
80
|
+
# @return [false] if buffering is not allowed
|
81
|
+
#
|
82
|
+
attr_reader :buffered
|
83
|
+
|
84
|
+
def buffered=(value)
|
85
|
+
@buffered = value ? true : false
|
38
86
|
end
|
39
87
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
88
|
+
#
|
89
|
+
# Enables/disables use of Socket-level keep alive handling.
|
90
|
+
#
|
91
|
+
# @return [true] if the connection is allowed to use keep alive signals
|
92
|
+
# (default)
|
93
|
+
# @return [false] if the connection should not check keep alive
|
94
|
+
#
|
95
|
+
attr_reader :keep_alive
|
96
|
+
|
97
|
+
def keep_alive=(value)
|
98
|
+
@keep_alive = value ? true : false
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Enables/disables address lookup.
|
103
|
+
#
|
104
|
+
# @return [true] if the connection is allowed to lookup the address
|
105
|
+
# (default)
|
106
|
+
# @return [false] if the address lookup is not required
|
107
|
+
#
|
108
|
+
attr_reader :reverse_lookup
|
109
|
+
|
110
|
+
def reverse_lookup=(value)
|
111
|
+
@reverse_lookup = value ? true : false
|
44
112
|
end
|
45
113
|
|
114
|
+
#
|
115
|
+
# @!parse attr_reader :ssl?
|
116
|
+
# @return [Boolean] wheter SSL is configured, see {#ssl_params}
|
117
|
+
#
|
46
118
|
def ssl?
|
47
119
|
@ssl_params ? true : false
|
48
120
|
end
|
49
121
|
|
50
|
-
|
122
|
+
#
|
123
|
+
# Parameters used to initialize a SSL context.
|
124
|
+
#
|
125
|
+
# @return [Hash<Symbol, Object>] SSL parameters for the SSL context
|
126
|
+
# @return [nil] if no SSL should be used (default)
|
127
|
+
#
|
128
|
+
attr_reader :ssl_params
|
129
|
+
|
130
|
+
def ssl_params=(value)
|
51
131
|
@ssl_params =
|
52
132
|
if value.respond_to?(:to_hash)
|
53
133
|
Hash[value.to_hash]
|
134
|
+
elsif value.respond_to?(:to_h)
|
135
|
+
value.nil? ? nil : Hash[value.to_h]
|
54
136
|
else
|
55
137
|
value ? {} : nil
|
56
138
|
end
|
57
139
|
end
|
140
|
+
alias ssl= ssl_params=
|
58
141
|
|
59
|
-
|
60
|
-
@buffered = value ? true : false
|
61
|
-
end
|
142
|
+
# @!endgroup
|
62
143
|
|
63
|
-
|
64
|
-
|
144
|
+
# @!group Instance Attributes Timeout Monitoring
|
145
|
+
|
146
|
+
#
|
147
|
+
# The maximum time in seconds to establish a connection.
|
148
|
+
#
|
149
|
+
# @return [Numeric] maximum time in seconds
|
150
|
+
# @return [nil] if the connect time should not be monitored (default)
|
151
|
+
#
|
152
|
+
# @see TCPClient#connect
|
153
|
+
#
|
154
|
+
attr_reader :connect_timeout
|
155
|
+
|
156
|
+
def connect_timeout=(value)
|
157
|
+
@connect_timeout = seconds(value)
|
65
158
|
end
|
66
159
|
|
67
|
-
|
68
|
-
|
160
|
+
#
|
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
|
165
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
166
|
+
#
|
167
|
+
attr_reader :connect_timeout_error
|
168
|
+
|
169
|
+
def connect_timeout_error=(value)
|
170
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
171
|
+
@connect_timeout_error = value
|
69
172
|
end
|
70
173
|
|
71
|
-
|
72
|
-
|
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)
|
179
|
+
#
|
180
|
+
# @see TCPClient#read
|
181
|
+
#
|
182
|
+
attr_reader :read_timeout
|
183
|
+
|
184
|
+
def read_timeout=(value)
|
185
|
+
@read_timeout = seconds(value)
|
73
186
|
end
|
74
187
|
|
75
|
-
|
76
|
-
|
188
|
+
#
|
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
|
193
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
194
|
+
#
|
195
|
+
attr_reader :read_timeout_error
|
196
|
+
|
197
|
+
def read_timeout_error=(value)
|
198
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
199
|
+
@read_timeout_error = value
|
77
200
|
end
|
78
201
|
|
79
|
-
|
80
|
-
|
202
|
+
#
|
203
|
+
# The maximum time in seconds to write to a connection.
|
204
|
+
#
|
205
|
+
# @return [Numeric] maximum time in seconds
|
206
|
+
# @return [nil] if the write time should not be monitored (default)
|
207
|
+
#
|
208
|
+
# @see TCPClient#write
|
209
|
+
#
|
210
|
+
attr_reader :write_timeout
|
211
|
+
|
212
|
+
def write_timeout=(value)
|
213
|
+
@write_timeout = seconds(value)
|
81
214
|
end
|
82
215
|
|
83
|
-
|
84
|
-
|
216
|
+
#
|
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
|
221
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
222
|
+
#
|
223
|
+
attr_reader :write_timeout_error
|
224
|
+
|
225
|
+
def write_timeout_error=(value)
|
226
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
227
|
+
@write_timeout_error = value
|
85
228
|
end
|
86
229
|
|
87
|
-
|
88
|
-
|
230
|
+
#
|
231
|
+
# @attribute [w] timeout
|
232
|
+
# Shorthand to set maximum time in seconds for all timeout monitoring.
|
233
|
+
#
|
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)
|
89
243
|
end
|
90
244
|
|
91
|
-
|
92
|
-
|
245
|
+
#
|
246
|
+
# @attribute [w] timeout_error
|
247
|
+
# Shorthand to set the exception class wich will by raised by any timeout.
|
248
|
+
#
|
249
|
+
# @return [Class] exception class raised
|
250
|
+
#
|
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)
|
93
259
|
@connect_timeout_error =
|
94
|
-
@read_timeout_error = @write_timeout_error =
|
260
|
+
@read_timeout_error = @write_timeout_error = value
|
95
261
|
end
|
96
262
|
|
97
|
-
|
98
|
-
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
99
|
-
@connect_timeout_error = exception
|
100
|
-
end
|
263
|
+
# @!endgroup
|
101
264
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
106
278
|
|
107
|
-
def
|
108
|
-
|
109
|
-
@write_timeout_error = exception
|
279
|
+
def normalize_network_errors=(value)
|
280
|
+
@normalize_network_errors = value ? true : false
|
110
281
|
end
|
111
282
|
|
112
|
-
|
283
|
+
#
|
284
|
+
# Convert `self` to a Hash containing all attributes.
|
285
|
+
#
|
286
|
+
# @return [Hash<Symbol, Object>]
|
287
|
+
#
|
288
|
+
# @see #initialize
|
289
|
+
#
|
290
|
+
def to_h
|
113
291
|
{
|
114
292
|
buffered: @buffered,
|
115
293
|
keep_alive: @keep_alive,
|
116
294
|
reverse_lookup: @reverse_lookup,
|
295
|
+
ssl_params: @ssl_params,
|
117
296
|
connect_timeout: @connect_timeout,
|
118
297
|
connect_timeout_error: @connect_timeout_error,
|
119
298
|
read_timeout: @read_timeout,
|
120
299
|
read_timeout_error: @read_timeout_error,
|
121
300
|
write_timeout: @write_timeout,
|
122
301
|
write_timeout_error: @write_timeout_error,
|
123
|
-
|
302
|
+
normalize_network_errors: @normalize_network_errors
|
124
303
|
}
|
125
304
|
end
|
126
305
|
|
127
|
-
|
128
|
-
|
306
|
+
# @!visibility private
|
307
|
+
def freeze
|
308
|
+
@ssl_params.freeze
|
309
|
+
super
|
310
|
+
end
|
311
|
+
|
312
|
+
# @!visibility private
|
313
|
+
def initialize_copy(_org)
|
314
|
+
super
|
315
|
+
@ssl_params = Hash[@ssl_params] if @ssl_params
|
316
|
+
self
|
129
317
|
end
|
130
318
|
|
319
|
+
# @!visibility private
|
131
320
|
def ==(other)
|
132
|
-
|
321
|
+
to_h == other.to_h
|
133
322
|
end
|
134
323
|
alias eql? ==
|
135
324
|
|
325
|
+
# @!visibility private
|
136
326
|
def equal?(other)
|
137
327
|
self.class == other.class && self == other
|
138
328
|
end
|
@@ -6,16 +6,50 @@ class TCPClient
|
|
6
6
|
@default_configuration = Configuration.new
|
7
7
|
|
8
8
|
class << self
|
9
|
+
#
|
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]
|
15
|
+
#
|
9
16
|
attr_reader :default_configuration
|
10
17
|
|
18
|
+
#
|
19
|
+
# Configure the {.default_configuration} which is used if no dedicated
|
20
|
+
# configuration was specified to {.open} or {#connect}.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# TCPClient.configure do |cfg|
|
24
|
+
# cfg.buffered = false
|
25
|
+
# cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @param options [Hash] see {Configuration#initialize} for details
|
29
|
+
#
|
30
|
+
# @yieldparam cfg {Configuration} the new configuration
|
31
|
+
#
|
32
|
+
# @return [Configuration] the new default configuration
|
33
|
+
#
|
11
34
|
def configure(options = {}, &block)
|
12
35
|
@default_configuration = Configuration.create(options, &block)
|
13
36
|
end
|
14
37
|
end
|
15
38
|
|
16
39
|
class Configuration
|
17
|
-
|
18
|
-
|
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
|
19
53
|
end
|
20
54
|
end
|
21
55
|
end
|
data/lib/tcp-client/errors.rb
CHANGED
@@ -1,78 +1,153 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class TCPClient
|
4
|
+
#
|
5
|
+
# Raised when a SSL connection should be establshed but the OpenSSL gem is not available.
|
6
|
+
#
|
4
7
|
class NoOpenSSLError < RuntimeError
|
5
8
|
def initialize
|
6
9
|
super('OpenSSL is not available')
|
7
10
|
end
|
8
11
|
end
|
9
12
|
|
13
|
+
#
|
14
|
+
# Raised when a method requires a callback block but no such block is specified.
|
15
|
+
#
|
10
16
|
class NoBlockGivenError < ArgumentError
|
11
17
|
def initialize
|
12
18
|
super('no block given')
|
13
19
|
end
|
14
20
|
end
|
15
21
|
|
22
|
+
#
|
23
|
+
# Raised when an invalid timeout value was specified.
|
24
|
+
#
|
16
25
|
class InvalidDeadLineError < ArgumentError
|
26
|
+
#
|
27
|
+
# @param timeout [Object] the invalid value
|
28
|
+
#
|
17
29
|
def initialize(timeout)
|
18
30
|
super("invalid deadline - #{timeout}")
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
34
|
+
#
|
35
|
+
# Raised by {Configuration} when an undefined attribute should be set.
|
36
|
+
#
|
22
37
|
class UnknownAttributeError < ArgumentError
|
38
|
+
#
|
39
|
+
# @param attribute [Object] the undefined atttribute
|
40
|
+
#
|
23
41
|
def initialize(attribute)
|
24
42
|
super("unknown attribute - #{attribute}")
|
25
43
|
end
|
26
44
|
end
|
27
45
|
|
46
|
+
#
|
47
|
+
# Raised when a given timeout exception parameter is not an exception class.
|
48
|
+
#
|
28
49
|
class NotAnExceptionError < TypeError
|
50
|
+
#
|
51
|
+
# @param object [Object] the invalid object
|
52
|
+
#
|
29
53
|
def initialize(object)
|
30
54
|
super("exception class required - #{object.inspect}")
|
31
55
|
end
|
32
56
|
end
|
33
57
|
|
34
|
-
|
58
|
+
#
|
59
|
+
# Base exception class for all network related errors.
|
60
|
+
#
|
61
|
+
# Will be raised for any system level network error when {Configuration.normalize_network_errors} is configured.
|
62
|
+
#
|
63
|
+
# You should catch this exception class when you like to handle any relevant {TCPClient} error.
|
64
|
+
#
|
65
|
+
class NetworkError < StandardError
|
66
|
+
end
|
35
67
|
|
68
|
+
#
|
69
|
+
# Raised when a {TCPClient} instance should read/write from/to the network but is not connected.
|
70
|
+
#
|
36
71
|
class NotConnectedError < NetworkError
|
37
72
|
def initialize
|
38
73
|
super('client not connected')
|
39
74
|
end
|
40
75
|
end
|
41
76
|
|
77
|
+
#
|
78
|
+
# Base exception class for a detected timeout.
|
79
|
+
#
|
80
|
+
# You should catch this exception class when you like to handle any timeout error.
|
81
|
+
#
|
42
82
|
class TimeoutError < NetworkError
|
83
|
+
#
|
84
|
+
# Initializes the instance with an optional message.
|
85
|
+
#
|
86
|
+
# The message will be generated from {#action} when not specified.
|
87
|
+
#
|
88
|
+
# @overload initialize
|
89
|
+
# @overload initialize(message)
|
90
|
+
#
|
91
|
+
# @param message [#to_s] the error message
|
92
|
+
#
|
43
93
|
def initialize(message = nil)
|
44
94
|
super(message || "unable to #{action} in time")
|
45
95
|
end
|
46
96
|
|
97
|
+
#
|
98
|
+
# @attribute [r] action
|
99
|
+
# @return [Symbol] the action which timed out
|
100
|
+
#
|
47
101
|
def action
|
48
102
|
:process
|
49
103
|
end
|
50
104
|
end
|
51
105
|
|
106
|
+
#
|
107
|
+
# Raised by default whenever a {TCPClient.connect} timed out.
|
108
|
+
#
|
52
109
|
class ConnectTimeoutError < TimeoutError
|
110
|
+
#
|
111
|
+
# @attribute [r] action
|
112
|
+
# @return [Symbol] the action which timed out: `:connect`
|
113
|
+
#
|
53
114
|
def action
|
54
115
|
:connect
|
55
116
|
end
|
56
117
|
end
|
57
118
|
|
119
|
+
#
|
120
|
+
# Raised by default whenever a {TCPClient#read} timed out.
|
121
|
+
#
|
58
122
|
class ReadTimeoutError < TimeoutError
|
123
|
+
#
|
124
|
+
# @attribute [r] action
|
125
|
+
# @return [Symbol] the action which timed out: :read`
|
126
|
+
#
|
59
127
|
def action
|
60
128
|
:read
|
61
129
|
end
|
62
130
|
end
|
63
131
|
|
132
|
+
#
|
133
|
+
# Raised by default whenever a {TCPClient#write} timed out.
|
134
|
+
#
|
64
135
|
class WriteTimeoutError < TimeoutError
|
136
|
+
#
|
137
|
+
# @attribute [r] action
|
138
|
+
# @return [Symbol] the action which timed out: `:write`
|
139
|
+
#
|
65
140
|
def action
|
66
141
|
:write
|
67
142
|
end
|
68
143
|
end
|
69
144
|
|
70
|
-
NoOpenSSL = NoOpenSSLError
|
71
|
-
NoBlockGiven = NoBlockGivenError
|
72
|
-
InvalidDeadLine = InvalidDeadLineError
|
73
|
-
UnknownAttribute = UnknownAttributeError
|
74
|
-
NotAnException = NotAnExceptionError
|
75
|
-
NotConnected = NotConnectedError
|
145
|
+
NoOpenSSL = NoOpenSSLError # @!visibility private
|
146
|
+
NoBlockGiven = NoBlockGivenError # @!visibility private
|
147
|
+
InvalidDeadLine = InvalidDeadLineError # @!visibility private
|
148
|
+
UnknownAttribute = UnknownAttributeError # @!visibility private
|
149
|
+
NotAnException = NotAnExceptionError # @!visibility private
|
150
|
+
NotConnected = NotConnectedError # @!visibility private
|
76
151
|
deprecate_constant(
|
77
152
|
:NoOpenSSL,
|
78
153
|
:NoBlockGiven,
|
data/lib/tcp-client/version.rb
CHANGED
data/lib/tcp-client.rb
CHANGED
@@ -9,7 +9,47 @@ require_relative 'tcp-client/configuration'
|
|
9
9
|
require_relative 'tcp-client/default_configuration'
|
10
10
|
require_relative 'tcp-client/version'
|
11
11
|
|
12
|
+
#
|
13
|
+
# Client class to communicate with a server via TCP w/o SSL.
|
14
|
+
#
|
15
|
+
# All connect/read/write actions can be monitored to ensure that all actions
|
16
|
+
# terminate before given time limits - or raise an exception.
|
17
|
+
#
|
18
|
+
# @example request to Google.com and limit network interactions to 1.5 seconds
|
19
|
+
# TCPClient.with_deadline(1.5, 'www.google.com:443') do |client|
|
20
|
+
# client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
21
|
+
# client.read(12)
|
22
|
+
# end
|
23
|
+
# # => "HTTP/1.1 200"
|
24
|
+
#
|
25
|
+
#
|
12
26
|
class TCPClient
|
27
|
+
#
|
28
|
+
# Creates a new instance which is connected to the server on the given
|
29
|
+
# `address`.
|
30
|
+
#
|
31
|
+
# If no `configuration` is given, the {.default_configuration} will be used.
|
32
|
+
#
|
33
|
+
# If an optional block is given, then the block's result is returned and the
|
34
|
+
# connection will be closed when the block execution ends.
|
35
|
+
# This can be used to create an ad-hoc connection which is garanteed to be
|
36
|
+
# closed.
|
37
|
+
#
|
38
|
+
# If no block is giiven the connected client instance is returned.
|
39
|
+
# This can be used as a shorthand to create & connect a client.
|
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
|
45
|
+
#
|
46
|
+
# @yieldparam client [TCPClient] the connected client
|
47
|
+
# @yieldreturn [Object] any result
|
48
|
+
#
|
49
|
+
# @return [Object, TCPClient] the block result or the connected client
|
50
|
+
#
|
51
|
+
# @see #connect
|
52
|
+
#
|
13
53
|
def self.open(address, configuration = nil)
|
14
54
|
client = new
|
15
55
|
client.connect(Address.new(address), configuration)
|
@@ -18,6 +58,34 @@ class TCPClient
|
|
18
58
|
client.close if block_given?
|
19
59
|
end
|
20
60
|
|
61
|
+
#
|
62
|
+
# Yields a new instance which is connected to the server on the given
|
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.
|
68
|
+
#
|
69
|
+
# This can be used to create an ad-hoc connection which is garanteed to be
|
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.
|
74
|
+
#
|
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
|
81
|
+
#
|
82
|
+
# @yieldparam client [TCPClient] the connected client
|
83
|
+
# @yieldreturn [Object] any result
|
84
|
+
#
|
85
|
+
# @return [Object] the block's result
|
86
|
+
#
|
87
|
+
# @see #with_deadline
|
88
|
+
#
|
21
89
|
def self.with_deadline(timeout, address, configuration = nil)
|
22
90
|
client = nil
|
23
91
|
raise(NoBlockGivenError) unless block_given?
|
@@ -30,21 +98,29 @@ class TCPClient
|
|
30
98
|
client&.close
|
31
99
|
end
|
32
100
|
|
33
|
-
|
101
|
+
#
|
102
|
+
# @return [Address] the address used by this client instance
|
103
|
+
#
|
104
|
+
attr_reader :address
|
34
105
|
|
35
|
-
|
36
|
-
|
37
|
-
|
106
|
+
#
|
107
|
+
# @return [Configuration] the configuration used by this client instance
|
108
|
+
#
|
109
|
+
attr_reader :configuration
|
38
110
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@socket
|
45
|
-
self
|
111
|
+
#
|
112
|
+
# @!parse attr_reader :closed?
|
113
|
+
# @return [Boolean] true when the connection is closed, false when connected
|
114
|
+
#
|
115
|
+
def closed?
|
116
|
+
@socket.nil? || @socket.closed?
|
46
117
|
end
|
47
118
|
|
119
|
+
#
|
120
|
+
# Close the current connection if connected.
|
121
|
+
#
|
122
|
+
# @return [self]
|
123
|
+
#
|
48
124
|
def close
|
49
125
|
@socket&.close
|
50
126
|
self
|
@@ -54,20 +130,67 @@ class TCPClient
|
|
54
130
|
@socket = @deadline = nil
|
55
131
|
end
|
56
132
|
|
57
|
-
|
58
|
-
|
133
|
+
#
|
134
|
+
# Establishes a new connection to a given `address`.
|
135
|
+
#
|
136
|
+
# It accepts a connection-specific configuration or uses the
|
137
|
+
# {.default_configuration}. The {#configuration} used by this instance will
|
138
|
+
# be a copy of the configuration used for this method call. This allows to
|
139
|
+
# configure the behavior per connection.
|
140
|
+
#
|
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
|
151
|
+
#
|
152
|
+
# @return [self]
|
153
|
+
#
|
154
|
+
# @raise {NoOpenSSLError} if SSL should be used but OpenSSL is not avail
|
155
|
+
#
|
156
|
+
# @see NetworkError
|
157
|
+
#
|
158
|
+
def connect(address, configuration = nil, timeout: nil, exception: nil)
|
159
|
+
close if @socket
|
160
|
+
@address = Address.new(address)
|
161
|
+
@configuration = (configuration || Configuration.default).dup
|
162
|
+
raise(NoOpenSSLError) if @configuration.ssl? && !defined?(SSLSocket)
|
163
|
+
@socket = create_socket(timeout, exception)
|
164
|
+
self
|
59
165
|
end
|
60
166
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
167
|
+
#
|
168
|
+
# Flush all internal buffers (write all through).
|
169
|
+
#
|
170
|
+
# @return [self]
|
171
|
+
#
|
172
|
+
def flush
|
173
|
+
stem_errors { @socket&.flush }
|
174
|
+
self
|
69
175
|
end
|
70
176
|
|
177
|
+
#
|
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
|
191
|
+
#
|
192
|
+
# @see NetworkError
|
193
|
+
#
|
71
194
|
def read(nbytes = nil, timeout: nil, exception: nil)
|
72
195
|
raise(NotConnectedError) if closed?
|
73
196
|
deadline = create_deadline(timeout, configuration.read_timeout)
|
@@ -78,23 +201,77 @@ class TCPClient
|
|
78
201
|
end
|
79
202
|
end
|
80
203
|
|
81
|
-
|
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
|
221
|
+
# answer = client.with_deadline(2.5) do
|
222
|
+
# client.write('HELO')
|
223
|
+
# client.read(4)
|
224
|
+
# end
|
225
|
+
# # answer is EHLO when server speaks fluent SMPT
|
226
|
+
#
|
227
|
+
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
228
|
+
# {#write} calls within the block
|
229
|
+
#
|
230
|
+
# @yieldparam client [TCPClient] self
|
231
|
+
# @yieldreturn [Object] any result
|
232
|
+
#
|
233
|
+
# @return [Object] the block`s result
|
234
|
+
#
|
235
|
+
# @raise [NoBlockGivenError] if the block is missing
|
236
|
+
#
|
237
|
+
def with_deadline(timeout)
|
238
|
+
previous_deadline = @deadline
|
239
|
+
raise(NoBlockGivenError) unless block_given?
|
240
|
+
@deadline = Deadline.new(timeout)
|
241
|
+
raise(InvalidDeadLineError, timeout) unless @deadline.valid?
|
242
|
+
yield(self)
|
243
|
+
ensure
|
244
|
+
@deadline = previous_deadline
|
245
|
+
end
|
246
|
+
|
247
|
+
#
|
248
|
+
# Write the given `messages` to the server.
|
249
|
+
#
|
250
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
251
|
+
# `write_timeout` and `write_timeout_error` values of the used
|
252
|
+
# {#configuration}.
|
253
|
+
#
|
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
|
258
|
+
#
|
259
|
+
# @return [Integer] bytes written
|
260
|
+
#
|
261
|
+
# @raise [NotConnectedError] if {#connect} was not called before
|
262
|
+
#
|
263
|
+
def write(*messages, timeout: nil, exception: nil)
|
82
264
|
raise(NotConnectedError) if closed?
|
83
265
|
deadline = create_deadline(timeout, configuration.write_timeout)
|
84
|
-
return stem_errors { @socket.write(*
|
266
|
+
return stem_errors { @socket.write(*messages) } unless deadline.valid?
|
85
267
|
exception ||= configuration.write_timeout_error
|
86
268
|
stem_errors(exception) do
|
87
|
-
|
269
|
+
messages.sum do |chunk|
|
88
270
|
@socket.write_with_deadline(chunk.b, deadline, exception)
|
89
271
|
end
|
90
272
|
end
|
91
273
|
end
|
92
274
|
|
93
|
-
def flush
|
94
|
-
stem_errors { @socket&.flush }
|
95
|
-
self
|
96
|
-
end
|
97
|
-
|
98
275
|
private
|
99
276
|
|
100
277
|
def create_deadline(timeout, default)
|
@@ -133,4 +310,5 @@ class TCPClient
|
|
133
310
|
].tap do |errors|
|
134
311
|
errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
|
135
312
|
end.freeze
|
313
|
+
private_constant(:NETWORK_ERRORS)
|
136
314
|
end
|
data/rakefile.rb
CHANGED
@@ -3,11 +3,10 @@
|
|
3
3
|
require 'rake/clean'
|
4
4
|
require 'bundler/gem_tasks'
|
5
5
|
require 'rspec/core/rake_task'
|
6
|
+
require 'yard'
|
6
7
|
|
7
8
|
$stdout.sync = $stderr.sync = true
|
8
|
-
|
9
|
-
CLOBBER << 'prj'
|
10
|
-
|
9
|
+
CLOBBER << 'prj' << 'doc' << '.yardoc'
|
11
10
|
task(:default) { exec('rake --tasks') }
|
12
|
-
|
13
11
|
RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
|
12
|
+
YARD::Rake::YardocTask.new { |task| task.stats_options = %w[--list-undoc] }
|
@@ -91,25 +91,12 @@ RSpec.describe TCPClient::Address do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
describe '#to_hash' do
|
95
|
-
subject(:address) { TCPClient::Address.new('localhost:42') }
|
96
|
-
|
97
|
-
it 'returns itself as an Hash' do
|
98
|
-
expect(address.to_hash).to eq(host: 'localhost', port: 42)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
94
|
describe '#to_h' do
|
103
95
|
subject(:address) { TCPClient::Address.new('localhost:42') }
|
104
96
|
|
105
97
|
it 'returns itself as an Hash' do
|
106
98
|
expect(address.to_h).to eq(host: 'localhost', port: 42)
|
107
99
|
end
|
108
|
-
|
109
|
-
it 'allows to specify the keys the result should contain' do
|
110
|
-
expect(address.to_h(:port)).to eq(port: 42)
|
111
|
-
expect(address.to_h(:host)).to eq(host: 'localhost')
|
112
|
-
end
|
113
100
|
end
|
114
101
|
|
115
102
|
describe 'comparison' do
|
@@ -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
|
@@ -157,39 +157,6 @@ RSpec.describe TCPClient::Configuration do
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
-
describe '#to_hash' do
|
161
|
-
subject(:configuration) do
|
162
|
-
TCPClient::Configuration.new(
|
163
|
-
buffered: false,
|
164
|
-
connect_timeout: 1,
|
165
|
-
read_timeout: 2,
|
166
|
-
write_timeout: 3,
|
167
|
-
ssl: {
|
168
|
-
min_version: :TLS1_2,
|
169
|
-
max_version: :TLS1_3
|
170
|
-
}
|
171
|
-
)
|
172
|
-
end
|
173
|
-
|
174
|
-
it 'returns itself as an Hash' do
|
175
|
-
expect(configuration.to_hash).to eq(
|
176
|
-
buffered: false,
|
177
|
-
keep_alive: true,
|
178
|
-
reverse_lookup: true,
|
179
|
-
connect_timeout: 1,
|
180
|
-
connect_timeout_error: TCPClient::ConnectTimeoutError,
|
181
|
-
read_timeout: 2,
|
182
|
-
read_timeout_error: TCPClient::ReadTimeoutError,
|
183
|
-
write_timeout: 3,
|
184
|
-
write_timeout_error: TCPClient::WriteTimeoutError,
|
185
|
-
ssl_params: {
|
186
|
-
min_version: :TLS1_2,
|
187
|
-
max_version: :TLS1_3
|
188
|
-
}
|
189
|
-
)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
160
|
describe '#to_h' do
|
194
161
|
subject(:configuration) do
|
195
162
|
TCPClient::Configuration.new(
|
@@ -215,18 +182,13 @@ RSpec.describe TCPClient::Configuration do
|
|
215
182
|
read_timeout_error: TCPClient::ReadTimeoutError,
|
216
183
|
write_timeout: 3,
|
217
184
|
write_timeout_error: TCPClient::WriteTimeoutError,
|
185
|
+
normalize_network_errors: false,
|
218
186
|
ssl_params: {
|
219
187
|
min_version: :TLS1_2,
|
220
188
|
max_version: :TLS1_3
|
221
189
|
}
|
222
190
|
)
|
223
191
|
end
|
224
|
-
|
225
|
-
it 'allows to specify the keys the result should contain' do
|
226
|
-
expect(
|
227
|
-
configuration.to_h(:keep_alive, :read_timeout, :write_timeout)
|
228
|
-
).to eq(keep_alive: true, read_timeout: 2, write_timeout: 3)
|
229
|
-
end
|
230
192
|
end
|
231
193
|
|
232
194
|
describe '#dup' do
|
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,21 +17,23 @@ 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'
|
31
32
|
spec.add_development_dependency 'rspec'
|
33
|
+
spec.add_development_dependency 'yard'
|
32
34
|
|
33
35
|
all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
|
34
36
|
spec.test_files = all_files.grep(%r{^spec/})
|
35
37
|
spec.files = all_files - spec.test_files
|
36
|
-
|
37
38
|
spec.extra_rdoc_files = %w[README.md LICENSE]
|
38
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.
|
4
|
+
version: 0.9.3
|
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-
|
11
|
+
date: 2021-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: |
|
56
70
|
This Gem implements a TCP client with (optional) SSL support.
|
57
71
|
It is an easy to use, versatile configurable client that can correctly
|
@@ -68,6 +82,7 @@ extra_rdoc_files:
|
|
68
82
|
- LICENSE
|
69
83
|
files:
|
70
84
|
- ".gitignore"
|
85
|
+
- ".yardopts"
|
71
86
|
- LICENSE
|
72
87
|
- README.md
|
73
88
|
- gems.rb
|
@@ -98,6 +113,7 @@ licenses:
|
|
98
113
|
metadata:
|
99
114
|
source_code_uri: https://github.com/mblumtritt/tcp-client
|
100
115
|
bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
|
116
|
+
documentation_uri: https://rubydoc.info/github/mblumtritt/tcp-client
|
101
117
|
post_install_message:
|
102
118
|
rdoc_options: []
|
103
119
|
require_paths:
|
@@ -113,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
129
|
- !ruby/object:Gem::Version
|
114
130
|
version: '0'
|
115
131
|
requirements: []
|
116
|
-
rubygems_version: 3.2.
|
132
|
+
rubygems_version: 3.2.32
|
117
133
|
signing_key:
|
118
134
|
specification_version: 4
|
119
135
|
summary: A TCP client implementation with working timeout support.
|