tcp-client 0.3.2 → 0.4.0
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 +1 -1
- data/README.md +5 -4
- data/lib/tcp-client.rb +9 -9
- data/lib/tcp-client/address.rb +1 -4
- data/lib/tcp-client/configuration.rb +7 -7
- data/lib/tcp-client/deadline.rb +32 -0
- data/lib/tcp-client/default_configuration.rb +2 -0
- data/lib/tcp-client/mixin/io_with_deadline.rb +13 -14
- data/lib/tcp-client/ssl_socket.rb +9 -6
- data/lib/tcp-client/tcp_socket.rb +6 -3
- data/lib/tcp-client/version.rb +3 -1
- data/rakefile.rb +2 -3
- data/sample/google.rb +4 -3
- data/sample/google_ssl.rb +5 -3
- data/tcp-client.gemspec +7 -5
- data/test/tcp-client/address_test.rb +2 -0
- data/test/tcp-client/configuration_test.rb +2 -0
- data/test/tcp-client/deadline_test.rb +26 -0
- data/test/tcp-client/version_test.rb +2 -0
- data/test/tcp_client_test.rb +2 -0
- metadata +12 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d280b6685729b5f2573ec4bfb2f8d57b154275b381ef61084138e03bc5c2c2e
|
4
|
+
data.tar.gz: 5bbd6d4d3b1c94fa87e44d9ae03d80e38f444e0776680eb0eb42aed60d915fd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b7b41def4591e42fc6db859a7c5f485fc5416e266728526d59313141cfc4ccd828f7adff563a425647a61fac5bc751a1a01193bc98bef589c9b9393737c92c4
|
7
|
+
data.tar.gz: 8d6d5e239dc2ba17a1015a565de2603be0f0421ee0e10fb24a94de86e5cb150d59842fd053cb27a3f58ca58fe43728f321cdfe02bba39a2c723a66ffb2013d0e
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
A TCP client implementation with working timeout support.
|
4
4
|
|
5
5
|
## Description
|
6
|
-
|
6
|
+
|
7
|
+
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.
|
7
8
|
|
8
9
|
## Sample
|
9
10
|
|
@@ -11,15 +12,15 @@ This gem implements a TCP client with (optional) SSL support. The motivation of
|
|
11
12
|
require 'tcp-client'
|
12
13
|
|
13
14
|
TCPClient.configure do |cfg|
|
14
|
-
cfg.connect_timeout = 1 #
|
15
|
+
cfg.connect_timeout = 1 # limit connect time the server to 1 second
|
15
16
|
cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
|
16
17
|
end
|
17
18
|
|
18
19
|
TCPClient.open('www.google.com:443') do |client|
|
19
|
-
#
|
20
|
+
# next sequence should not last longer than 0.5 seconds
|
20
21
|
client.with_deadline(0.5) do
|
21
22
|
# simple HTTP get request
|
22
|
-
pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
23
|
+
pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
23
24
|
|
24
25
|
# read "HTTP/1.1 " + 3 byte HTTP status code
|
25
26
|
pp client.read(12)
|
data/lib/tcp-client.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'tcp-client/errors'
|
4
4
|
require_relative 'tcp-client/address'
|
5
|
+
require_relative 'tcp-client/deadline'
|
5
6
|
require_relative 'tcp-client/tcp_socket'
|
6
7
|
require_relative 'tcp-client/ssl_socket'
|
7
8
|
require_relative 'tcp-client/configuration'
|
@@ -55,9 +56,8 @@ class TCPClient
|
|
55
56
|
def with_deadline(timeout)
|
56
57
|
previous_deadline = @deadline
|
57
58
|
raise(NoBlockGiven) unless block_given?
|
58
|
-
|
59
|
-
raise(InvalidDeadLine) unless
|
60
|
-
@deadline = Time.now + tm
|
59
|
+
@deadline = Deadline.new(timeout)
|
60
|
+
raise(InvalidDeadLine, timeout) unless @deadline.valid?
|
61
61
|
yield(self)
|
62
62
|
ensure
|
63
63
|
@deadline = previous_deadline
|
@@ -67,18 +67,18 @@ class TCPClient
|
|
67
67
|
raise(NotConnected) if closed?
|
68
68
|
timeout.nil? && @deadline and
|
69
69
|
return read_with_deadline(nbytes, @deadline, exception)
|
70
|
-
|
71
|
-
return @socket.read(nbytes) unless
|
72
|
-
read_with_deadline(nbytes,
|
70
|
+
deadline = Deadline.new(timeout || @cfg.read_timeout)
|
71
|
+
return @socket.read(nbytes) unless deadline.valid?
|
72
|
+
read_with_deadline(nbytes, deadline, exception)
|
73
73
|
end
|
74
74
|
|
75
75
|
def write(*msg, timeout: nil, exception: nil)
|
76
76
|
raise(NotConnected) if closed?
|
77
77
|
timeout.nil? && @deadline and
|
78
78
|
return write_with_deadline(msg, @deadline, exception)
|
79
|
-
|
80
|
-
return @socket.write(*msg) unless
|
81
|
-
write_with_deadline(msg,
|
79
|
+
deadline = Deadline.new(timeout || @cfg.read_timeout)
|
80
|
+
return @socket.write(*msg) unless deadline.valid?
|
81
|
+
write_with_deadline(msg, deadline, exception)
|
82
82
|
end
|
83
83
|
|
84
84
|
def flush
|
data/lib/tcp-client/address.rb
CHANGED
@@ -58,10 +58,7 @@ class TCPClient
|
|
58
58
|
|
59
59
|
def from_string(str)
|
60
60
|
idx = str.rindex(':') or return nil, str.to_i
|
61
|
-
name = str[0, idx]
|
62
|
-
if name.start_with?('[') && name.end_with?(']')
|
63
|
-
name = name[1, name.size - 2]
|
64
|
-
end
|
61
|
+
name = str[0, idx].delete_prefix('[').delete_suffix(']')
|
65
62
|
[name, str[idx + 1, str.size - idx].to_i]
|
66
63
|
end
|
67
64
|
end
|
@@ -13,7 +13,6 @@ class TCPClient
|
|
13
13
|
attr_reader :buffered,
|
14
14
|
:keep_alive,
|
15
15
|
:reverse_lookup,
|
16
|
-
:timeout,
|
17
16
|
:connect_timeout,
|
18
17
|
:read_timeout,
|
19
18
|
:write_timeout,
|
@@ -47,9 +46,12 @@ class TCPClient
|
|
47
46
|
end
|
48
47
|
|
49
48
|
def ssl=(value)
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
@ssl_params =
|
50
|
+
if Hash === value
|
51
|
+
value.dup
|
52
|
+
else
|
53
|
+
value ? {} : nil
|
54
|
+
end
|
53
55
|
end
|
54
56
|
|
55
57
|
def buffered=(value)
|
@@ -65,8 +67,7 @@ class TCPClient
|
|
65
67
|
end
|
66
68
|
|
67
69
|
def timeout=(seconds)
|
68
|
-
@
|
69
|
-
@connect_timeout = @write_timeout = @read_timeout = seconds(seconds)
|
70
|
+
@connect_timeout = @write_timeout = @read_timeout = seconds(seconds)
|
70
71
|
end
|
71
72
|
|
72
73
|
def connect_timeout=(seconds)
|
@@ -107,7 +108,6 @@ class TCPClient
|
|
107
108
|
buffered: @buffered,
|
108
109
|
keep_alive: @keep_alive,
|
109
110
|
reverse_lookup: @reverse_lookup,
|
110
|
-
timeout: @timeout,
|
111
111
|
connect_timeout: @connect_timeout,
|
112
112
|
read_timeout: @read_timeout,
|
113
113
|
write_timeout: @write_timeout,
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TCPClient
|
4
|
+
class Deadline
|
5
|
+
MONOTONIC = !!defined?(Process::CLOCK_MONOTONIC)
|
6
|
+
|
7
|
+
def initialize(timeout)
|
8
|
+
timeout = timeout&.to_f
|
9
|
+
@deadline = timeout&.positive? ? now + timeout : 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
@deadline != 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def remaining_time
|
17
|
+
(@deadline != 0) && (remaining = @deadline - now) > 0 ? remaining : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
if MONOTONIC
|
23
|
+
def now
|
24
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
def now
|
28
|
+
::Time.now
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module IOWithDeadlineMixin
|
2
4
|
def self.included(mod)
|
3
5
|
methods = mod.instance_methods
|
@@ -9,25 +11,22 @@ module IOWithDeadlineMixin
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def read_with_deadline(bytes_to_read, deadline, exception)
|
12
|
-
raise(exception)
|
14
|
+
raise(exception) unless deadline.remaining_time
|
13
15
|
result = ''.b
|
14
|
-
|
15
|
-
loop do
|
16
|
+
while result.bytesize < bytes_to_read
|
16
17
|
read =
|
17
18
|
with_deadline(deadline, exception) do
|
18
19
|
read_nonblock(bytes_to_read - result.bytesize, exception: false)
|
19
20
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
result += read
|
25
|
-
return result if result.bytesize >= bytes_to_read
|
21
|
+
next result += read if read
|
22
|
+
close
|
23
|
+
break
|
26
24
|
end
|
25
|
+
result
|
27
26
|
end
|
28
27
|
|
29
28
|
def write_with_deadline(data, deadline, exception)
|
30
|
-
raise(exception)
|
29
|
+
raise(exception) unless deadline.remaining_time
|
31
30
|
return 0 if (size = data.bytesize).zero?
|
32
31
|
result = 0
|
33
32
|
loop do
|
@@ -46,10 +45,10 @@ module IOWithDeadlineMixin
|
|
46
45
|
loop do
|
47
46
|
case ret = yield
|
48
47
|
when :wait_writable
|
49
|
-
|
48
|
+
remaining_time = deadline.remaining_time or raise(exception)
|
50
49
|
raise(exception) if wait_writable(remaining_time).nil?
|
51
50
|
when :wait_readable
|
52
|
-
|
51
|
+
remaining_time = deadline.remaining_time or raise(exception)
|
53
52
|
raise(exception) if wait_readable(remaining_time).nil?
|
54
53
|
else
|
55
54
|
return ret
|
@@ -65,10 +64,10 @@ module IOWithDeadlineMixin
|
|
65
64
|
loop do
|
66
65
|
case ret = yield
|
67
66
|
when :wait_writable
|
68
|
-
|
67
|
+
remaining_time = deadline.remaining_time or raise(exception)
|
69
68
|
raise(exception) if ::IO.select(nil, [self], nil, remaining_time).nil?
|
70
69
|
when :wait_readable
|
71
|
-
|
70
|
+
remaining_time = deadline.remaining_time or raise(exception)
|
72
71
|
raise(exception) if ::IO.select([self], nil, nil, remaining_time).nil?
|
73
72
|
else
|
74
73
|
return ret
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'openssl'
|
3
5
|
rescue LoadError
|
4
6
|
return
|
5
7
|
end
|
6
8
|
|
9
|
+
require_relative 'deadline'
|
7
10
|
require_relative 'mixin/io_with_deadline'
|
8
11
|
|
9
12
|
class TCPClient
|
@@ -12,8 +15,8 @@ class TCPClient
|
|
12
15
|
|
13
16
|
def initialize(socket, address, configuration, exception)
|
14
17
|
ssl_params = Hash[configuration.ssl_params]
|
15
|
-
self.sync_close = true
|
16
18
|
super(socket, create_context(ssl_params))
|
19
|
+
self.sync_close = true
|
17
20
|
connect_to(
|
18
21
|
address,
|
19
22
|
ssl_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE,
|
@@ -32,13 +35,13 @@ class TCPClient
|
|
32
35
|
|
33
36
|
def connect_to(address, check, timeout, exception)
|
34
37
|
self.hostname = address.hostname
|
35
|
-
|
36
|
-
if
|
37
|
-
|
38
|
-
else
|
39
|
-
with_deadline(Time.now + timeout, exception) do
|
38
|
+
deadline = Deadline.new(timeout)
|
39
|
+
if deadline.valid?
|
40
|
+
with_deadline(deadline, exception) do
|
40
41
|
connect_nonblock(exception: false)
|
41
42
|
end
|
43
|
+
else
|
44
|
+
connect
|
42
45
|
end
|
43
46
|
post_connection_check(address.hostname) if check
|
44
47
|
end
|
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'socket'
|
4
|
+
require_relative 'deadline'
|
2
5
|
require_relative 'mixin/io_with_deadline'
|
3
6
|
|
4
7
|
class TCPClient
|
@@ -19,9 +22,9 @@ class TCPClient
|
|
19
22
|
address.addrinfo.ip_port,
|
20
23
|
address.addrinfo.ip_address
|
21
24
|
)
|
22
|
-
|
23
|
-
return connect(addr)
|
24
|
-
with_deadline(
|
25
|
+
deadline = Deadline.new(timeout)
|
26
|
+
return connect(addr) unless deadline.valid?
|
27
|
+
with_deadline(deadline, exception) do
|
25
28
|
connect_nonblock(addr, exception: false)
|
26
29
|
end
|
27
30
|
end
|
data/lib/tcp-client/version.rb
CHANGED
data/rakefile.rb
CHANGED
@@ -11,7 +11,6 @@ CLOBBER << 'prj'
|
|
11
11
|
task(:default) { exec('rake --tasks') }
|
12
12
|
|
13
13
|
Rake::TestTask.new(:test) do |task|
|
14
|
-
task.
|
15
|
-
task.
|
16
|
-
task.verbose = true
|
14
|
+
task.pattern = 'test/**/*_test.rb'
|
15
|
+
task.warning = task.verbose = true
|
17
16
|
end
|
data/sample/google.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../lib/tcp-client'
|
2
4
|
|
3
5
|
TCPClient.configure(
|
@@ -6,15 +8,14 @@ TCPClient.configure(
|
|
6
8
|
read_timeout: 0.5 # seconds to read some bytes
|
7
9
|
)
|
8
10
|
|
9
|
-
# the following
|
10
|
-
# to last longer than 1.25 seconds:
|
11
|
+
# the following sequence is not allowed to last longer than 1.25 seconds:
|
11
12
|
# 0.5 seconds to connect
|
12
13
|
# + 0.25 seconds to write data
|
13
14
|
# + 0.5 seconds to read a response
|
14
15
|
|
15
16
|
TCPClient.open('www.google.com:80') do |client|
|
16
17
|
# simple HTTP get request
|
17
|
-
pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
18
|
+
pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
18
19
|
|
19
20
|
# read "HTTP/1.1 " + 3 byte HTTP status code
|
20
21
|
pp client.read(12)
|
data/sample/google_ssl.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../lib/tcp-client'
|
2
4
|
|
3
5
|
TCPClient.configure do |cfg|
|
4
|
-
cfg.connect_timeout = 1 #
|
6
|
+
cfg.connect_timeout = 1 # limit connect time the server to 1 second
|
5
7
|
cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
|
6
8
|
end
|
7
9
|
|
8
10
|
TCPClient.open('www.google.com:443') do |client|
|
9
|
-
#
|
11
|
+
# next sequence should not last longer than 0.5 seconds
|
10
12
|
client.with_deadline(0.5) do
|
11
13
|
# simple HTTP get request
|
12
|
-
pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
|
14
|
+
pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
13
15
|
|
14
16
|
# read "HTTP/1.1 " + 3 byte HTTP status code
|
15
17
|
pp client.read(12)
|
data/tcp-client.gemspec
CHANGED
@@ -11,11 +11,13 @@ GemSpec = Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = 'A TCP client implementation with working timeout support.'
|
13
13
|
spec.description = <<~DESCRIPTION
|
14
|
-
This
|
15
|
-
|
16
|
-
|
17
|
-
other implementations this client respects
|
18
|
-
limits for each method
|
14
|
+
This Gem implements a TCP client with (optional) SSL support.
|
15
|
+
It is an easy to use, versatile configurable client that can correctly
|
16
|
+
handle time limits.
|
17
|
+
Unlike other implementations, this client respects
|
18
|
+
predefined/configurable time limits for each method
|
19
|
+
(`connect`, `read`, `write`). Deadlines for a sequence of read/write
|
20
|
+
actions can also be monitored.
|
19
21
|
DESCRIPTION
|
20
22
|
spec.homepage = 'https://github.com/mblumtritt/tcp-client'
|
21
23
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../test_helper'
|
4
|
+
|
5
|
+
class Deadlineest < MiniTest::Test
|
6
|
+
parallelize_me!
|
7
|
+
|
8
|
+
def test_validity
|
9
|
+
assert(TCPClient::Deadline.new(1).valid?)
|
10
|
+
assert(TCPClient::Deadline.new(0.0001).valid?)
|
11
|
+
|
12
|
+
refute(TCPClient::Deadline.new(0).valid?)
|
13
|
+
refute(TCPClient::Deadline.new(nil).valid?)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_remaining_time
|
17
|
+
assert(TCPClient::Deadline.new(1).remaining_time > 0)
|
18
|
+
|
19
|
+
assert_nil(TCPClient::Deadline.new(0).remaining_time)
|
20
|
+
assert_nil(TCPClient::Deadline.new(nil).remaining_time)
|
21
|
+
|
22
|
+
deadline = TCPClient::Deadline.new(0.2)
|
23
|
+
sleep(0.2)
|
24
|
+
assert_nil(deadline.remaining_time)
|
25
|
+
end
|
26
|
+
end
|
data/test/tcp_client_test.rb
CHANGED
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.4.0
|
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-07-
|
11
|
+
date: 2021-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -53,11 +53,13 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
description: |
|
56
|
-
This
|
57
|
-
|
58
|
-
|
59
|
-
other implementations this client respects
|
60
|
-
limits for each method
|
56
|
+
This Gem implements a TCP client with (optional) SSL support.
|
57
|
+
It is an easy to use, versatile configurable client that can correctly
|
58
|
+
handle time limits.
|
59
|
+
Unlike other implementations, this client respects
|
60
|
+
predefined/configurable time limits for each method
|
61
|
+
(`connect`, `read`, `write`). Deadlines for a sequence of read/write
|
62
|
+
actions can also be monitored.
|
61
63
|
email:
|
62
64
|
executables: []
|
63
65
|
extensions: []
|
@@ -70,6 +72,7 @@ files:
|
|
70
72
|
- lib/tcp-client.rb
|
71
73
|
- lib/tcp-client/address.rb
|
72
74
|
- lib/tcp-client/configuration.rb
|
75
|
+
- lib/tcp-client/deadline.rb
|
73
76
|
- lib/tcp-client/default_configuration.rb
|
74
77
|
- lib/tcp-client/errors.rb
|
75
78
|
- lib/tcp-client/mixin/io_with_deadline.rb
|
@@ -83,6 +86,7 @@ files:
|
|
83
86
|
- tcp-client.gemspec
|
84
87
|
- test/tcp-client/address_test.rb
|
85
88
|
- test/tcp-client/configuration_test.rb
|
89
|
+
- test/tcp-client/deadline_test.rb
|
86
90
|
- test/tcp-client/default_configuration_test.rb
|
87
91
|
- test/tcp-client/version_test.rb
|
88
92
|
- test/tcp_client_test.rb
|
@@ -114,6 +118,7 @@ summary: A TCP client implementation with working timeout support.
|
|
114
118
|
test_files:
|
115
119
|
- test/tcp-client/address_test.rb
|
116
120
|
- test/tcp-client/configuration_test.rb
|
121
|
+
- test/tcp-client/deadline_test.rb
|
117
122
|
- test/tcp-client/default_configuration_test.rb
|
118
123
|
- test/tcp-client/version_test.rb
|
119
124
|
- test/tcp_client_test.rb
|