tcp-client 0.3.2 → 0.5.1
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/LICENSE +28 -0
- data/README.md +5 -4
- data/lib/tcp-client/address.rb +1 -4
- data/lib/tcp-client/configuration.rb +8 -8
- data/lib/tcp-client/deadline.rb +32 -0
- data/lib/tcp-client/default_configuration.rb +2 -0
- data/lib/tcp-client/errors.rb +2 -4
- data/lib/tcp-client/mixin/io_with_deadline.rb +13 -14
- data/lib/tcp-client/ssl_socket.rb +15 -22
- data/lib/tcp-client/tcp_socket.rb +13 -11
- data/lib/tcp-client/version.rb +3 -1
- data/lib/tcp-client.rb +36 -31
- data/lib/tcp_client.rb +2 -0
- data/rakefile.rb +2 -3
- data/sample/google.rb +4 -3
- data/sample/google_ssl.rb +5 -3
- data/tcp-client.gemspec +12 -8
- 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 +16 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f36e18e6865766292ff44adf5cd46244e8944049f524553ba4168725454a2d0b
|
4
|
+
data.tar.gz: abd4ec63f48c1be4e73922aaed7702bdacebe398e6cda13887fcd2efaa62818c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11b5302d3da2d0d46f35fb2370177916b1044b659b1f62bdbf0a30c39581844dd425487a6c94300b4a7afcf25eafef5828a6ef04cb0ff9bcae4fff01a9a312bb
|
7
|
+
data.tar.gz: 54b3505c1eb90565e0a80dab5c92a394883b74cf3bb975ed83df4c1affbad15e9acbfcee7ec3d0a5bb79c1486fa46fbfbd958fc9e43106b9a97dc22411c84335
|
data/.gitignore
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
BSD 3-Clause License
|
2
|
+
|
3
|
+
Copyright (c) 2017-2021, Mike Blumtritt. All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
13
|
+
and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
16
|
+
contributors may be used to endorse or promote products derived from
|
17
|
+
this software without specific prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
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/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,
|
@@ -38,7 +37,7 @@ class TCPClient
|
|
38
37
|
|
39
38
|
def initialize_copy(_org)
|
40
39
|
super
|
41
|
-
@ssl_params = @ssl_params
|
40
|
+
@ssl_params = Hash[@ssl_params] if @ssl_params
|
42
41
|
self
|
43
42
|
end
|
44
43
|
|
@@ -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
|
+
Hash[value]
|
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) ? true : false
|
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
|
data/lib/tcp-client/errors.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'socket'
|
4
|
-
|
5
3
|
class TCPClient
|
6
4
|
class NoOpenSSL < RuntimeError
|
7
5
|
def initialize
|
8
|
-
super('OpenSSL is not
|
6
|
+
super('OpenSSL is not available')
|
9
7
|
end
|
10
8
|
end
|
11
9
|
|
@@ -29,7 +27,7 @@ class TCPClient
|
|
29
27
|
|
30
28
|
class NotAnException < TypeError
|
31
29
|
def initialize(object)
|
32
|
-
super("
|
30
|
+
super("exception class required - #{object.inspect}")
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
@@ -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,46 +1,39 @@
|
|
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
|
10
13
|
class SSLSocket < ::OpenSSL::SSL::SSLSocket
|
11
14
|
include IOWithDeadlineMixin
|
12
15
|
|
13
|
-
def initialize(socket, address, configuration, exception)
|
16
|
+
def initialize(socket, address, configuration, deadline, exception)
|
14
17
|
ssl_params = Hash[configuration.ssl_params]
|
15
|
-
self.sync_close = true
|
16
18
|
super(socket, create_context(ssl_params))
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
exception
|
22
|
-
)
|
19
|
+
self.sync_close = true
|
20
|
+
self.hostname = address.hostname
|
21
|
+
deadline.valid? ? connect_with_deadline(deadline, exception) : connect
|
22
|
+
post_connection_check(address.hostname) if should_verify?(ssl_params)
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def create_context(ssl_params)
|
28
|
-
|
29
|
-
context.set_params(ssl_params)
|
30
|
-
context
|
28
|
+
OpenSSL::SSL::SSLContext.new.tap { |ctx| ctx.set_params(ssl_params) }
|
31
29
|
end
|
32
30
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
with_deadline(Time.now + timeout, exception) do
|
40
|
-
connect_nonblock(exception: false)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
post_connection_check(address.hostname) if check
|
31
|
+
def connect_with_deadline(deadline, exception)
|
32
|
+
with_deadline(deadline, exception) { connect_nonblock(exception: false) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def should_verify?(ssl_params)
|
36
|
+
ssl_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE
|
44
37
|
end
|
45
38
|
end
|
46
39
|
|
@@ -1,27 +1,29 @@
|
|
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
|
5
8
|
class TCPSocket < ::Socket
|
6
9
|
include IOWithDeadlineMixin
|
7
10
|
|
8
|
-
def initialize(address, configuration, exception)
|
11
|
+
def initialize(address, configuration, deadline, exception)
|
9
12
|
super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
|
10
13
|
configure(configuration)
|
11
|
-
connect_to(address,
|
14
|
+
connect_to(as_addr_in(address), deadline, exception)
|
12
15
|
end
|
13
16
|
|
14
17
|
private
|
15
18
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
with_deadline(Time.now + timeout, exception) do
|
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)
|
25
|
+
return connect(addr) unless deadline.valid?
|
26
|
+
with_deadline(deadline, exception) do
|
25
27
|
connect_nonblock(addr, exception: false)
|
26
28
|
end
|
27
29
|
end
|
data/lib/tcp-client/version.rb
CHANGED
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'
|
@@ -9,33 +10,33 @@ require_relative 'tcp-client/default_configuration'
|
|
9
10
|
require_relative 'tcp-client/version'
|
10
11
|
|
11
12
|
class TCPClient
|
12
|
-
def self.open(
|
13
|
+
def self.open(address, configuration = Configuration.default)
|
13
14
|
client = new
|
14
|
-
client.connect(Address.new(
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
client.connect(Address.new(address), configuration)
|
16
|
+
return client unless block_given?
|
17
|
+
begin
|
18
|
+
yield(client)
|
19
|
+
ensure
|
20
|
+
client.close
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
|
-
attr_reader :address
|
24
|
+
attr_reader :address, :configuration
|
21
25
|
|
22
26
|
def initialize
|
23
|
-
@socket = @address = @deadline = @
|
27
|
+
@socket = @address = @deadline = @configuration = nil
|
24
28
|
end
|
25
29
|
|
26
30
|
def to_s
|
27
|
-
@address
|
31
|
+
@address&.to_s || ''
|
28
32
|
end
|
29
33
|
|
30
|
-
def connect(
|
31
|
-
close
|
34
|
+
def connect(address, configuration, exception: nil)
|
32
35
|
raise(NoOpenSSL) if configuration.ssl? && !defined?(SSLSocket)
|
33
|
-
|
34
|
-
@
|
35
|
-
|
36
|
-
@socket =
|
37
|
-
@cfg.ssl? &&
|
38
|
-
@socket = SSLSocket.new(@socket, @address, configuration, exception)
|
36
|
+
close
|
37
|
+
@address = Address.new(address)
|
38
|
+
@configuration = configuration.dup.freeze
|
39
|
+
@socket = create_socket(exception)
|
39
40
|
self
|
40
41
|
end
|
41
42
|
|
@@ -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 || @configuration.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 || @configuration.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
|
@@ -88,16 +88,21 @@ class TCPClient
|
|
88
88
|
|
89
89
|
private
|
90
90
|
|
91
|
+
def create_socket(exception)
|
92
|
+
exception ||= @configuration.connect_timeout_error
|
93
|
+
deadline = Deadline.new(@configuration.connect_timeout)
|
94
|
+
@socket = TCPSocket.new(@address, @configuration, deadline, exception)
|
95
|
+
return @socket unless @configuration.ssl?
|
96
|
+
SSLSocket.new(@socket, @address, @configuration, deadline, exception)
|
97
|
+
end
|
98
|
+
|
91
99
|
def read_with_deadline(nbytes, deadline, exception)
|
92
|
-
@
|
93
|
-
|
94
|
-
deadline,
|
95
|
-
exception || @cfg.read_timeout_error
|
96
|
-
)
|
100
|
+
exception ||= @configuration.read_timeout_error
|
101
|
+
@socket.read_with_deadline(nbytes, deadline, exception)
|
97
102
|
end
|
98
103
|
|
99
104
|
def write_with_deadline(msg, deadline, exception)
|
100
|
-
exception ||= @
|
105
|
+
exception ||= @configuration.write_timeout_error
|
101
106
|
msg.sum do |chunk|
|
102
107
|
@socket.write_with_deadline(chunk.b, deadline, exception)
|
103
108
|
end
|
data/lib/tcp_client.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
@@ -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'
|
@@ -11,16 +11,20 @@ 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'
|
23
|
+
spec.license = 'BSD-3-Clause'
|
21
24
|
|
22
25
|
spec.metadata['source_code_uri'] = 'https://github.com/mblumtritt/tcp-client'
|
23
|
-
spec.metadata['bug_tracker_uri'] =
|
26
|
+
spec.metadata['bug_tracker_uri'] =
|
27
|
+
'https://github.com/mblumtritt/tcp-client/issues'
|
24
28
|
|
25
29
|
spec.add_development_dependency 'bundler'
|
26
30
|
spec.add_development_dependency 'minitest'
|
@@ -30,5 +34,5 @@ GemSpec = Gem::Specification.new do |spec|
|
|
30
34
|
spec.test_files = all_files.grep(%r{^test/})
|
31
35
|
spec.files = all_files - spec.test_files
|
32
36
|
|
33
|
-
spec.extra_rdoc_files = %w[README.md]
|
37
|
+
spec.extra_rdoc_files = %w[README.md LICENSE]
|
34
38
|
end
|
@@ -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.5.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: 2021-
|
11
|
+
date: 2021-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -53,23 +53,28 @@ 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: []
|
64
66
|
extra_rdoc_files:
|
65
67
|
- README.md
|
68
|
+
- LICENSE
|
66
69
|
files:
|
67
70
|
- ".gitignore"
|
71
|
+
- LICENSE
|
68
72
|
- README.md
|
69
73
|
- gems.rb
|
70
74
|
- lib/tcp-client.rb
|
71
75
|
- lib/tcp-client/address.rb
|
72
76
|
- lib/tcp-client/configuration.rb
|
77
|
+
- lib/tcp-client/deadline.rb
|
73
78
|
- lib/tcp-client/default_configuration.rb
|
74
79
|
- lib/tcp-client/errors.rb
|
75
80
|
- lib/tcp-client/mixin/io_with_deadline.rb
|
@@ -83,12 +88,14 @@ files:
|
|
83
88
|
- tcp-client.gemspec
|
84
89
|
- test/tcp-client/address_test.rb
|
85
90
|
- test/tcp-client/configuration_test.rb
|
91
|
+
- test/tcp-client/deadline_test.rb
|
86
92
|
- test/tcp-client/default_configuration_test.rb
|
87
93
|
- test/tcp-client/version_test.rb
|
88
94
|
- test/tcp_client_test.rb
|
89
95
|
- test/test_helper.rb
|
90
96
|
homepage: https://github.com/mblumtritt/tcp-client
|
91
|
-
licenses:
|
97
|
+
licenses:
|
98
|
+
- BSD-3-Clause
|
92
99
|
metadata:
|
93
100
|
source_code_uri: https://github.com/mblumtritt/tcp-client
|
94
101
|
bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
|
@@ -114,6 +121,7 @@ summary: A TCP client implementation with working timeout support.
|
|
114
121
|
test_files:
|
115
122
|
- test/tcp-client/address_test.rb
|
116
123
|
- test/tcp-client/configuration_test.rb
|
124
|
+
- test/tcp-client/deadline_test.rb
|
117
125
|
- test/tcp-client/default_configuration_test.rb
|
118
126
|
- test/tcp-client/version_test.rb
|
119
127
|
- test/tcp_client_test.rb
|