tcp-client 0.14.0 → 1.0.1
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/README.md +1 -1
- data/lib/tcp-client/configuration.rb +1 -1
- data/lib/tcp-client/deadline.rb +15 -4
- data/lib/tcp-client/ssl_socket.rb +6 -6
- data/lib/tcp-client/tcp_socket.rb +6 -8
- data/lib/tcp-client/version.rb +1 -1
- data/lib/tcp-client/with_deadline.rb +103 -0
- data/lib/tcp-client.rb +42 -24
- metadata +4 -4
- data/lib/tcp-client/mixin/io_with_deadline.rb +0 -108
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f087df088e9bdfc71ffd19b01d3f9f7a229f4db510a339a08ea7d02c07a32eda
|
4
|
+
data.tar.gz: 4b4d332dcdeb98a2bc81f3e7963973da21336e5b2d89199bfc9b0d2c92c9b122
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0c31448b596bc77288424a2863528714db40576f7201f2927172d0c39cac4086fe976a315347d6da78a96ca022a33e31956533caa02c025b18b7b3e3129d97d
|
7
|
+
data.tar.gz: 69dd0aa063a8019c07976b6942f0d08df0f31036eb903040e63cfdc40844c0275396a879a2e7f27332ad9843b2a2ba4326fbccabee3d12e1a3b31a587cb5ba9a
|
data/.yardopts
CHANGED
data/README.md
CHANGED
@@ -336,7 +336,7 @@ class TCPClient
|
|
336
336
|
|
337
337
|
private
|
338
338
|
|
339
|
-
def as_seconds(value) = value
|
339
|
+
def as_seconds(value) = value.to_f.positive? ? value : nil
|
340
340
|
|
341
341
|
def as_exception(value)
|
342
342
|
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
|
@@ -7,19 +7,19 @@ rescue LoadError
|
|
7
7
|
end
|
8
8
|
|
9
9
|
require_relative 'deadline'
|
10
|
-
require_relative '
|
10
|
+
require_relative 'with_deadline'
|
11
11
|
|
12
12
|
class TCPClient
|
13
13
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
14
|
-
include
|
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)
|
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'socket'
|
4
4
|
require_relative 'deadline'
|
5
|
-
require_relative '
|
5
|
+
require_relative 'with_deadline'
|
6
6
|
|
7
7
|
class TCPClient
|
8
8
|
class TCPSocket < ::Socket
|
9
|
-
include
|
9
|
+
include WithDeadline
|
10
10
|
|
11
|
-
def initialize(address, configuration, deadline
|
11
|
+
def initialize(address, configuration, deadline)
|
12
12
|
super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
|
13
13
|
configure(configuration)
|
14
|
-
connect_to(as_addr_in(address), deadline
|
14
|
+
connect_to(as_addr_in(address), deadline)
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
@@ -21,11 +21,9 @@ class TCPClient
|
|
21
21
|
::Socket.pack_sockaddr_in(addrinfo.ip_port, addrinfo.ip_address)
|
22
22
|
end
|
23
23
|
|
24
|
-
def connect_to(addr, deadline
|
24
|
+
def connect_to(addr, deadline)
|
25
25
|
return connect(addr) unless deadline.valid?
|
26
|
-
with_deadline(deadline, exception)
|
27
|
-
connect_nonblock(addr, exception: false)
|
28
|
-
end
|
26
|
+
with_deadline(deadline) { connect_nonblock(addr, exception: false) }
|
29
27
|
end
|
30
28
|
|
31
29
|
def configure(configuration)
|
data/lib/tcp-client/version.rb
CHANGED
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TCPClient
|
4
|
+
module WithDeadline
|
5
|
+
def read_with_deadline(nbytes, deadline)
|
6
|
+
deadline.remaining_time
|
7
|
+
return fetch_avail(deadline) if nbytes.nil?
|
8
|
+
@read_buffer ||= ''.b
|
9
|
+
while @read_buffer.bytesize < nbytes
|
10
|
+
read = fetch_next(deadline)
|
11
|
+
read ? @read_buffer << read : (break close)
|
12
|
+
end
|
13
|
+
fetch_slice(nbytes)
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_to_with_deadline(sep, deadline)
|
17
|
+
deadline.remaining_time
|
18
|
+
@read_buffer ||= ''.b
|
19
|
+
while (index = @read_buffer.index(sep)).nil?
|
20
|
+
read = fetch_next(deadline)
|
21
|
+
read ? @read_buffer << read : (break close)
|
22
|
+
end
|
23
|
+
return fetch_slice(index + sep.bytesize) if index
|
24
|
+
result = @read_buffer
|
25
|
+
@read_buffer = nil
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_with_deadline(data, deadline)
|
30
|
+
return 0 if (size = data.bytesize).zero?
|
31
|
+
deadline.remaining_time
|
32
|
+
result = 0
|
33
|
+
while true
|
34
|
+
written =
|
35
|
+
with_deadline(deadline) { write_nonblock(data, exception: false) }
|
36
|
+
return result if (result += written) >= size
|
37
|
+
data = data.byteslice(written, data.bytesize - written)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def fetch_avail(deadline)
|
44
|
+
if (result = @read_buffer || fetch_next(deadline)).nil?
|
45
|
+
close
|
46
|
+
return ''.b
|
47
|
+
end
|
48
|
+
@read_buffer = nil
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_slice(size)
|
53
|
+
return ''.b if size <= 0
|
54
|
+
result = @read_buffer.byteslice(0, size)
|
55
|
+
rest = @read_buffer.bytesize - result.bytesize
|
56
|
+
@read_buffer = rest.zero? ? nil : @read_buffer.byteslice(size, rest)
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
def fetch_next(deadline)
|
61
|
+
with_deadline(deadline) { read_nonblock(65_536, exception: false) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def with_deadline(deadline)
|
65
|
+
while true
|
66
|
+
case ret = yield
|
67
|
+
when :wait_writable
|
68
|
+
wait_write[deadline.remaining_time] or deadline.timed_out!
|
69
|
+
when :wait_readable
|
70
|
+
wait_read[deadline.remaining_time] or deadline.timed_out!
|
71
|
+
else
|
72
|
+
return ret
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue Errno::ETIMEDOUT
|
76
|
+
deadline.timed_out!
|
77
|
+
end
|
78
|
+
|
79
|
+
def wait_write
|
80
|
+
@wait_write ||=
|
81
|
+
if defined?(wait_writable)
|
82
|
+
->(t) { wait_writable(t) }
|
83
|
+
elsif defined?(to_io)
|
84
|
+
->(t) { to_io.wait_writable(t) }
|
85
|
+
else
|
86
|
+
->(t) { ::IO.select(nil, [self], nil, t) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait_read
|
91
|
+
@wait_read ||=
|
92
|
+
if defined?(wait_readable)
|
93
|
+
->(t) { wait_readable(t) }
|
94
|
+
elsif defined?(to_io)
|
95
|
+
->(t) { to_io.wait_readable(t) }
|
96
|
+
else
|
97
|
+
->(t) { ::IO.select([self], nil, nil, t) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private_constant(:WithDeadline)
|
103
|
+
end
|
data/lib/tcp-client.rb
CHANGED
@@ -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,36 +300,46 @@ 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
341
|
rescue *NETWORK_ERRORS => e
|
324
|
-
raise unless configuration.normalize_network_errors
|
342
|
+
raise unless @configuration.normalize_network_errors
|
325
343
|
except && e.is_a?(except) ? raise : raise(NetworkError, e)
|
326
344
|
end
|
327
345
|
|
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: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Blumtritt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
This gem implements a customizable TCP client class that gives you control
|
@@ -31,10 +31,10 @@ files:
|
|
31
31
|
- lib/tcp-client/deadline.rb
|
32
32
|
- lib/tcp-client/default_configuration.rb
|
33
33
|
- lib/tcp-client/errors.rb
|
34
|
-
- lib/tcp-client/mixin/io_with_deadline.rb
|
35
34
|
- lib/tcp-client/ssl_socket.rb
|
36
35
|
- lib/tcp-client/tcp_socket.rb
|
37
36
|
- lib/tcp-client/version.rb
|
37
|
+
- lib/tcp-client/with_deadline.rb
|
38
38
|
- lib/tcp_client.rb
|
39
39
|
homepage: https://github.com/mblumtritt/tcp-client
|
40
40
|
licenses:
|
@@ -59,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '0'
|
61
61
|
requirements: []
|
62
|
-
rubygems_version: 3.5.
|
62
|
+
rubygems_version: 3.5.10
|
63
63
|
signing_key:
|
64
64
|
specification_version: 4
|
65
65
|
summary: Use your TCP connections with working timeout.
|
@@ -1,108 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class TCPClient
|
4
|
-
module IOWithDeadlineMixin
|
5
|
-
class << self
|
6
|
-
private
|
7
|
-
|
8
|
-
def included(mod)
|
9
|
-
return if defined?(mod.wait_writable) && defined?(mod.wait_readable)
|
10
|
-
mod.include(defined?(mod.to_io) ? WaitWithIO : WaitWithSelect)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def read_with_deadline(nbytes, deadline, exception)
|
15
|
-
raise(exception) unless deadline.remaining_time
|
16
|
-
return fetch_avail(deadline, exception) if nbytes.nil?
|
17
|
-
@read_buffer ||= ''.b
|
18
|
-
while @read_buffer.bytesize < nbytes
|
19
|
-
read = fetch_next(deadline, exception)
|
20
|
-
read ? @read_buffer << read : (break close)
|
21
|
-
end
|
22
|
-
fetch_slice(nbytes)
|
23
|
-
end
|
24
|
-
|
25
|
-
def read_to_with_deadline(sep, deadline, exception)
|
26
|
-
raise(exception) unless deadline.remaining_time
|
27
|
-
@read_buffer ||= ''.b
|
28
|
-
while (index = @read_buffer.index(sep)).nil?
|
29
|
-
read = fetch_next(deadline, exception)
|
30
|
-
read ? @read_buffer << read : (break close)
|
31
|
-
end
|
32
|
-
return fetch_slice(index + sep.bytesize) if index
|
33
|
-
result = @read_buffer
|
34
|
-
@read_buffer = nil
|
35
|
-
result
|
36
|
-
end
|
37
|
-
|
38
|
-
def write_with_deadline(data, deadline, exception)
|
39
|
-
return 0 if (size = data.bytesize).zero?
|
40
|
-
raise(exception) unless deadline.remaining_time
|
41
|
-
result = 0
|
42
|
-
while true
|
43
|
-
written =
|
44
|
-
with_deadline(deadline, exception) do
|
45
|
-
write_nonblock(data, exception: false)
|
46
|
-
end
|
47
|
-
return result if (result += written) >= size
|
48
|
-
data = data.byteslice(written, data.bytesize - written)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def fetch_avail(deadline, exception)
|
55
|
-
if (result = @read_buffer || fetch_next(deadline, exception)).nil?
|
56
|
-
close
|
57
|
-
return ''.b
|
58
|
-
end
|
59
|
-
@read_buffer = nil
|
60
|
-
result
|
61
|
-
end
|
62
|
-
|
63
|
-
def fetch_slice(size)
|
64
|
-
return ''.b if size <= 0
|
65
|
-
result = @read_buffer.byteslice(0, size)
|
66
|
-
rest = @read_buffer.bytesize - result.bytesize
|
67
|
-
@read_buffer = rest.zero? ? nil : @read_buffer.byteslice(size, rest)
|
68
|
-
result
|
69
|
-
end
|
70
|
-
|
71
|
-
def fetch_next(deadline, exception)
|
72
|
-
with_deadline(deadline, exception) do
|
73
|
-
read_nonblock(65_536, exception: false)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def with_deadline(deadline, exception)
|
78
|
-
while true
|
79
|
-
case ret = yield
|
80
|
-
when :wait_writable
|
81
|
-
remaining_time = deadline.remaining_time or raise(exception)
|
82
|
-
wait_writable(remaining_time) or raise(exception)
|
83
|
-
when :wait_readable
|
84
|
-
remaining_time = deadline.remaining_time or raise(exception)
|
85
|
-
wait_readable(remaining_time) or raise(exception)
|
86
|
-
else
|
87
|
-
return ret
|
88
|
-
end
|
89
|
-
end
|
90
|
-
rescue Errno::ETIMEDOUT
|
91
|
-
raise(exception)
|
92
|
-
end
|
93
|
-
|
94
|
-
module WaitWithIO
|
95
|
-
def wait_writable(time) = to_io.wait_writable(time)
|
96
|
-
def wait_readable(time) = to_io.wait_readable(time)
|
97
|
-
end
|
98
|
-
|
99
|
-
module WaitWithSelect
|
100
|
-
def wait_writable(time) = ::IO.select(nil, [self], nil, time)
|
101
|
-
def wait_readable(time) = ::IO.select([self], nil, nil, time)
|
102
|
-
end
|
103
|
-
|
104
|
-
private_constant(:WaitWithIO, :WaitWithSelect)
|
105
|
-
end
|
106
|
-
|
107
|
-
private_constant(:IOWithDeadlineMixin)
|
108
|
-
end
|