tcp-client 0.14.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|