tcp-client 0.4.1 → 0.7.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/lib/tcp-client/configuration.rb +7 -7
- data/lib/tcp-client/deadline.rb +3 -1
- data/lib/tcp-client/errors.rb +20 -8
- data/lib/tcp-client/mixin/io_with_deadline.rb +29 -1
- data/lib/tcp-client/ssl_socket.rb +4 -6
- data/lib/tcp-client/tcp_socket.rb +8 -9
- data/lib/tcp-client/version.rb +1 -1
- data/lib/tcp-client.rb +50 -37
- data/lib/tcp_client.rb +2 -0
- data/sample/google.rb +14 -15
- data/sample/google_ssl.rb +18 -13
- data/tcp-client.gemspec +5 -3
- data/test/helper.rb +41 -0
- data/test/tcp-client/address_test.rb +2 -4
- data/test/tcp-client/configuration_test.rb +2 -4
- data/test/tcp-client/deadline_test.rb +11 -11
- data/test/tcp-client/default_configuration_test.rb +1 -1
- data/test/tcp-client/version_test.rb +2 -4
- data/test/tcp_client_test.rb +51 -30
- metadata +8 -6
- data/test/test_helper.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 582575d6b5a66a264900141fd5010a1a80a363e15d0ca369f262889d99d932d0
|
4
|
+
data.tar.gz: a0c2335a50addbf5f424d869cd2931256a1334a0a075530ef3873d9065246834
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b160bd656c4f6091a669cf65349e54aeaa2e7c057e0a5a3c896edf5e8ab91166a3a5cdafedfe84e4b04d5a79e0602fb2433c6ff9144e6881746f3607b8d8290b
|
7
|
+
data.tar.gz: 6726ebdfbc777258e62b3d915fa7e4a57f7ae514d6259ce5f0051ee649294142acf09faac490901e32083072d7a68dd4ce71fa697e0bf05c0d9556c0d57bb6a5
|
@@ -37,7 +37,7 @@ class TCPClient
|
|
37
37
|
|
38
38
|
def initialize_copy(_org)
|
39
39
|
super
|
40
|
-
@ssl_params = @ssl_params
|
40
|
+
@ssl_params = Hash[@ssl_params] if @ssl_params
|
41
41
|
self
|
42
42
|
end
|
43
43
|
|
@@ -48,7 +48,7 @@ class TCPClient
|
|
48
48
|
def ssl=(value)
|
49
49
|
@ssl_params =
|
50
50
|
if Hash === value
|
51
|
-
value
|
51
|
+
Hash[value]
|
52
52
|
else
|
53
53
|
value ? {} : nil
|
54
54
|
end
|
@@ -83,23 +83,23 @@ class TCPClient
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def timeout_error=(exception)
|
86
|
-
raise(
|
86
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
87
87
|
@connect_timeout_error =
|
88
88
|
@read_timeout_error = @write_timeout_error = exception
|
89
89
|
end
|
90
90
|
|
91
91
|
def connect_timeout_error=(exception)
|
92
|
-
raise(
|
92
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
93
93
|
@connect_timeout_error = exception
|
94
94
|
end
|
95
95
|
|
96
96
|
def read_timeout_error=(exception)
|
97
|
-
raise(
|
97
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
98
98
|
@read_timeout_error = exception
|
99
99
|
end
|
100
100
|
|
101
101
|
def write_timeout_error=(exception)
|
102
|
-
raise(
|
102
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
103
103
|
@write_timeout_error = exception
|
104
104
|
end
|
105
105
|
|
@@ -136,7 +136,7 @@ class TCPClient
|
|
136
136
|
def set(attribute, value)
|
137
137
|
public_send("#{attribute}=", value)
|
138
138
|
rescue NoMethodError
|
139
|
-
raise(
|
139
|
+
raise(UnknownAttributeError, attribute)
|
140
140
|
end
|
141
141
|
|
142
142
|
def seconds(value)
|
data/lib/tcp-client/deadline.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class TCPClient
|
4
4
|
class Deadline
|
5
|
-
MONOTONIC =
|
5
|
+
MONOTONIC = defined?(Process::CLOCK_MONOTONIC) ? true : false
|
6
6
|
|
7
7
|
def initialize(timeout)
|
8
8
|
timeout = timeout&.to_f
|
@@ -29,4 +29,6 @@ class TCPClient
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
private_constant(:Deadline)
|
32
34
|
end
|
data/lib/tcp-client/errors.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class TCPClient
|
4
|
-
class
|
4
|
+
class NoOpenSSLError < RuntimeError
|
5
5
|
def initialize
|
6
6
|
super('OpenSSL is not available')
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
-
class
|
10
|
+
class NoBlockGivenError < ArgumentError
|
11
11
|
def initialize
|
12
12
|
super('no block given')
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
class
|
16
|
+
class InvalidDeadLineError < ArgumentError
|
17
17
|
def initialize(timeout)
|
18
18
|
super("invalid deadline - #{timeout}")
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
class
|
22
|
+
class UnknownAttributeError < ArgumentError
|
23
23
|
def initialize(attribute)
|
24
24
|
super("unknown attribute - #{attribute}")
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
class
|
28
|
+
class NotAnExceptionError < TypeError
|
29
29
|
def initialize(object)
|
30
30
|
super("exception class required - #{object.inspect}")
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
class
|
34
|
+
class NotConnectedError < IOError
|
35
35
|
def initialize
|
36
36
|
super('client not connected')
|
37
37
|
end
|
@@ -65,6 +65,18 @@ class TCPClient
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
|
69
|
-
|
68
|
+
NoOpenSSL = NoOpenSSLError
|
69
|
+
NoBlockGiven = NoBlockGivenError
|
70
|
+
InvalidDeadLine = InvalidDeadLineError
|
71
|
+
UnknownAttribute = UnknownAttributeError
|
72
|
+
NotAnException = NotAnExceptionError
|
73
|
+
NotConnected = NotConnectedError
|
74
|
+
deprecate_constant(
|
75
|
+
:NoOpenSSL,
|
76
|
+
:NoBlockGiven,
|
77
|
+
:InvalidDeadLine,
|
78
|
+
:UnknownAttribute,
|
79
|
+
:NotAnException,
|
80
|
+
:NotConnected
|
81
|
+
)
|
70
82
|
end
|
@@ -5,6 +5,8 @@ module IOWithDeadlineMixin
|
|
5
5
|
methods = mod.instance_methods
|
6
6
|
if methods.index(:wait_writable) && methods.index(:wait_readable)
|
7
7
|
mod.include(ViaWaitMethod)
|
8
|
+
elsif methods.index(:to_io)
|
9
|
+
mod.include(ViaIOWaitMethod)
|
8
10
|
else
|
9
11
|
mod.include(ViaSelect)
|
10
12
|
end
|
@@ -12,6 +14,13 @@ module IOWithDeadlineMixin
|
|
12
14
|
|
13
15
|
def read_with_deadline(bytes_to_read, deadline, exception)
|
14
16
|
raise(exception) unless deadline.remaining_time
|
17
|
+
if bytes_to_read.nil?
|
18
|
+
return(
|
19
|
+
with_deadline(deadline, exception) do
|
20
|
+
read_nonblock(65_536, exception: false)
|
21
|
+
end
|
22
|
+
)
|
23
|
+
end
|
15
24
|
result = ''.b
|
16
25
|
while result.bytesize < bytes_to_read
|
17
26
|
read =
|
@@ -59,6 +68,25 @@ module IOWithDeadlineMixin
|
|
59
68
|
end
|
60
69
|
end
|
61
70
|
|
71
|
+
module ViaIOWaitMethod
|
72
|
+
private def with_deadline(deadline, exception)
|
73
|
+
loop do
|
74
|
+
case ret = yield
|
75
|
+
when :wait_writable
|
76
|
+
remaining_time = deadline.remaining_time or raise(exception)
|
77
|
+
raise(exception) if to_io.wait_writable(remaining_time).nil?
|
78
|
+
when :wait_readable
|
79
|
+
remaining_time = deadline.remaining_time or raise(exception)
|
80
|
+
raise(exception) if to_io.wait_readable(remaining_time).nil?
|
81
|
+
else
|
82
|
+
return ret
|
83
|
+
end
|
84
|
+
end
|
85
|
+
rescue Errno::ETIMEDOUT
|
86
|
+
raise(exception)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
62
90
|
module ViaSelect
|
63
91
|
private def with_deadline(deadline, exception)
|
64
92
|
loop do
|
@@ -78,5 +106,5 @@ module IOWithDeadlineMixin
|
|
78
106
|
end
|
79
107
|
end
|
80
108
|
|
81
|
-
private_constant(:ViaWaitMethod, :ViaSelect)
|
109
|
+
private_constant(:ViaWaitMethod, :ViaIOWaitMethod, :ViaSelect)
|
82
110
|
end
|
@@ -13,12 +13,11 @@ class TCPClient
|
|
13
13
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
14
14
|
include IOWithDeadlineMixin
|
15
15
|
|
16
|
-
def initialize(socket, address, configuration, exception)
|
16
|
+
def initialize(socket, address, configuration, deadline, exception)
|
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
|
-
deadline = Deadline.new(configuration.connect_timeout)
|
22
21
|
deadline.valid? ? connect_with_deadline(deadline, exception) : connect
|
23
22
|
post_connection_check(address.hostname) if should_verify?(ssl_params)
|
24
23
|
end
|
@@ -26,9 +25,7 @@ class TCPClient
|
|
26
25
|
private
|
27
26
|
|
28
27
|
def create_context(ssl_params)
|
29
|
-
|
30
|
-
context.set_params(ssl_params)
|
31
|
-
context
|
28
|
+
OpenSSL::SSL::SSLContext.new.tap { |ctx| ctx.set_params(ssl_params) }
|
32
29
|
end
|
33
30
|
|
34
31
|
def connect_with_deadline(deadline, exception)
|
@@ -36,7 +33,8 @@ class TCPClient
|
|
36
33
|
end
|
37
34
|
|
38
35
|
def should_verify?(ssl_params)
|
39
|
-
ssl_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE
|
36
|
+
ssl_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE &&
|
37
|
+
context.verify_hostname
|
40
38
|
end
|
41
39
|
end
|
42
40
|
|
@@ -8,21 +8,20 @@ class TCPClient
|
|
8
8
|
class TCPSocket < ::Socket
|
9
9
|
include IOWithDeadlineMixin
|
10
10
|
|
11
|
-
def initialize(address, configuration, exception)
|
11
|
+
def initialize(address, configuration, deadline, exception)
|
12
12
|
super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
|
13
13
|
configure(configuration)
|
14
|
-
deadline
|
15
|
-
connect_to(address, deadline, exception)
|
14
|
+
connect_to(as_addr_in(address), deadline, exception)
|
16
15
|
end
|
17
16
|
|
18
17
|
private
|
19
18
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
def as_addr_in(address)
|
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)
|
26
25
|
return connect(addr) unless deadline.valid?
|
27
26
|
with_deadline(deadline, exception) do
|
28
27
|
connect_nonblock(addr, exception: false)
|
data/lib/tcp-client/version.rb
CHANGED
data/lib/tcp-client.rb
CHANGED
@@ -10,33 +10,46 @@ require_relative 'tcp-client/default_configuration'
|
|
10
10
|
require_relative 'tcp-client/version'
|
11
11
|
|
12
12
|
class TCPClient
|
13
|
-
def self.open(
|
13
|
+
def self.open(address, configuration = Configuration.default)
|
14
14
|
client = new
|
15
|
-
client.connect(Address.new(
|
15
|
+
client.connect(Address.new(address), configuration)
|
16
16
|
block_given? ? yield(client) : client
|
17
17
|
ensure
|
18
|
-
client
|
18
|
+
client.close if block_given?
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
def self.with_deadline(
|
22
|
+
timeout,
|
23
|
+
address,
|
24
|
+
configuration = Configuration.default
|
25
|
+
)
|
26
|
+
client = nil
|
27
|
+
raise(NoBlockGivenError) unless block_given?
|
28
|
+
address = Address.new(address)
|
29
|
+
client = new
|
30
|
+
client.with_deadline(timeout) do
|
31
|
+
yield(client.connect(address, configuration))
|
32
|
+
end
|
33
|
+
ensure
|
34
|
+
client&.close
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :address, :configuration
|
22
38
|
|
23
39
|
def initialize
|
24
|
-
@socket = @address = @deadline = @
|
40
|
+
@socket = @address = @deadline = @configuration = nil
|
25
41
|
end
|
26
42
|
|
27
43
|
def to_s
|
28
|
-
@address
|
44
|
+
@address&.to_s || ''
|
29
45
|
end
|
30
46
|
|
31
|
-
def connect(
|
32
|
-
|
33
|
-
|
34
|
-
@address = Address.new(
|
35
|
-
@
|
36
|
-
|
37
|
-
@socket = TCPSocket.new(@address, @cfg, exception)
|
38
|
-
@cfg.ssl? &&
|
39
|
-
@socket = SSLSocket.new(@socket, @address, configuration, exception)
|
47
|
+
def connect(address, configuration, timeout: nil, exception: nil)
|
48
|
+
close if @socket
|
49
|
+
raise(NoOpenSSLError) if configuration.ssl? && !defined?(SSLSocket)
|
50
|
+
@address = Address.new(address)
|
51
|
+
@configuration = configuration.dup.freeze
|
52
|
+
@socket = create_socket(timeout, exception)
|
40
53
|
self
|
41
54
|
end
|
42
55
|
|
@@ -55,48 +68,48 @@ class TCPClient
|
|
55
68
|
|
56
69
|
def with_deadline(timeout)
|
57
70
|
previous_deadline = @deadline
|
58
|
-
raise(
|
71
|
+
raise(NoBlockGivenError) unless block_given?
|
59
72
|
@deadline = Deadline.new(timeout)
|
60
|
-
raise(
|
73
|
+
raise(InvalidDeadLineError, timeout) unless @deadline.valid?
|
61
74
|
yield(self)
|
62
75
|
ensure
|
63
76
|
@deadline = previous_deadline
|
64
77
|
end
|
65
78
|
|
66
|
-
def read(nbytes, timeout: nil, exception: nil)
|
67
|
-
raise(
|
68
|
-
|
69
|
-
return read_with_deadline(nbytes, @deadline, exception)
|
70
|
-
deadline = Deadline.new(timeout || @cfg.read_timeout)
|
79
|
+
def read(nbytes = nil, timeout: nil, exception: nil)
|
80
|
+
raise(NotConnectedError) if closed?
|
81
|
+
deadline = create_deadline(timeout, configuration.read_timeout)
|
71
82
|
return @socket.read(nbytes) unless deadline.valid?
|
72
|
-
|
83
|
+
exception ||= configuration.read_timeout_error
|
84
|
+
@socket.read_with_deadline(nbytes, deadline, exception)
|
73
85
|
end
|
74
86
|
|
75
87
|
def write(*msg, timeout: nil, exception: nil)
|
76
|
-
raise(
|
77
|
-
|
78
|
-
return write_with_deadline(msg, @deadline, exception)
|
79
|
-
deadline = Deadline.new(timeout || @cfg.read_timeout)
|
88
|
+
raise(NotConnectedError) if closed?
|
89
|
+
deadline = create_deadline(timeout, configuration.write_timeout)
|
80
90
|
return @socket.write(*msg) unless deadline.valid?
|
81
|
-
|
91
|
+
exception ||= configuration.write_timeout_error
|
92
|
+
msg.sum do |chunk|
|
93
|
+
@socket.write_with_deadline(chunk.b, deadline, exception)
|
94
|
+
end
|
82
95
|
end
|
83
96
|
|
84
97
|
def flush
|
85
|
-
@socket
|
98
|
+
@socket&.flush
|
86
99
|
self
|
87
100
|
end
|
88
101
|
|
89
102
|
private
|
90
103
|
|
91
|
-
def
|
92
|
-
|
93
|
-
@socket.read_with_deadline(nbytes, deadline, exception)
|
104
|
+
def create_deadline(timeout, default)
|
105
|
+
timeout.nil? && @deadline ? @deadline : Deadline.new(timeout || default)
|
94
106
|
end
|
95
107
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
108
|
+
def create_socket(timeout, exception)
|
109
|
+
deadline = create_deadline(timeout, configuration.connect_timeout)
|
110
|
+
exception ||= configuration.connect_timeout_error
|
111
|
+
@socket = TCPSocket.new(address, configuration, deadline, exception)
|
112
|
+
return @socket unless configuration.ssl?
|
113
|
+
SSLSocket.new(@socket, address, configuration, deadline, exception)
|
101
114
|
end
|
102
115
|
end
|
data/lib/tcp_client.rb
CHANGED
data/sample/google.rb
CHANGED
@@ -2,21 +2,20 @@
|
|
2
2
|
|
3
3
|
require_relative '../lib/tcp-client'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# + 0.5 seconds to read a response
|
5
|
+
# global configuration.
|
6
|
+
# - 0.5 seconds to connect the server
|
7
|
+
# - 0.25 seconds to write a single data junk
|
8
|
+
# - 0.25 seconds to read some bytes
|
9
|
+
TCPClient.configure do |cfg|
|
10
|
+
cfg.connect_timeout = 0.5
|
11
|
+
cfg.write_timeout = 0.25
|
12
|
+
cfg.read_timeout = 0.25
|
13
|
+
end
|
15
14
|
|
15
|
+
# request to Google:
|
16
|
+
# - send a simple HTTP get request
|
17
|
+
# - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
|
16
18
|
TCPClient.open('www.google.com:80') do |client|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# read "HTTP/1.1 " + 3 byte HTTP status code
|
21
|
-
pp client.read(12)
|
19
|
+
p client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
20
|
+
p client.read(12)
|
22
21
|
end
|
data/sample/google_ssl.rb
CHANGED
@@ -2,18 +2,23 @@
|
|
2
2
|
|
3
3
|
require_relative '../lib/tcp-client'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
# create a configuration.
|
6
|
+
# - use TLS 1.2
|
7
|
+
# - don't use internal buffering
|
8
|
+
cfg =
|
9
|
+
TCPClient::Configuration.create(
|
10
|
+
buffered: false,
|
11
|
+
ssl_params: {
|
12
|
+
ssl_version: :TLSv1_2
|
13
|
+
}
|
14
|
+
)
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
# request to Google:
|
17
|
+
# - limit all interactions to 0.5 seconds
|
18
|
+
# - use the Configuration cfg
|
19
|
+
# - send a simple HTTP get request
|
20
|
+
# - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
|
21
|
+
TCPClient.with_deadline(0.5, 'www.google.com:443', cfg) do |client|
|
22
|
+
p client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
23
|
+
p client.read(12)
|
19
24
|
end
|
data/tcp-client.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative './lib/tcp-client/version'
|
4
4
|
|
5
|
-
|
5
|
+
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'tcp-client'
|
7
7
|
spec.version = TCPClient::VERSION
|
8
8
|
spec.author = 'Mike Blumtritt'
|
@@ -20,9 +20,11 @@ GemSpec = Gem::Specification.new do |spec|
|
|
20
20
|
actions can also be monitored.
|
21
21
|
DESCRIPTION
|
22
22
|
spec.homepage = 'https://github.com/mblumtritt/tcp-client'
|
23
|
+
spec.license = 'BSD-3-Clause'
|
23
24
|
|
24
25
|
spec.metadata['source_code_uri'] = 'https://github.com/mblumtritt/tcp-client'
|
25
|
-
spec.metadata['bug_tracker_uri'] =
|
26
|
+
spec.metadata['bug_tracker_uri'] =
|
27
|
+
'https://github.com/mblumtritt/tcp-client/issues'
|
26
28
|
|
27
29
|
spec.add_development_dependency 'bundler'
|
28
30
|
spec.add_development_dependency 'minitest'
|
@@ -32,5 +34,5 @@ GemSpec = Gem::Specification.new do |spec|
|
|
32
34
|
spec.test_files = all_files.grep(%r{^test/})
|
33
35
|
spec.files = all_files - spec.test_files
|
34
36
|
|
35
|
-
spec.extra_rdoc_files = %w[README.md]
|
37
|
+
spec.extra_rdoc_files = %w[README.md LICENSE]
|
36
38
|
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/parallel'
|
5
|
+
require_relative '../lib/tcp-client'
|
6
|
+
|
7
|
+
# this pseudo-server never reads or writes anything
|
8
|
+
PseudoServer = TCPServer.new('localhost', 0)
|
9
|
+
Minitest.after_run { PseudoServer.close }
|
10
|
+
|
11
|
+
class Test < MiniTest::Test
|
12
|
+
parallelize_me!
|
13
|
+
end
|
14
|
+
|
15
|
+
class Timing
|
16
|
+
def initialize
|
17
|
+
@start_time = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def started?
|
21
|
+
@start_time != nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
@start_time = now
|
26
|
+
end
|
27
|
+
|
28
|
+
def elapsed
|
29
|
+
now - @start_time
|
30
|
+
end
|
31
|
+
|
32
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
33
|
+
def now
|
34
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
def now
|
38
|
+
::Time.now
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../
|
4
|
-
|
5
|
-
class AddressTest < MiniTest::Test
|
6
|
-
parallelize_me!
|
3
|
+
require_relative '../helper'
|
7
4
|
|
5
|
+
class AddressTest < Test
|
8
6
|
def test_create_from_integer
|
9
7
|
subject = TCPClient::Address.new(42)
|
10
8
|
assert_equal('localhost:42', subject.to_s)
|
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../
|
4
|
-
|
5
|
-
class ConfigurationTest < MiniTest::Test
|
6
|
-
parallelize_me!
|
3
|
+
require_relative '../helper'
|
7
4
|
|
5
|
+
class ConfigurationTest < Test
|
8
6
|
def test_defaults
|
9
7
|
subject = TCPClient::Configuration.new
|
10
8
|
assert(subject.buffered)
|
@@ -1,25 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../
|
3
|
+
require_relative '../helper'
|
4
4
|
|
5
|
-
class
|
6
|
-
|
5
|
+
class DeadlineTest < Test
|
6
|
+
Deadline = TCPClient.const_get(:Deadline)
|
7
7
|
|
8
8
|
def test_validity
|
9
|
-
assert(
|
10
|
-
assert(
|
9
|
+
assert(Deadline.new(1).valid?)
|
10
|
+
assert(Deadline.new(0.0001).valid?)
|
11
11
|
|
12
|
-
refute(
|
13
|
-
refute(
|
12
|
+
refute(Deadline.new(0).valid?)
|
13
|
+
refute(Deadline.new(nil).valid?)
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_remaining_time
|
17
|
-
assert(
|
17
|
+
assert(Deadline.new(1).remaining_time > 0)
|
18
18
|
|
19
|
-
assert_nil(
|
20
|
-
assert_nil(
|
19
|
+
assert_nil(Deadline.new(0).remaining_time)
|
20
|
+
assert_nil(Deadline.new(nil).remaining_time)
|
21
21
|
|
22
|
-
deadline =
|
22
|
+
deadline = Deadline.new(0.2)
|
23
23
|
sleep(0.2)
|
24
24
|
assert_nil(deadline.remaining_time)
|
25
25
|
end
|
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../
|
4
|
-
|
5
|
-
class VersionTest < MiniTest::Test
|
6
|
-
parallelize_me!
|
3
|
+
require_relative '../helper'
|
7
4
|
|
5
|
+
class VersionTest < Test
|
8
6
|
def test_format
|
9
7
|
assert_match(/\d+\.\d+\.\d+/, TCPClient::VERSION)
|
10
8
|
end
|
data/test/tcp_client_test.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'helper'
|
4
4
|
|
5
|
-
class TCPClientTest <
|
6
|
-
|
7
|
-
|
8
|
-
HUGE_AMOUNT_OF_DATA = Array.new(2024, '?' * 1024).freeze
|
5
|
+
class TCPClientTest < Test
|
6
|
+
HUGE_AMOUNT_OF_DATA = Array.new(1024, '?' * 2048).freeze
|
9
7
|
|
10
8
|
attr_reader :config
|
11
9
|
|
@@ -17,20 +15,26 @@ class TCPClientTest < MiniTest::Test
|
|
17
15
|
PseudoServer.local_address.ip_port
|
18
16
|
end
|
19
17
|
|
18
|
+
def address
|
19
|
+
"localhost:#{port}"
|
20
|
+
end
|
21
|
+
|
20
22
|
def test_defaults
|
21
23
|
subject = TCPClient.new
|
22
24
|
assert(subject.closed?)
|
23
25
|
assert_equal('', subject.to_s)
|
24
26
|
assert_nil(subject.address)
|
25
27
|
subject.close
|
26
|
-
assert_raises(TCPClient::
|
27
|
-
|
28
|
+
assert_raises(TCPClient::NotConnectedError) do
|
29
|
+
subject.write('hello world!')
|
30
|
+
end
|
31
|
+
assert_raises(TCPClient::NotConnectedError) { subject.read(42) }
|
28
32
|
end
|
29
33
|
|
30
34
|
def create_nonconnected_client
|
31
35
|
client = TCPClient.new
|
32
36
|
client.connect('', config)
|
33
|
-
|
37
|
+
:you_should_not_get_this
|
34
38
|
rescue Errno::EADDRNOTAVAIL
|
35
39
|
client
|
36
40
|
end
|
@@ -44,17 +48,19 @@ class TCPClientTest < MiniTest::Test
|
|
44
48
|
assert_equal('localhost', subject.address.hostname)
|
45
49
|
assert_instance_of(Addrinfo, subject.address.addrinfo)
|
46
50
|
assert_same(0, subject.address.addrinfo.ip_port)
|
47
|
-
assert_raises(TCPClient::
|
48
|
-
|
51
|
+
assert_raises(TCPClient::NotConnectedError) do
|
52
|
+
subject.write('hello world!')
|
53
|
+
end
|
54
|
+
assert_raises(TCPClient::NotConnectedError) { subject.read(42) }
|
49
55
|
end
|
50
56
|
|
51
57
|
def test_connected_state
|
52
|
-
TCPClient.open(
|
58
|
+
TCPClient.open(address, config) do |subject|
|
53
59
|
refute(subject.closed?)
|
54
|
-
assert_equal(
|
60
|
+
assert_equal(address, subject.to_s)
|
55
61
|
refute_nil(subject.address)
|
56
62
|
address_when_opened = subject.address
|
57
|
-
assert_equal(
|
63
|
+
assert_equal(address, subject.address.to_s)
|
58
64
|
assert_equal('localhost', subject.address.hostname)
|
59
65
|
assert_instance_of(Addrinfo, subject.address.addrinfo)
|
60
66
|
assert_same(port, subject.address.addrinfo.ip_port)
|
@@ -66,14 +72,14 @@ class TCPClientTest < MiniTest::Test
|
|
66
72
|
end
|
67
73
|
|
68
74
|
def check_read_timeout(timeout)
|
69
|
-
TCPClient.open(
|
75
|
+
TCPClient.open(address, config) do |subject|
|
70
76
|
refute(subject.closed?)
|
71
|
-
|
77
|
+
timing = Timing.new
|
72
78
|
assert_raises(TCPClient::ReadTimeoutError) do
|
73
|
-
|
79
|
+
timing.start
|
74
80
|
subject.read(42, timeout: timeout)
|
75
81
|
end
|
76
|
-
assert_in_delta(timeout,
|
82
|
+
assert_in_delta(timeout, timing.elapsed, 0.15)
|
77
83
|
end
|
78
84
|
end
|
79
85
|
|
@@ -84,14 +90,14 @@ class TCPClientTest < MiniTest::Test
|
|
84
90
|
end
|
85
91
|
|
86
92
|
def check_write_timeout(timeout)
|
87
|
-
TCPClient.open(
|
93
|
+
TCPClient.open(address, config) do |subject|
|
88
94
|
refute(subject.closed?)
|
89
|
-
|
95
|
+
timing = Timing.new
|
90
96
|
assert_raises(TCPClient::WriteTimeoutError) do
|
91
|
-
|
97
|
+
timing.start
|
92
98
|
subject.write(*HUGE_AMOUNT_OF_DATA, timeout: timeout)
|
93
99
|
end
|
94
|
-
assert_in_delta(timeout,
|
100
|
+
assert_in_delta(timeout, timing.elapsed, 0.15)
|
95
101
|
end
|
96
102
|
end
|
97
103
|
|
@@ -101,7 +107,7 @@ class TCPClientTest < MiniTest::Test
|
|
101
107
|
end
|
102
108
|
|
103
109
|
def test_write_deadline
|
104
|
-
TCPClient.open(
|
110
|
+
TCPClient.open(address, config) do |subject|
|
105
111
|
refute(subject.closed?)
|
106
112
|
assert_raises(TCPClient::WriteTimeoutError) do
|
107
113
|
subject.with_deadline(0.25) do |*args|
|
@@ -113,25 +119,25 @@ class TCPClientTest < MiniTest::Test
|
|
113
119
|
end
|
114
120
|
|
115
121
|
def test_read_deadline
|
116
|
-
TCPClient.open(
|
122
|
+
TCPClient.open(address, config) do |subject|
|
117
123
|
refute(subject.closed?)
|
118
124
|
assert_raises(TCPClient::ReadTimeoutError) do
|
119
125
|
subject.with_deadline(0.25) do |*args|
|
120
126
|
assert_equal([subject], args)
|
121
|
-
loop { subject.read(
|
127
|
+
loop { subject.read(42) }
|
122
128
|
end
|
123
129
|
end
|
124
130
|
end
|
125
131
|
end
|
126
132
|
|
127
133
|
def test_read_write_deadline
|
128
|
-
TCPClient.open(
|
134
|
+
TCPClient.open(address, config) do |subject|
|
129
135
|
refute(subject.closed?)
|
130
136
|
assert_raises(TCPClient::TimeoutError) do
|
131
137
|
subject.with_deadline(0.25) do |*args|
|
132
138
|
assert_equal([subject], args)
|
133
139
|
loop do
|
134
|
-
subject.write('
|
140
|
+
subject.write('some data')
|
135
141
|
subject.read(0)
|
136
142
|
end
|
137
143
|
end
|
@@ -140,12 +146,12 @@ class TCPClientTest < MiniTest::Test
|
|
140
146
|
end
|
141
147
|
|
142
148
|
def check_connect_timeout(ssl_config)
|
143
|
-
|
149
|
+
timing = Timing.new
|
144
150
|
assert_raises(TCPClient::ConnectTimeoutError) do
|
145
|
-
|
146
|
-
TCPClient.new.connect(
|
151
|
+
timing.start
|
152
|
+
TCPClient.new.connect(address, ssl_config)
|
147
153
|
end
|
148
|
-
assert_in_delta(ssl_config.connect_timeout,
|
154
|
+
assert_in_delta(ssl_config.connect_timeout, timing.elapsed, 0.25)
|
149
155
|
end
|
150
156
|
|
151
157
|
def test_connect_ssl_timeout
|
@@ -160,4 +166,19 @@ class TCPClientTest < MiniTest::Test
|
|
160
166
|
ssl_config.connect_timeout = 1.5
|
161
167
|
check_connect_timeout(ssl_config)
|
162
168
|
end
|
169
|
+
|
170
|
+
def test_deadline
|
171
|
+
assert(TCPClient.with_deadline(0.15, address, config, &:itself).closed?)
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_deadline_timeout
|
175
|
+
timing = Timing.new
|
176
|
+
assert_raises(TCPClient::ReadTimeoutError) do
|
177
|
+
timing.start
|
178
|
+
TCPClient.with_deadline(0.15, address, config) do |client|
|
179
|
+
client.read(42)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
assert_in_delta(0.15, timing.elapsed, 0.15)
|
183
|
+
end
|
163
184
|
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.7.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-
|
11
|
+
date: 2021-11-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -65,6 +65,7 @@ executables: []
|
|
65
65
|
extensions: []
|
66
66
|
extra_rdoc_files:
|
67
67
|
- README.md
|
68
|
+
- LICENSE
|
68
69
|
files:
|
69
70
|
- ".gitignore"
|
70
71
|
- LICENSE
|
@@ -85,15 +86,16 @@ files:
|
|
85
86
|
- sample/google.rb
|
86
87
|
- sample/google_ssl.rb
|
87
88
|
- tcp-client.gemspec
|
89
|
+
- test/helper.rb
|
88
90
|
- test/tcp-client/address_test.rb
|
89
91
|
- test/tcp-client/configuration_test.rb
|
90
92
|
- test/tcp-client/deadline_test.rb
|
91
93
|
- test/tcp-client/default_configuration_test.rb
|
92
94
|
- test/tcp-client/version_test.rb
|
93
95
|
- test/tcp_client_test.rb
|
94
|
-
- test/test_helper.rb
|
95
96
|
homepage: https://github.com/mblumtritt/tcp-client
|
96
|
-
licenses:
|
97
|
+
licenses:
|
98
|
+
- BSD-3-Clause
|
97
99
|
metadata:
|
98
100
|
source_code_uri: https://github.com/mblumtritt/tcp-client
|
99
101
|
bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
|
@@ -112,15 +114,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
114
|
- !ruby/object:Gem::Version
|
113
115
|
version: '0'
|
114
116
|
requirements: []
|
115
|
-
rubygems_version: 3.2.
|
117
|
+
rubygems_version: 3.2.28
|
116
118
|
signing_key:
|
117
119
|
specification_version: 4
|
118
120
|
summary: A TCP client implementation with working timeout support.
|
119
121
|
test_files:
|
122
|
+
- test/helper.rb
|
120
123
|
- test/tcp-client/address_test.rb
|
121
124
|
- test/tcp-client/configuration_test.rb
|
122
125
|
- test/tcp-client/deadline_test.rb
|
123
126
|
- test/tcp-client/default_configuration_test.rb
|
124
127
|
- test/tcp-client/version_test.rb
|
125
128
|
- test/tcp_client_test.rb
|
126
|
-
- test/test_helper.rb
|
data/test/test_helper.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'minitest/autorun'
|
4
|
-
require 'minitest/parallel'
|
5
|
-
require_relative '../lib/tcp-client'
|
6
|
-
|
7
|
-
# this pseudo-server never reads or writes anything
|
8
|
-
PseudoServer = TCPServer.new('localhost', 0)
|
9
|
-
Minitest.after_run { PseudoServer.close }
|