tcp-client 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/README.md +4 -4
- data/lib/tcp-client.rb +7 -6
- data/lib/tcp-client/address.rb +3 -2
- data/lib/tcp-client/mixin/io_timeout.rb +12 -12
- data/lib/tcp-client/ssl_socket.rb +4 -4
- data/lib/tcp-client/tcp_socket.rb +5 -4
- data/lib/tcp-client/version.rb +1 -1
- data/sample/google.rb +3 -3
- data/sample/google_ssl.rb +4 -4
- data/tcp-client.gemspec +1 -1
- data/test/tcp-client/address_test.rb +55 -0
- data/test/tcp_client_test.rb +6 -6
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1100d0c6684061225cff8107b369fe38d56454f3e5bb4c7efd4a7e2c07adc491
|
4
|
+
data.tar.gz: 584cda11bc5398b9dd1574ea1e459a4ec963cf73195a5a200bdd520aa9725ece
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eafb90f60c331022b7115d291ede9334a6963842ba453329dff11d2ae9c208c5cc8acf2afaa9f4b377d20a3bddb6c60ab7baeccc0bee77a3b17665a2df18587f
|
7
|
+
data.tar.gz: 7d174d7172e9d1da102da679741f655e8bda71f888e3b68868d1ec993cd9f82ba8ed6f185be58483296c77868eefce4c2897b79e2fcb1fe71b52c6774b05fb6a
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -11,14 +11,14 @@ This gem implements a TCP client with (optional) SSL support. The motivation of
|
|
11
11
|
configuration = TCPClient::Configuration.create do |cfg|
|
12
12
|
cfg.connect_timeout = 1 # second to connect the server
|
13
13
|
cfg.write_timeout = 0.25 # seconds to write a single data junk
|
14
|
-
cfg.read_timeout = 0.
|
15
|
-
cfg.ssl_params = {} # use
|
14
|
+
cfg.read_timeout = 0.5 # seconds to read some bytes
|
15
|
+
cfg.ssl_params = {ssl_version: :TLSv1_2} # use TLS 1.2
|
16
16
|
end
|
17
17
|
|
18
|
-
# the following request sequence is not allowed to last longer than
|
18
|
+
# the following request sequence is not allowed to last longer than 2 seconds:
|
19
19
|
# 1 second to connect (incl. SSL handshake etc.)
|
20
20
|
# + 0.25 seconds to write data
|
21
|
-
# + 0.
|
21
|
+
# + 0.5 seconds to read
|
22
22
|
# a response
|
23
23
|
TCPClient.open('www.google.com:443', configuration) do |client|
|
24
24
|
pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
|
data/lib/tcp-client.rb
CHANGED
@@ -19,6 +19,8 @@ class TCPClient
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
Timeout = Class.new(IOError)
|
23
|
+
|
22
24
|
def self.open(addr, configuration = Configuration.new)
|
23
25
|
addr = Address.new(addr)
|
24
26
|
client = new
|
@@ -44,8 +46,8 @@ class TCPClient
|
|
44
46
|
close
|
45
47
|
NoOpenSSL.raise! if configuration.ssl? && !defined?(SSLSocket)
|
46
48
|
@address = Address.new(addr)
|
47
|
-
@socket = TCPSocket.new(@address, configuration)
|
48
|
-
@socket = SSLSocket.new(@socket, @address, configuration) if configuration.ssl?
|
49
|
+
@socket = TCPSocket.new(@address, configuration, Timeout)
|
50
|
+
@socket = SSLSocket.new(@socket, @address, configuration, Timeout) if configuration.ssl?
|
49
51
|
@write_timeout = configuration.write_timeout
|
50
52
|
@read_timeout = configuration.read_timeout
|
51
53
|
self
|
@@ -64,11 +66,10 @@ class TCPClient
|
|
64
66
|
end
|
65
67
|
|
66
68
|
def read(nbytes, timeout: @read_timeout)
|
67
|
-
closed? ? NotConnected.raise!(self) : @socket.read(nbytes, timeout: timeout)
|
69
|
+
closed? ? NotConnected.raise!(self) : @socket.read(nbytes, timeout: timeout, exception: Timeout)
|
68
70
|
end
|
69
71
|
|
70
|
-
def write(*
|
71
|
-
closed? ? NotConnected.raise!(self) : @socket.write(*
|
72
|
+
def write(*msg, timeout: @write_timeout)
|
73
|
+
closed? ? NotConnected.raise!(self) : @socket.write(*msg, timeout: timeout, exception: Timeout)
|
72
74
|
end
|
73
75
|
end
|
74
|
-
|
data/lib/tcp-client/address.rb
CHANGED
@@ -13,7 +13,7 @@ class TCPClient
|
|
13
13
|
when Integer
|
14
14
|
init_from_addrinfo(Addrinfo.tcp(nil, addr))
|
15
15
|
when Addrinfo
|
16
|
-
init_from_addrinfo(
|
16
|
+
init_from_addrinfo(addr)
|
17
17
|
else
|
18
18
|
init_from_string(addr)
|
19
19
|
end
|
@@ -24,8 +24,9 @@ class TCPClient
|
|
24
24
|
|
25
25
|
def init_from_string(str)
|
26
26
|
@hostname, port = from_string(str.to_s)
|
27
|
+
return init_from_addrinfo(Addrinfo.tcp(nil, port)) unless @hostname
|
27
28
|
@addrinfo = Addrinfo.tcp(@hostname, port)
|
28
|
-
@to_s = "#{@hostname}:#{port}"
|
29
|
+
@to_s = @hostname.index(':') ? "[#{@hostname}]:#{port}" : "#{@hostname}:#{port}"
|
29
30
|
end
|
30
31
|
|
31
32
|
def init_from_selfclass(address)
|
@@ -6,21 +6,21 @@ module IOTimeoutMixin
|
|
6
6
|
mod.include(im.index(:wait_writable) && im.index(:wait_readable) ? WithDeadlineMethods : WidthDeadlineIO)
|
7
7
|
end
|
8
8
|
|
9
|
-
def read(nbytes, timeout: nil)
|
9
|
+
def read(nbytes, timeout: nil, exception: IOTimeoutError)
|
10
10
|
timeout = timeout.to_f
|
11
11
|
return read_all(nbytes){ |junk_size| super(junk_size) } if timeout <= 0
|
12
12
|
deadline = Time.now + timeout
|
13
13
|
read_all(nbytes) do |junk_size|
|
14
|
-
with_deadline(deadline){ read_nonblock(junk_size, exception: false) }
|
14
|
+
with_deadline(deadline, exception){ read_nonblock(junk_size, exception: false) }
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def write(*
|
18
|
+
def write(*msgs, timeout: nil, exception: IOTimeoutError)
|
19
19
|
timeout = timeout.to_f
|
20
|
-
return write_all(
|
20
|
+
return write_all(msgs.join){ |junk| super(junk) } if timeout <= 0
|
21
21
|
deadline = Time.now + timeout
|
22
|
-
write_all(
|
23
|
-
with_deadline(deadline){ write_nonblock(junk, exception: false) }
|
22
|
+
write_all(msgs.join) do |junk|
|
23
|
+
with_deadline(deadline, exception){ write_nonblock(junk, exception: false) }
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -53,15 +53,15 @@ module IOTimeoutMixin
|
|
53
53
|
module WithDeadlineMethods
|
54
54
|
private
|
55
55
|
|
56
|
-
def with_deadline(deadline)
|
56
|
+
def with_deadline(deadline, exclass)
|
57
57
|
loop do
|
58
58
|
case ret = yield
|
59
59
|
when :wait_writable
|
60
60
|
remaining_time = deadline - Time.now
|
61
|
-
raise(
|
61
|
+
raise(exclass) if remaining_time <= 0 || wait_writable(remaining_time).nil?
|
62
62
|
when :wait_readable
|
63
63
|
remaining_time = deadline - Time.now
|
64
|
-
raise(
|
64
|
+
raise(exclass) if remaining_time <= 0 || wait_readable(remaining_time).nil?
|
65
65
|
else
|
66
66
|
return ret
|
67
67
|
end
|
@@ -72,15 +72,15 @@ module IOTimeoutMixin
|
|
72
72
|
module WidthDeadlineIO
|
73
73
|
private
|
74
74
|
|
75
|
-
def with_deadline(deadline)
|
75
|
+
def with_deadline(deadline, exclass)
|
76
76
|
loop do
|
77
77
|
case ret = yield
|
78
78
|
when :wait_writable
|
79
79
|
remaining_time = deadline - Time.now
|
80
|
-
raise(
|
80
|
+
raise(exclass) if remaining_time <= 0 || ::IO.select(nil, [self], nil, remaining_time).nil?
|
81
81
|
when :wait_readable
|
82
82
|
remaining_time = deadline - Time.now
|
83
|
-
raise(
|
83
|
+
raise(exclass) if remaining_time <= 0 || ::IO.select([self], nil, nil, remaining_time).nil?
|
84
84
|
else
|
85
85
|
return ret
|
86
86
|
end
|
@@ -10,10 +10,10 @@ class TCPClient
|
|
10
10
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
11
11
|
include IOTimeoutMixin
|
12
12
|
|
13
|
-
def initialize(socket, address, configuration)
|
13
|
+
def initialize(socket, address, configuration, exception)
|
14
14
|
ssl_params = Hash[configuration.ssl_params]
|
15
15
|
super(socket, create_context(ssl_params))
|
16
|
-
connect_to(address, configuration.connect_timeout)
|
16
|
+
connect_to(address, configuration.connect_timeout, exception)
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
@@ -24,9 +24,9 @@ class TCPClient
|
|
24
24
|
ctx
|
25
25
|
end
|
26
26
|
|
27
|
-
def connect_to(address, timeout)
|
27
|
+
def connect_to(address, timeout, exception)
|
28
28
|
self.hostname = address.hostname
|
29
|
-
timeout ? with_deadline(Time.now + timeout){ connect_nonblock(exception: false) } : connect
|
29
|
+
timeout ? with_deadline(Time.now + timeout, exception){ connect_nonblock(exception: false) } : connect
|
30
30
|
post_connection_check(address.hostname)
|
31
31
|
end
|
32
32
|
end
|
@@ -5,17 +5,18 @@ class TCPClient
|
|
5
5
|
class TCPSocket < ::Socket
|
6
6
|
include IOTimeoutMixin
|
7
7
|
|
8
|
-
def initialize(address, configuration)
|
8
|
+
def initialize(address, configuration, exception)
|
9
9
|
super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
|
10
10
|
configure(configuration)
|
11
|
-
connect_to(address, configuration.connect_timeout)
|
11
|
+
connect_to(address, configuration.connect_timeout, exception)
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
def connect_to(address, timeout)
|
16
|
+
def connect_to(address, timeout, exception)
|
17
17
|
addr = ::Socket.pack_sockaddr_in(address.addrinfo.ip_port, address.addrinfo.ip_address)
|
18
|
-
|
18
|
+
return connect(addr) unless timeout
|
19
|
+
with_deadline(Time.now + timeout, exception){ connect_nonblock(addr, exception: false) }
|
19
20
|
end
|
20
21
|
|
21
22
|
def configure(configuration)
|
data/lib/tcp-client/version.rb
CHANGED
data/sample/google.rb
CHANGED
@@ -3,13 +3,13 @@ require_relative '../lib/tcp-client'
|
|
3
3
|
configuration = TCPClient::Configuration.create do |cfg|
|
4
4
|
cfg.connect_timeout = 0.5 # seconds to connect the server
|
5
5
|
cfg.write_timeout = 0.25 # seconds to write a single data junk
|
6
|
-
cfg.read_timeout = 0.
|
6
|
+
cfg.read_timeout = 0.5 # seconds to read some bytes
|
7
7
|
end
|
8
8
|
|
9
|
-
# the following request sequence is not allowed to last longer than
|
9
|
+
# the following request sequence is not allowed to last longer than 1.25 seconds:
|
10
10
|
# 0.5 seconds to connect
|
11
11
|
# + 0.25 seconds to write data
|
12
|
-
# + 0.
|
12
|
+
# + 0.5 seconds to read
|
13
13
|
# a response
|
14
14
|
TCPClient.open('www.google.com:80', configuration) do |client|
|
15
15
|
pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
|
data/sample/google_ssl.rb
CHANGED
@@ -3,14 +3,14 @@ require_relative '../lib/tcp-client'
|
|
3
3
|
configuration = TCPClient::Configuration.create do |cfg|
|
4
4
|
cfg.connect_timeout = 1 # second to connect the server
|
5
5
|
cfg.write_timeout = 0.25 # seconds to write a single data junk
|
6
|
-
cfg.read_timeout = 0.
|
7
|
-
cfg.ssl_params = {} # use
|
6
|
+
cfg.read_timeout = 0.5 # seconds to read some bytes
|
7
|
+
cfg.ssl_params = {ssl_version: :TLSv1_2} # use TLS 1.2
|
8
8
|
end
|
9
9
|
|
10
|
-
# the following request sequence is not allowed to last longer than
|
10
|
+
# the following request sequence is not allowed to last longer than 2 seconds:
|
11
11
|
# 1 second to connect (incl. SSL handshake etc.)
|
12
12
|
# + 0.25 seconds to write data
|
13
|
-
# + 0.
|
13
|
+
# + 0.5 seconds to read
|
14
14
|
# a response
|
15
15
|
TCPClient.open('www.google.com:443', configuration) do |client|
|
16
16
|
pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
|
data/tcp-client.gemspec
CHANGED
@@ -25,7 +25,7 @@ GemSpec = Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
spec.platform = Gem::Platform::RUBY
|
27
27
|
spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
28
|
-
spec.required_ruby_version = '>= 2.
|
28
|
+
spec.required_ruby_version = '>= 2.5.0'
|
29
29
|
|
30
30
|
spec.require_paths = %w[lib]
|
31
31
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class AddressTest < Test
|
4
|
+
def test_create_from_integer
|
5
|
+
subject = TCPClient::Address.new(42)
|
6
|
+
assert_equal('localhost:42', subject.to_s)
|
7
|
+
assert_equal('localhost', subject.hostname)
|
8
|
+
assert(subject.addrinfo.ip?)
|
9
|
+
assert(subject.addrinfo.ipv6?)
|
10
|
+
assert_same(42, subject.addrinfo.ip_port)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_create_from_addrinfo
|
14
|
+
addrinfo = Addrinfo.tcp('google.com', 42)
|
15
|
+
subject = TCPClient::Address.new(addrinfo)
|
16
|
+
assert_equal(addrinfo.getnameinfo[0], subject.hostname)
|
17
|
+
assert_equal(addrinfo, subject.addrinfo)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_create_from_str
|
21
|
+
subject = TCPClient::Address.new('localhost:42')
|
22
|
+
assert_equal('localhost:42', subject.to_s)
|
23
|
+
assert_equal('localhost', subject.hostname)
|
24
|
+
assert(subject.addrinfo.ip?)
|
25
|
+
assert(subject.addrinfo.ipv6?)
|
26
|
+
assert_same(42, subject.addrinfo.ip_port)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_create_from_str_short
|
30
|
+
subject = TCPClient::Address.new(':42')
|
31
|
+
assert_equal(':42', subject.to_s)
|
32
|
+
assert_empty(subject.hostname)
|
33
|
+
assert_same(42, subject.addrinfo.ip_port)
|
34
|
+
assert(subject.addrinfo.ip?)
|
35
|
+
assert(subject.addrinfo.ipv4?)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_create_from_str_ip6
|
39
|
+
subject = TCPClient::Address.new('[::1]:42')
|
40
|
+
assert_equal('[::1]:42', subject.to_s)
|
41
|
+
assert_equal('::1', subject.hostname)
|
42
|
+
assert_same(42, subject.addrinfo.ip_port)
|
43
|
+
assert(subject.addrinfo.ip?)
|
44
|
+
assert(subject.addrinfo.ipv6?)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_create_from_empty_str
|
48
|
+
subject = TCPClient::Address.new('')
|
49
|
+
assert_equal('localhost:0', subject.to_s)
|
50
|
+
assert_equal('localhost', subject.hostname)
|
51
|
+
assert_same(0, subject.addrinfo.ip_port)
|
52
|
+
assert(subject.addrinfo.ip?)
|
53
|
+
assert(subject.addrinfo.ipv6?)
|
54
|
+
end
|
55
|
+
end
|
data/test/tcp_client_test.rb
CHANGED
@@ -26,10 +26,10 @@ class TCPClientTest < Test
|
|
26
26
|
def test_failed_state
|
27
27
|
subject = create_nonconnected_client
|
28
28
|
assert(subject.closed?)
|
29
|
-
assert_equal(':0', subject.to_s)
|
29
|
+
assert_equal('localhost:0', subject.to_s)
|
30
30
|
refute_nil(subject.address)
|
31
|
-
assert_equal(':0', subject.address.to_s)
|
32
|
-
|
31
|
+
assert_equal('localhost:0', subject.address.to_s)
|
32
|
+
assert_equal('localhost', subject.address.hostname)
|
33
33
|
assert_instance_of(Addrinfo, subject.address.addrinfo)
|
34
34
|
assert_same(0, subject.address.addrinfo.ip_port)
|
35
35
|
assert_raises(TCPClient::NotConnected) do
|
@@ -64,13 +64,13 @@ class TCPClientTest < Test
|
|
64
64
|
TCPClient.open(addr) do |subject|
|
65
65
|
refute(subject.closed?)
|
66
66
|
start_time = nil
|
67
|
-
assert_raises(
|
67
|
+
assert_raises(TCPClient::Timeout) do
|
68
68
|
start_time = Time.now
|
69
69
|
# we need to send 1MB to avoid any TCP stack buffering
|
70
70
|
subject.write('?' * (1024 * 1024), timeout: timeout)
|
71
71
|
end
|
72
72
|
assert_in_delta(timeout, Time.now - start_time, 0.02)
|
73
|
-
assert_raises(
|
73
|
+
assert_raises(TCPClient::Timeout) do
|
74
74
|
start_time = Time.now
|
75
75
|
subject.read(42, timeout: timeout)
|
76
76
|
end
|
@@ -89,7 +89,7 @@ class TCPClientTest < Test
|
|
89
89
|
|
90
90
|
def check_connect_timeout(addr, config, timeout)
|
91
91
|
start_time = nil
|
92
|
-
assert_raises(
|
92
|
+
assert_raises(TCPClient::Timeout) do
|
93
93
|
start_time = Time.now
|
94
94
|
TCPClient.new.connect(addr, config)
|
95
95
|
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.4
|
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-02-
|
11
|
+
date: 2018-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -79,6 +79,7 @@ files:
|
|
79
79
|
- sample/google.rb
|
80
80
|
- sample/google_ssl.rb
|
81
81
|
- tcp-client.gemspec
|
82
|
+
- test/tcp-client/address_test.rb
|
82
83
|
- test/tcp-client/configuration_test.rb
|
83
84
|
- test/tcp-client/version_test.rb
|
84
85
|
- test/tcp_client_test.rb
|
@@ -95,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
96
|
requirements:
|
96
97
|
- - ">="
|
97
98
|
- !ruby/object:Gem::Version
|
98
|
-
version: 2.
|
99
|
+
version: 2.5.0
|
99
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
101
|
requirements:
|
101
102
|
- - ">="
|
@@ -108,6 +109,7 @@ signing_key:
|
|
108
109
|
specification_version: 4
|
109
110
|
summary: A TCP client implementation with working timeout support.
|
110
111
|
test_files:
|
112
|
+
- test/tcp-client/address_test.rb
|
111
113
|
- test/tcp-client/configuration_test.rb
|
112
114
|
- test/tcp-client/version_test.rb
|
113
115
|
- test/tcp_client_test.rb
|