tcp-client 1.0.0 → 1.0.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 +6 -1
- data/LICENSE +2 -1
- data/README.md +1 -1
- data/lib/tcp-client/address.rb +5 -4
- data/lib/tcp-client/configuration.rb +2 -3
- data/lib/tcp-client/deadline.rb +15 -4
- data/lib/tcp-client/errors.rb +0 -15
- data/lib/tcp-client/ssl_socket.rb +6 -4
- data/lib/tcp-client/tcp_socket.rb +9 -12
- data/lib/tcp-client/version.rb +1 -1
- data/lib/tcp-client/with_deadline.rb +19 -25
- data/lib/tcp-client.rb +59 -43
- metadata +3 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0318e4b31864c2b3f2b0af3e4d78f8d64b2d51b48eb2dd888d012583afd5f16
|
4
|
+
data.tar.gz: 5bf4683093179f1bf2a9c98ac0f208b401042b2303b8552bc98034dd138b5cb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d582c9da10d45d722a52621f205e471f0f280a5ad0bfe0b7dc3fbb2f019d3bbf9089fe9918fedf8661655c7b20c17d5586c869739f1323528c238c7f8b576f2e
|
7
|
+
data.tar.gz: 6d2507dc23e40951647c323ce6bf53be89952fc4d7177d1c4e17300625240937100968127c222b285af7ac117844717f2cd22696a5e0c70e068d9e66e98c507e
|
data/.yardopts
CHANGED
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
BSD 3-Clause License
|
2
2
|
|
3
|
-
Copyright (c) 2017-
|
3
|
+
Copyright (c) 2017-2025, Mike Blumtritt. All rights reserved.
|
4
4
|
|
5
5
|
Redistribution and use in source and binary forms, with or without
|
6
6
|
modification, are permitted provided that the following conditions are met:
|
@@ -26,3 +26,4 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
26
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
27
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
data/README.md
CHANGED
data/lib/tcp-client/address.rb
CHANGED
@@ -100,10 +100,11 @@ class TCPClient
|
|
100
100
|
# @return [Address] itself
|
101
101
|
#
|
102
102
|
def freeze
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
unless frozen?
|
104
|
+
solve
|
105
|
+
@addrinfo.freeze
|
106
|
+
@host.freeze
|
107
|
+
end
|
107
108
|
super
|
108
109
|
end
|
109
110
|
|
@@ -43,7 +43,6 @@ class TCPClient
|
|
43
43
|
#
|
44
44
|
def initialize(options = nil)
|
45
45
|
@buffered = @keep_alive = @reverse_lookup = true
|
46
|
-
self.timeout = @ssl_params = nil
|
47
46
|
@connect_timeout_error = ConnectTimeoutError
|
48
47
|
@read_timeout_error = ReadTimeoutError
|
49
48
|
@write_timeout_error = WriteTimeoutError
|
@@ -107,7 +106,7 @@ class TCPClient
|
|
107
106
|
def ssl_params=(value)
|
108
107
|
@ssl_params =
|
109
108
|
if value.respond_to?(:to_hash)
|
110
|
-
|
109
|
+
value.to_hash.dup
|
111
110
|
elsif value.respond_to?(:to_h)
|
112
111
|
value.nil? ? nil : value.to_h.dup
|
113
112
|
else
|
@@ -336,7 +335,7 @@ class TCPClient
|
|
336
335
|
|
337
336
|
private
|
338
337
|
|
339
|
-
def as_seconds(value) = value
|
338
|
+
def as_seconds(value) = value.to_f.positive? ? value : nil
|
340
339
|
|
341
340
|
def as_exception(value)
|
342
341
|
return value if value.is_a?(Class) && value < Exception
|
data/lib/tcp-client/deadline.rb
CHANGED
@@ -2,15 +2,26 @@
|
|
2
2
|
|
3
3
|
class TCPClient
|
4
4
|
class Deadline
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
attr_accessor :exception
|
6
|
+
|
7
|
+
def initialize(timeout, exception)
|
8
|
+
@timeout = timeout.to_f
|
9
|
+
@exception = exception
|
10
|
+
@deadline = @timeout.positive? ? now + @timeout : nil
|
8
11
|
end
|
9
12
|
|
10
13
|
def valid? = !@deadline.nil?
|
11
14
|
|
12
15
|
def remaining_time
|
13
|
-
|
16
|
+
(remaining = @deadline - now) > 0 ? remaining : timed_out!(caller(1))
|
17
|
+
end
|
18
|
+
|
19
|
+
def timed_out!(call_stack = nil)
|
20
|
+
raise(
|
21
|
+
@exception,
|
22
|
+
"execution expired - #{@timeout} seconds",
|
23
|
+
call_stack || caller(1)
|
24
|
+
)
|
14
25
|
end
|
15
26
|
|
16
27
|
private
|
data/lib/tcp-client/errors.rb
CHANGED
@@ -129,19 +129,4 @@ class TCPClient
|
|
129
129
|
#
|
130
130
|
def action = :write
|
131
131
|
end
|
132
|
-
|
133
|
-
NoOpenSSL = NoOpenSSLError # @!visibility private
|
134
|
-
NoBlockGiven = NoBlockGivenError # @!visibility private
|
135
|
-
InvalidDeadLine = InvalidDeadLineError # @!visibility private
|
136
|
-
UnknownAttribute = UnknownAttributeError # @!visibility private
|
137
|
-
NotAnException = NotAnExceptionError # @!visibility private
|
138
|
-
NotConnected = NotConnectedError # @!visibility private
|
139
|
-
deprecate_constant(
|
140
|
-
:NoOpenSSL,
|
141
|
-
:NoBlockGiven,
|
142
|
-
:InvalidDeadLine,
|
143
|
-
:UnknownAttribute,
|
144
|
-
:NotAnException,
|
145
|
-
:NotConnected
|
146
|
-
)
|
147
132
|
end
|
@@ -13,13 +13,13 @@ class TCPClient
|
|
13
13
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
14
14
|
include WithDeadline
|
15
15
|
|
16
|
-
def initialize(socket, address, configuration, deadline
|
16
|
+
def initialize(socket, address, configuration, deadline)
|
17
17
|
ssl_params = Hash[configuration.ssl_params]
|
18
18
|
super(socket, create_context(ssl_params))
|
19
19
|
self.sync_close = true
|
20
20
|
self.hostname = address.hostname
|
21
21
|
check_new_session if @new_session
|
22
|
-
deadline.valid? ? connect_with_deadline(deadline
|
22
|
+
deadline.valid? ? connect_with_deadline(deadline) : connect
|
23
23
|
post_connection_check(address.hostname) if should_verify?(ssl_params)
|
24
24
|
end
|
25
25
|
|
@@ -41,8 +41,8 @@ class TCPClient
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def connect_with_deadline(deadline
|
45
|
-
with_deadline(deadline
|
44
|
+
def connect_with_deadline(deadline)
|
45
|
+
with_deadline(deadline) { connect_nonblock(exception: false) }
|
46
46
|
end
|
47
47
|
|
48
48
|
def should_verify?(ssl_params)
|
@@ -53,6 +53,8 @@ class TCPClient
|
|
53
53
|
CONTEXT_CACHE_MODE =
|
54
54
|
::OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
|
55
55
|
::OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
56
|
+
|
57
|
+
private_constant(:CONTEXT_CACHE_MODE)
|
56
58
|
end
|
57
59
|
|
58
60
|
private_constant(:SSLSocket)
|
@@ -8,24 +8,21 @@ class TCPClient
|
|
8
8
|
class TCPSocket < ::Socket
|
9
9
|
include WithDeadline
|
10
10
|
|
11
|
-
def initialize(address, configuration, deadline
|
12
|
-
|
11
|
+
def initialize(address, configuration, deadline)
|
12
|
+
addrinfo = address.addrinfo
|
13
|
+
super(addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
|
13
14
|
configure(configuration)
|
14
|
-
connect_to(
|
15
|
+
connect_to(
|
16
|
+
::Socket.pack_sockaddr_in(addrinfo.ip_port, addrinfo.ip_address),
|
17
|
+
deadline
|
18
|
+
)
|
15
19
|
end
|
16
20
|
|
17
21
|
private
|
18
22
|
|
19
|
-
def
|
20
|
-
addrinfo = address.addrinfo
|
21
|
-
::Socket.pack_sockaddr_in(addrinfo.ip_port, addrinfo.ip_address)
|
22
|
-
end
|
23
|
-
|
24
|
-
def connect_to(addr, deadline, exception)
|
23
|
+
def connect_to(addr, deadline)
|
25
24
|
return connect(addr) unless deadline.valid?
|
26
|
-
with_deadline(deadline, exception)
|
27
|
-
connect_nonblock(addr, exception: false)
|
28
|
-
end
|
25
|
+
with_deadline(deadline) { connect_nonblock(addr, exception: false) }
|
29
26
|
end
|
30
27
|
|
31
28
|
def configure(configuration)
|
data/lib/tcp-client/version.rb
CHANGED
@@ -2,22 +2,22 @@
|
|
2
2
|
|
3
3
|
class TCPClient
|
4
4
|
module WithDeadline
|
5
|
-
def read_with_deadline(nbytes, deadline
|
6
|
-
|
7
|
-
return fetch_avail(deadline
|
5
|
+
def read_with_deadline(nbytes, deadline)
|
6
|
+
deadline.remaining_time
|
7
|
+
return fetch_avail(deadline) if nbytes.nil?
|
8
8
|
@read_buffer ||= ''.b
|
9
|
-
while @read_buffer.bytesize
|
10
|
-
read = fetch_next(deadline,
|
9
|
+
while (diff = nbytes - @read_buffer.bytesize) > 0
|
10
|
+
read = fetch_next(deadline, diff)
|
11
11
|
read ? @read_buffer << read : (break close)
|
12
12
|
end
|
13
13
|
fetch_slice(nbytes)
|
14
14
|
end
|
15
15
|
|
16
|
-
def read_to_with_deadline(sep, deadline
|
17
|
-
|
16
|
+
def read_to_with_deadline(sep, deadline)
|
17
|
+
deadline.remaining_time
|
18
18
|
@read_buffer ||= ''.b
|
19
19
|
while (index = @read_buffer.index(sep)).nil?
|
20
|
-
read = fetch_next(deadline
|
20
|
+
read = fetch_next(deadline)
|
21
21
|
read ? @read_buffer << read : (break close)
|
22
22
|
end
|
23
23
|
return fetch_slice(index + sep.bytesize) if index
|
@@ -26,15 +26,13 @@ class TCPClient
|
|
26
26
|
result
|
27
27
|
end
|
28
28
|
|
29
|
-
def write_with_deadline(data, deadline
|
29
|
+
def write_with_deadline(data, deadline)
|
30
30
|
return 0 if (size = data.bytesize).zero?
|
31
|
-
|
31
|
+
deadline.remaining_time
|
32
32
|
result = 0
|
33
33
|
while true
|
34
34
|
written =
|
35
|
-
with_deadline(deadline, exception)
|
36
|
-
write_nonblock(data, exception: false)
|
37
|
-
end
|
35
|
+
with_deadline(deadline) { write_nonblock(data, exception: false) }
|
38
36
|
return result if (result += written) >= size
|
39
37
|
data = data.byteslice(written, data.bytesize - written)
|
40
38
|
end
|
@@ -42,8 +40,8 @@ class TCPClient
|
|
42
40
|
|
43
41
|
private
|
44
42
|
|
45
|
-
def fetch_avail(deadline
|
46
|
-
if (result = @read_buffer || fetch_next(deadline
|
43
|
+
def fetch_avail(deadline)
|
44
|
+
if (result = @read_buffer || fetch_next(deadline)).nil?
|
47
45
|
close
|
48
46
|
return ''.b
|
49
47
|
end
|
@@ -59,27 +57,23 @@ class TCPClient
|
|
59
57
|
result
|
60
58
|
end
|
61
59
|
|
62
|
-
def fetch_next(deadline,
|
63
|
-
with_deadline(deadline, exception)
|
64
|
-
read_nonblock(65_536, exception: false)
|
65
|
-
end
|
60
|
+
def fetch_next(deadline, size = 65_536)
|
61
|
+
with_deadline(deadline) { read_nonblock(size, exception: false) }
|
66
62
|
end
|
67
63
|
|
68
|
-
def with_deadline(deadline
|
64
|
+
def with_deadline(deadline)
|
69
65
|
while true
|
70
66
|
case ret = yield
|
71
67
|
when :wait_writable
|
72
|
-
|
73
|
-
wait_write[remaining_time] or raise(exception)
|
68
|
+
wait_write[deadline.remaining_time] or deadline.timed_out!
|
74
69
|
when :wait_readable
|
75
|
-
|
76
|
-
wait_read[remaining_time] or raise(exception)
|
70
|
+
wait_read[deadline.remaining_time] or deadline.timed_out!
|
77
71
|
else
|
78
72
|
return ret
|
79
73
|
end
|
80
74
|
end
|
81
75
|
rescue Errno::ETIMEDOUT
|
82
|
-
|
76
|
+
deadline.timed_out!
|
83
77
|
end
|
84
78
|
|
85
79
|
def wait_write
|
data/lib/tcp-client.rb
CHANGED
@@ -125,7 +125,7 @@ class TCPClient
|
|
125
125
|
def close
|
126
126
|
@socket&.close
|
127
127
|
self
|
128
|
-
rescue
|
128
|
+
rescue *@@net_errors
|
129
129
|
self
|
130
130
|
ensure
|
131
131
|
@socket = @deadline = nil
|
@@ -192,11 +192,15 @@ class TCPClient
|
|
192
192
|
#
|
193
193
|
def read(nbytes = nil, timeout: nil, exception: nil)
|
194
194
|
raise(NotConnectedError) if closed?
|
195
|
-
deadline =
|
195
|
+
deadline =
|
196
|
+
create_deadline(
|
197
|
+
timeout,
|
198
|
+
@configuration.read_timeout,
|
199
|
+
exception || @configuration.read_timeout_error
|
200
|
+
)
|
196
201
|
return stem_errors { @socket.read(nbytes) } unless deadline.valid?
|
197
|
-
exception
|
198
|
-
|
199
|
-
@socket.read_with_deadline(nbytes, deadline, exception)
|
202
|
+
stem_errors(deadline.exception) do
|
203
|
+
@socket.read_with_deadline(nbytes, deadline)
|
200
204
|
end
|
201
205
|
end
|
202
206
|
|
@@ -221,13 +225,17 @@ class TCPClient
|
|
221
225
|
#
|
222
226
|
def readline(separator = $/, chomp: false, timeout: nil, exception: nil)
|
223
227
|
raise(NotConnectedError) if closed?
|
224
|
-
deadline =
|
228
|
+
deadline =
|
229
|
+
create_deadline(
|
230
|
+
timeout,
|
231
|
+
@configuration.read_timeout,
|
232
|
+
exception || @configuration.read_timeout_error
|
233
|
+
)
|
225
234
|
deadline.valid? or
|
226
235
|
return stem_errors { @socket.readline(separator, chomp: chomp) }
|
227
|
-
exception ||= configuration.read_timeout_error
|
228
236
|
line =
|
229
|
-
stem_errors(exception) do
|
230
|
-
@socket.read_to_with_deadline(separator, deadline
|
237
|
+
stem_errors(deadline.exception) do
|
238
|
+
@socket.read_to_with_deadline(separator, deadline)
|
231
239
|
end
|
232
240
|
chomp ? line.chomp : line
|
233
241
|
end
|
@@ -265,7 +273,7 @@ class TCPClient
|
|
265
273
|
def with_deadline(timeout)
|
266
274
|
previous_deadline = @deadline
|
267
275
|
raise(NoBlockGivenError) unless block_given?
|
268
|
-
@deadline = Deadline.new(timeout)
|
276
|
+
@deadline = Deadline.new(timeout, TimeoutError)
|
269
277
|
raise(InvalidDeadLineError, timeout) unless @deadline.valid?
|
270
278
|
yield(self)
|
271
279
|
ensure
|
@@ -292,54 +300,62 @@ class TCPClient
|
|
292
300
|
#
|
293
301
|
def write(*messages, timeout: nil, exception: nil)
|
294
302
|
raise(NotConnectedError) if closed?
|
295
|
-
deadline =
|
303
|
+
deadline =
|
304
|
+
create_deadline(
|
305
|
+
timeout,
|
306
|
+
@configuration.write_timeout,
|
307
|
+
exception || @configuration.write_timeout_error
|
308
|
+
)
|
296
309
|
return stem_errors { @socket.write(*messages) } unless deadline.valid?
|
297
|
-
exception
|
298
|
-
|
299
|
-
messages.sum do |chunk|
|
300
|
-
@socket.write_with_deadline(chunk.b, deadline, exception)
|
301
|
-
end
|
310
|
+
stem_errors(deadline.exception) do
|
311
|
+
messages.sum { |chunk| @socket.write_with_deadline(chunk.b, deadline) }
|
302
312
|
end
|
303
313
|
end
|
304
314
|
|
305
315
|
private
|
306
316
|
|
307
|
-
def create_deadline(timeout,
|
308
|
-
timeout.nil? && @deadline
|
317
|
+
def create_deadline(timeout, timeout_default, exception)
|
318
|
+
if timeout.nil? && @deadline
|
319
|
+
@deadline.exception = exception
|
320
|
+
return @deadline
|
321
|
+
end
|
322
|
+
Deadline.new(timeout || timeout_default, exception)
|
309
323
|
end
|
310
324
|
|
311
325
|
def create_socket(timeout, exception)
|
312
|
-
deadline =
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
326
|
+
deadline =
|
327
|
+
create_deadline(
|
328
|
+
timeout,
|
329
|
+
@configuration.connect_timeout,
|
330
|
+
exception || @configuration.connect_timeout_error
|
331
|
+
)
|
332
|
+
stem_errors(deadline.exception) do
|
333
|
+
@socket = TCPSocket.new(address, @configuration, deadline)
|
334
|
+
return @socket unless @configuration.ssl?
|
335
|
+
SSLSocket.new(@socket, address, @configuration, deadline)
|
318
336
|
end
|
319
337
|
end
|
320
338
|
|
321
339
|
def stem_errors(except = nil)
|
322
340
|
yield
|
323
|
-
rescue
|
324
|
-
raise unless configuration.normalize_network_errors
|
341
|
+
rescue *@@net_errors => e
|
342
|
+
raise unless @configuration.normalize_network_errors
|
325
343
|
except && e.is_a?(except) ? raise : raise(NetworkError, e)
|
326
344
|
end
|
327
345
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
.freeze
|
344
|
-
private_constant(:NETWORK_ERRORS)
|
346
|
+
@@net_errors = [
|
347
|
+
Errno::EADDRNOTAVAIL,
|
348
|
+
Errno::ECONNABORTED,
|
349
|
+
Errno::ECONNREFUSED,
|
350
|
+
Errno::EHOSTUNREACH,
|
351
|
+
Errno::ENETUNREACH,
|
352
|
+
Errno::ECONNRESET,
|
353
|
+
Errno::EINVAL,
|
354
|
+
Errno::EPIPE,
|
355
|
+
Errno::EPERM,
|
356
|
+
SocketError,
|
357
|
+
IOError
|
358
|
+
]
|
359
|
+
@@net_errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
|
360
|
+
@@net_errors.freeze
|
345
361
|
end
|
metadata
CHANGED
@@ -1,21 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tcp-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Blumtritt
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-20 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: |
|
14
13
|
This gem implements a customizable TCP client class that gives you control
|
15
14
|
over time limits. You can set time limits for individual read or write calls
|
16
15
|
or set a deadline for entire call sequences.
|
17
16
|
It has a very small footprint, no dependencies and is easily useable.
|
18
|
-
email:
|
19
17
|
executables: []
|
20
18
|
extensions: []
|
21
19
|
extra_rdoc_files:
|
@@ -44,7 +42,6 @@ metadata:
|
|
44
42
|
bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
|
45
43
|
documentation_uri: https://rubydoc.info/gems/tcp-client
|
46
44
|
rubygems_mfa_required: 'true'
|
47
|
-
post_install_message:
|
48
45
|
rdoc_options: []
|
49
46
|
require_paths:
|
50
47
|
- lib
|
@@ -59,8 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
56
|
- !ruby/object:Gem::Version
|
60
57
|
version: '0'
|
61
58
|
requirements: []
|
62
|
-
rubygems_version: 3.
|
63
|
-
signing_key:
|
59
|
+
rubygems_version: 3.6.6
|
64
60
|
specification_version: 4
|
65
61
|
summary: Use your TCP connections with working timeout.
|
66
62
|
test_files: []
|