tcp-client 0.0.6 → 0.0.8
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.rb +11 -9
- data/lib/tcp-client/configuration.rb +1 -1
- data/lib/tcp-client/mixin/io_timeout.rb +22 -14
- data/lib/tcp-client/ssl_socket.rb +7 -1
- data/lib/tcp-client/tcp_socket.rb +6 -2
- data/lib/tcp-client/version.rb +1 -1
- data/rakefile.rb +3 -1
- data/tcp-client.gemspec +5 -6
- data/test/tcp_client_test.rb +35 -31
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f139f1f90fcb6d8c32a0d0fab85b4c96eab70b2a08845e9cdb26c5de8acfd184
|
4
|
+
data.tar.gz: f703e1c0c6f5edb4d732284ba983e31820c1e116cff1329cb7458a642b4ec1af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a19cf4e0ba023fd6a52a7470d1304b6718250c1dc28b49cefa07dc6c1fca6c52d35797bd6e769a80cd9d96feba09d48ebfcbc45273b2718f2fea8fda1de6d749
|
7
|
+
data.tar.gz: 1e0444b2cf3169b4d3e31af9ea072f0c44a98c88983f8f657f7917e1d4ee7d245599d7a640183acbb7bcbded8a319f2eed677c4f3aca8bdd15138c04acd13895
|
data/lib/tcp-client.rb
CHANGED
@@ -15,7 +15,7 @@ class TCPClient
|
|
15
15
|
|
16
16
|
class NotConnected < SocketError
|
17
17
|
def self.raise!(which)
|
18
|
-
raise(self,
|
18
|
+
raise(self, "client not connected - #{which}", caller(1))
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -25,11 +25,9 @@ class TCPClient
|
|
25
25
|
addr = Address.new(addr)
|
26
26
|
client = new
|
27
27
|
client.connect(addr, configuration)
|
28
|
-
|
29
|
-
client, ret = nil, client
|
30
|
-
ret
|
28
|
+
block_given? ? yield(client) : client
|
31
29
|
ensure
|
32
|
-
client
|
30
|
+
client&.close if block_given?
|
33
31
|
end
|
34
32
|
|
35
33
|
attr_reader :address
|
@@ -47,7 +45,9 @@ class TCPClient
|
|
47
45
|
NoOpenSSL.raise! if configuration.ssl? && !defined?(SSLSocket)
|
48
46
|
@address = Address.new(addr)
|
49
47
|
@socket = TCPSocket.new(@address, configuration, Timeout)
|
50
|
-
@socket = SSLSocket.new(
|
48
|
+
configuration.ssl? && @socket = SSLSocket.new(
|
49
|
+
@socket, @address, configuration, Timeout
|
50
|
+
)
|
51
51
|
@write_timeout = configuration.write_timeout
|
52
52
|
@read_timeout = configuration.read_timeout
|
53
53
|
self
|
@@ -55,7 +55,7 @@ class TCPClient
|
|
55
55
|
|
56
56
|
def close
|
57
57
|
socket, @socket = @socket, nil
|
58
|
-
socket
|
58
|
+
socket&.close
|
59
59
|
self
|
60
60
|
rescue IOError
|
61
61
|
self
|
@@ -66,11 +66,13 @@ class TCPClient
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def read(nbytes, timeout: @read_timeout)
|
69
|
-
|
69
|
+
NotConnected.raise!(self) if closed?
|
70
|
+
@socket.read(nbytes, timeout: timeout, exception: Timeout)
|
70
71
|
end
|
71
72
|
|
72
73
|
def write(*msg, timeout: @write_timeout)
|
73
|
-
|
74
|
+
NotConnected.raise!(self) if closed?
|
75
|
+
@socket.write(*msg, timeout: timeout, exception: Timeout)
|
74
76
|
end
|
75
77
|
|
76
78
|
def flush
|
@@ -3,7 +3,11 @@ IOTimeoutError = Class.new(IOError) unless defined?(IOTimeoutError)
|
|
3
3
|
module IOTimeoutMixin
|
4
4
|
def self.included(mod)
|
5
5
|
im = mod.instance_methods
|
6
|
-
|
6
|
+
if im.index(:wait_writable) && im.index(:wait_readable)
|
7
|
+
mod.include(DeadlineMethods)
|
8
|
+
else
|
9
|
+
mod.include(DeadlineIO)
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
def read(nbytes, timeout: nil, exception: IOTimeoutError)
|
@@ -11,7 +15,9 @@ module IOTimeoutMixin
|
|
11
15
|
return read_all(nbytes){ |junk_size| super(junk_size) } if timeout <= 0
|
12
16
|
deadline = Time.now + timeout
|
13
17
|
read_all(nbytes) do |junk_size|
|
14
|
-
with_deadline(deadline, exception)
|
18
|
+
with_deadline(deadline, exception) do
|
19
|
+
read_nonblock(junk_size, exception: false)
|
20
|
+
end
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
@@ -20,7 +26,9 @@ module IOTimeoutMixin
|
|
20
26
|
return write_all(msgs.join){ |junk| super(junk) } if timeout <= 0
|
21
27
|
deadline = Time.now + timeout
|
22
28
|
write_all(msgs.join) do |junk|
|
23
|
-
with_deadline(deadline, exception)
|
29
|
+
with_deadline(deadline, exception) do
|
30
|
+
write_nonblock(junk, exception: false)
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
|
@@ -50,18 +58,18 @@ module IOTimeoutMixin
|
|
50
58
|
end
|
51
59
|
end
|
52
60
|
|
53
|
-
module
|
61
|
+
module DeadlineMethods
|
54
62
|
private
|
55
63
|
|
56
64
|
def with_deadline(deadline, exclass)
|
57
65
|
loop do
|
58
66
|
case ret = yield
|
59
67
|
when :wait_writable
|
60
|
-
remaining_time = deadline - Time.now
|
61
|
-
raise(exclass) if
|
68
|
+
raise(exclass) if (remaining_time = deadline - Time.now) <= 0
|
69
|
+
raise(exclass) if wait_writable(remaining_time).nil?
|
62
70
|
when :wait_readable
|
63
|
-
remaining_time = deadline - Time.now
|
64
|
-
raise(exclass) if
|
71
|
+
raise(exclass) if (remaining_time = deadline - Time.now) <= 0
|
72
|
+
raise(exclass) if wait_readable(remaining_time).nil?
|
65
73
|
else
|
66
74
|
return ret
|
67
75
|
end
|
@@ -69,18 +77,18 @@ module IOTimeoutMixin
|
|
69
77
|
end
|
70
78
|
end
|
71
79
|
|
72
|
-
module
|
80
|
+
module DeadlineIO
|
73
81
|
private
|
74
82
|
|
75
83
|
def with_deadline(deadline, exclass)
|
76
84
|
loop do
|
77
85
|
case ret = yield
|
78
86
|
when :wait_writable
|
79
|
-
remaining_time = deadline - Time.now
|
80
|
-
raise(exclass) if
|
87
|
+
raise(exclass) if (remaining_time = deadline - Time.now) <= 0
|
88
|
+
raise(exclass) if ::IO.select(nil, [self], nil, remaining_time).nil?
|
81
89
|
when :wait_readable
|
82
|
-
remaining_time = deadline - Time.now
|
83
|
-
raise(exclass) if
|
90
|
+
raise(exclass) if (remaining_time = deadline - Time.now) <= 0
|
91
|
+
raise(exclass) if ::IO.select([self], nil, nil, remaining_time).nil?
|
84
92
|
else
|
85
93
|
return ret
|
86
94
|
end
|
@@ -88,5 +96,5 @@ module IOTimeoutMixin
|
|
88
96
|
end
|
89
97
|
end
|
90
98
|
|
91
|
-
private_constant :
|
99
|
+
private_constant :DeadlineMethods, :DeadlineIO
|
92
100
|
end
|
@@ -31,7 +31,13 @@ class TCPClient
|
|
31
31
|
|
32
32
|
def connect_to(address, check, timeout, exception)
|
33
33
|
self.hostname = address.hostname
|
34
|
-
|
34
|
+
if timeout
|
35
|
+
with_deadline(Time.now + timeout, exception) do
|
36
|
+
connect_nonblock(exception: false)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
connect
|
40
|
+
end
|
35
41
|
post_connection_check(address.hostname) if check
|
36
42
|
end
|
37
43
|
end
|
@@ -14,9 +14,13 @@ class TCPClient
|
|
14
14
|
private
|
15
15
|
|
16
16
|
def connect_to(address, timeout, exception)
|
17
|
-
addr = ::Socket.pack_sockaddr_in(
|
17
|
+
addr = ::Socket.pack_sockaddr_in(
|
18
|
+
address.addrinfo.ip_port, address.addrinfo.ip_address
|
19
|
+
)
|
18
20
|
return connect(addr) unless timeout
|
19
|
-
with_deadline(Time.now + timeout, exception)
|
21
|
+
with_deadline(Time.now + timeout, exception) do
|
22
|
+
connect_nonblock(addr, exception: false)
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
26
|
def configure(configuration)
|
data/lib/tcp-client/version.rb
CHANGED
data/rakefile.rb
CHANGED
data/tcp-client.gemspec
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative './lib/tcp-client/version'
|
4
4
|
|
5
5
|
GemSpec = Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'tcp-client'
|
7
7
|
spec.version = TCPClient::VERSION
|
8
8
|
spec.summary = 'A TCP client implementation with working timeout support.'
|
9
|
-
spec.description = <<~
|
9
|
+
spec.description = <<~DESCRIPTION
|
10
10
|
This gem implements a TCP client with (optional) SSL support. The
|
11
11
|
motivation of this project is the need to have a _really working_
|
12
12
|
easy to use client which can handle time limits correctly. Unlike
|
13
13
|
other implementations this client respects given/configurable time
|
14
14
|
limits for each method (`connect`, `read`, `write`).
|
15
|
-
|
15
|
+
DESCRIPTION
|
16
16
|
spec.author = 'Mike Blumtritt'
|
17
17
|
spec.email = 'mike.blumtritt@invision.de'
|
18
18
|
spec.homepage = 'https://github.com/mblumtritt/tcp-client'
|
@@ -20,12 +20,12 @@ GemSpec = Gem::Specification.new do |spec|
|
|
20
20
|
spec.rubyforge_project = spec.name
|
21
21
|
|
22
22
|
spec.add_development_dependency 'bundler'
|
23
|
-
spec.add_development_dependency 'rake'
|
24
23
|
spec.add_development_dependency 'minitest'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
25
|
|
26
26
|
spec.platform = Gem::Platform::RUBY
|
27
|
-
spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
28
27
|
spec.required_ruby_version = '>= 2.5.0'
|
28
|
+
spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
29
29
|
|
30
30
|
spec.require_paths = %w[lib]
|
31
31
|
|
@@ -33,6 +33,5 @@ GemSpec = Gem::Specification.new do |spec|
|
|
33
33
|
spec.test_files = all_files.grep(%r{^(spec|test)/})
|
34
34
|
spec.files = all_files - spec.test_files
|
35
35
|
|
36
|
-
spec.has_rdoc = false
|
37
36
|
spec.extra_rdoc_files = %w[README.md]
|
38
37
|
end
|
data/test/tcp_client_test.rb
CHANGED
@@ -40,24 +40,30 @@ class TCPClientTest < Test
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
def with_dummy_server(port)
|
44
|
+
# this server will never receive or send any data
|
45
|
+
server = TCPServer.new('localhost', port)
|
46
|
+
ensure
|
47
|
+
server&.close
|
48
|
+
end
|
49
|
+
|
43
50
|
def test_connected_state
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
with_dummy_server(1234) do
|
52
|
+
TCPClient.open('localhost:1234') do |subject|
|
53
|
+
refute(subject.closed?)
|
54
|
+
assert_equal('localhost:1234', subject.to_s)
|
55
|
+
refute_nil(subject.address)
|
56
|
+
address_when_opened = subject.address
|
57
|
+
assert_equal('localhost:1234', subject.address.to_s)
|
58
|
+
assert_equal('localhost', subject.address.hostname)
|
59
|
+
assert_instance_of(Addrinfo, subject.address.addrinfo)
|
60
|
+
assert_same(1234, subject.address.addrinfo.ip_port)
|
54
61
|
|
55
|
-
|
56
|
-
|
57
|
-
|
62
|
+
subject.close
|
63
|
+
assert(subject.closed?)
|
64
|
+
assert_same(address_when_opened, subject.address)
|
65
|
+
end
|
58
66
|
end
|
59
|
-
ensure
|
60
|
-
server.close if server
|
61
67
|
end
|
62
68
|
|
63
69
|
def check_read_write_timeout(addr, timeout)
|
@@ -66,7 +72,7 @@ class TCPClientTest < Test
|
|
66
72
|
start_time = nil
|
67
73
|
assert_raises(TCPClient::Timeout) do
|
68
74
|
start_time = Time.now
|
69
|
-
#
|
75
|
+
# send 1MB to avoid any TCP stack buffering
|
70
76
|
subject.write('?' * (1024 * 1024), timeout: timeout)
|
71
77
|
end
|
72
78
|
assert_in_delta(timeout, Time.now - start_time, 0.02)
|
@@ -79,32 +85,30 @@ class TCPClientTest < Test
|
|
79
85
|
end
|
80
86
|
|
81
87
|
def test_read_write_timeout
|
82
|
-
|
83
|
-
|
84
|
-
|
88
|
+
with_dummy_server(1235) do
|
89
|
+
[0.5, 1, 1.5].each do |timeout|
|
90
|
+
check_read_write_timeout('localhost:1235', timeout)
|
91
|
+
end
|
85
92
|
end
|
86
|
-
ensure
|
87
|
-
server.close if server
|
88
93
|
end
|
89
94
|
|
90
|
-
def check_connect_timeout(addr, config
|
95
|
+
def check_connect_timeout(addr, config)
|
91
96
|
start_time = nil
|
92
97
|
assert_raises(TCPClient::Timeout) do
|
93
98
|
start_time = Time.now
|
94
99
|
TCPClient.new.connect(addr, config)
|
95
100
|
end
|
96
|
-
assert_in_delta(
|
101
|
+
assert_in_delta(config.connect_timeout, Time.now - start_time, 0.02)
|
97
102
|
end
|
98
103
|
|
99
104
|
def test_connect_ssl_timeout
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
105
|
+
with_dummy_server(1236) do
|
106
|
+
config = TCPClient::Configuration.new
|
107
|
+
config.ssl = true
|
108
|
+
[0.5, 1, 1.5].each do |timeout|
|
109
|
+
config.timeout = timeout
|
110
|
+
check_connect_timeout('localhost:1236', config)
|
111
|
+
end
|
106
112
|
end
|
107
|
-
ensure
|
108
|
-
server.close if server
|
109
113
|
end
|
110
114
|
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.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Blumtritt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
104
|
version: 1.3.6
|
105
105
|
requirements: []
|
106
106
|
rubyforge_project: tcp-client
|
107
|
-
rubygems_version: 2.7.
|
107
|
+
rubygems_version: 2.7.7
|
108
108
|
signing_key:
|
109
109
|
specification_version: 4
|
110
110
|
summary: A TCP client implementation with working timeout support.
|