tcp-client 0.5.0 → 0.8.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/README.md +18 -16
- data/lib/tcp-client/address.rb +6 -2
- data/lib/tcp-client/configuration.rb +22 -12
- data/lib/tcp-client/deadline.rb +2 -0
- data/lib/tcp-client/errors.rb +23 -9
- data/lib/tcp-client/mixin/io_with_deadline.rb +29 -1
- data/lib/tcp-client/ssl_socket.rb +20 -2
- data/lib/tcp-client/version.rb +1 -1
- data/lib/tcp-client.rb +70 -54
- data/rakefile.rb +2 -5
- data/sample/google.rb +14 -15
- data/sample/google_ssl.rb +19 -13
- data/spec/helper.rb +12 -0
- data/spec/tcp-client/address_spec.rb +158 -0
- data/spec/tcp-client/configuration_spec.rb +308 -0
- data/spec/tcp-client/default_configuration_spec.rb +22 -0
- data/spec/tcp-client/version_spec.rb +13 -0
- data/spec/tcp_client_spec.rb +596 -0
- data/tcp-client.gemspec +2 -2
- metadata +17 -19
- data/test/tcp-client/address_test.rb +0 -67
- data/test/tcp-client/configuration_test.rb +0 -143
- data/test/tcp-client/deadline_test.rb +0 -26
- data/test/tcp-client/default_configuration_test.rb +0 -59
- data/test/tcp-client/version_test.rb +0 -11
- data/test/tcp_client_test.rb +0 -163
- 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: 14f787ad4e8e06910bfebf67755ae7142183df4c4fb090edce84aae7d497a2b6
|
4
|
+
data.tar.gz: 6349bea2eac6bac053c724e0c6a162b1fe99502e94cf1c6734854d97f194f37a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b53fafbf091a67a329832663c058b4e8243b36a779f8bc52cc7f37de556ad0aa016245e0a059489b972e6afaccfea7d26a63a735686d5f50d88036a2f4ecaca1
|
7
|
+
data.tar.gz: 4a6ff368e221073c5addfab51e31df1b094ee3f69e09ebe3f38d6d552ebc761b14c552a1a2cd90dcfd148b1ba34ebb1b2e3abfcea9622ded8413870f21a60285
|
data/README.md
CHANGED
@@ -11,20 +11,22 @@ This Gem implements a TCP client with (optional) SSL support. It is an easy to u
|
|
11
11
|
```ruby
|
12
12
|
require 'tcp-client'
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
14
|
+
# create a configuration:
|
15
|
+
# - don't use internal buffering
|
16
|
+
# - use TLS 1.2 or TLS 1.3
|
17
|
+
cfg = TCPClient::Configuration.create(
|
18
|
+
buffered: false,
|
19
|
+
ssl_params: {min_version: :TLS1_2, max_version: :TLS1_3}
|
20
|
+
)
|
21
|
+
|
22
|
+
# request to Google.com:
|
23
|
+
# - limit all network interactions to 1.5 seconds
|
24
|
+
# - use the Configuration cfg
|
25
|
+
# - send a simple HTTP get request
|
26
|
+
# - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
|
27
|
+
TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
|
28
|
+
client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") # >= 40
|
29
|
+
client.read(12) # => "HTTP/1.1 200"
|
28
30
|
end
|
29
31
|
```
|
30
32
|
|
@@ -41,13 +43,13 @@ gem 'tcp-client'
|
|
41
43
|
and install it by running Bundler:
|
42
44
|
|
43
45
|
```bash
|
44
|
-
|
46
|
+
bundle
|
45
47
|
```
|
46
48
|
|
47
49
|
To install the gem globally use:
|
48
50
|
|
49
51
|
```bash
|
50
|
-
|
52
|
+
gem install tcp-client
|
51
53
|
```
|
52
54
|
|
53
55
|
After that you need only a single line of code in your project to have all tools on board:
|
data/lib/tcp-client/address.rb
CHANGED
@@ -25,12 +25,16 @@ class TCPClient
|
|
25
25
|
"#{@hostname}:#{@addrinfo.ip_port}"
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def to_hash
|
29
29
|
{ host: @hostname, port: @addrinfo.ip_port }
|
30
30
|
end
|
31
31
|
|
32
|
+
def to_h(*args)
|
33
|
+
args.empty? ? to_hash : to_hash.slice(*args)
|
34
|
+
end
|
35
|
+
|
32
36
|
def ==(other)
|
33
|
-
|
37
|
+
to_hash == other.to_hash
|
34
38
|
end
|
35
39
|
alias eql? ==
|
36
40
|
|
@@ -13,6 +13,7 @@ class TCPClient
|
|
13
13
|
attr_reader :buffered,
|
14
14
|
:keep_alive,
|
15
15
|
:reverse_lookup,
|
16
|
+
:normalize_network_errors,
|
16
17
|
:connect_timeout,
|
17
18
|
:read_timeout,
|
18
19
|
:write_timeout,
|
@@ -27,6 +28,7 @@ class TCPClient
|
|
27
28
|
@connect_timeout_error = ConnectTimeoutError
|
28
29
|
@read_timeout_error = ReadTimeoutError
|
29
30
|
@write_timeout_error = WriteTimeoutError
|
31
|
+
@normalize_network_errors = false
|
30
32
|
options.each_pair { |attribute, value| set(attribute, value) }
|
31
33
|
end
|
32
34
|
|
@@ -37,7 +39,7 @@ class TCPClient
|
|
37
39
|
|
38
40
|
def initialize_copy(_org)
|
39
41
|
super
|
40
|
-
@ssl_params = @ssl_params
|
42
|
+
@ssl_params = Hash[@ssl_params] if @ssl_params
|
41
43
|
self
|
42
44
|
end
|
43
45
|
|
@@ -47,8 +49,8 @@ class TCPClient
|
|
47
49
|
|
48
50
|
def ssl=(value)
|
49
51
|
@ssl_params =
|
50
|
-
if
|
51
|
-
value.
|
52
|
+
if value.respond_to?(:to_hash)
|
53
|
+
Hash[value.to_hash]
|
52
54
|
else
|
53
55
|
value ? {} : nil
|
54
56
|
end
|
@@ -66,6 +68,10 @@ class TCPClient
|
|
66
68
|
@reverse_lookup = value ? true : false
|
67
69
|
end
|
68
70
|
|
71
|
+
def normalize_network_errors=(value)
|
72
|
+
@normalize_network_errors = value ? true : false
|
73
|
+
end
|
74
|
+
|
69
75
|
def timeout=(seconds)
|
70
76
|
@connect_timeout = @write_timeout = @read_timeout = seconds(seconds)
|
71
77
|
end
|
@@ -83,43 +89,47 @@ class TCPClient
|
|
83
89
|
end
|
84
90
|
|
85
91
|
def timeout_error=(exception)
|
86
|
-
raise(
|
92
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
87
93
|
@connect_timeout_error =
|
88
94
|
@read_timeout_error = @write_timeout_error = exception
|
89
95
|
end
|
90
96
|
|
91
97
|
def connect_timeout_error=(exception)
|
92
|
-
raise(
|
98
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
93
99
|
@connect_timeout_error = exception
|
94
100
|
end
|
95
101
|
|
96
102
|
def read_timeout_error=(exception)
|
97
|
-
raise(
|
103
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
98
104
|
@read_timeout_error = exception
|
99
105
|
end
|
100
106
|
|
101
107
|
def write_timeout_error=(exception)
|
102
|
-
raise(
|
108
|
+
raise(NotAnExceptionError, exception) unless exception_class?(exception)
|
103
109
|
@write_timeout_error = exception
|
104
110
|
end
|
105
111
|
|
106
|
-
def
|
112
|
+
def to_hash
|
107
113
|
{
|
108
114
|
buffered: @buffered,
|
109
115
|
keep_alive: @keep_alive,
|
110
116
|
reverse_lookup: @reverse_lookup,
|
111
117
|
connect_timeout: @connect_timeout,
|
112
|
-
read_timeout: @read_timeout,
|
113
|
-
write_timeout: @write_timeout,
|
114
118
|
connect_timeout_error: @connect_timeout_error,
|
119
|
+
read_timeout: @read_timeout,
|
115
120
|
read_timeout_error: @read_timeout_error,
|
121
|
+
write_timeout: @write_timeout,
|
116
122
|
write_timeout_error: @write_timeout_error,
|
117
123
|
ssl_params: @ssl_params
|
118
124
|
}
|
119
125
|
end
|
120
126
|
|
127
|
+
def to_h(*args)
|
128
|
+
args.empty? ? to_hash : to_hash.slice(*args)
|
129
|
+
end
|
130
|
+
|
121
131
|
def ==(other)
|
122
|
-
|
132
|
+
to_hash == other.to_hash
|
123
133
|
end
|
124
134
|
alias eql? ==
|
125
135
|
|
@@ -136,7 +146,7 @@ class TCPClient
|
|
136
146
|
def set(attribute, value)
|
137
147
|
public_send("#{attribute}=", value)
|
138
148
|
rescue NoMethodError
|
139
|
-
raise(
|
149
|
+
raise(UnknownAttributeError, attribute)
|
140
150
|
end
|
141
151
|
|
142
152
|
def seconds(value)
|
data/lib/tcp-client/deadline.rb
CHANGED
data/lib/tcp-client/errors.rb
CHANGED
@@ -1,43 +1,45 @@
|
|
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
|
-
|
34
|
+
NetworkError = Class.new(StandardError)
|
35
|
+
|
36
|
+
class NotConnectedError < NetworkError
|
35
37
|
def initialize
|
36
38
|
super('client not connected')
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
40
|
-
class TimeoutError <
|
42
|
+
class TimeoutError < NetworkError
|
41
43
|
def initialize(message = nil)
|
42
44
|
super(message || "unable to #{action} in time")
|
43
45
|
end
|
@@ -65,6 +67,18 @@ class TCPClient
|
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
+
NoOpenSSL = NoOpenSSLError
|
71
|
+
NoBlockGiven = NoBlockGivenError
|
72
|
+
InvalidDeadLine = InvalidDeadLineError
|
73
|
+
UnknownAttribute = UnknownAttributeError
|
74
|
+
NotAnException = NotAnExceptionError
|
75
|
+
NotConnected = NotConnectedError
|
76
|
+
deprecate_constant(
|
77
|
+
:NoOpenSSL,
|
78
|
+
:NoBlockGiven,
|
79
|
+
:InvalidDeadLine,
|
80
|
+
:UnknownAttribute,
|
81
|
+
:NotAnException,
|
82
|
+
:NotConnected
|
83
|
+
)
|
70
84
|
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
|
@@ -18,6 +18,7 @@ class TCPClient
|
|
18
18
|
super(socket, create_context(ssl_params))
|
19
19
|
self.sync_close = true
|
20
20
|
self.hostname = address.hostname
|
21
|
+
check_new_session if @new_session
|
21
22
|
deadline.valid? ? connect_with_deadline(deadline, exception) : connect
|
22
23
|
post_connection_check(address.hostname) if should_verify?(ssl_params)
|
23
24
|
end
|
@@ -25,7 +26,19 @@ class TCPClient
|
|
25
26
|
private
|
26
27
|
|
27
28
|
def create_context(ssl_params)
|
28
|
-
|
29
|
+
@new_session = nil
|
30
|
+
::OpenSSL::SSL::SSLContext.new.tap do |ctx|
|
31
|
+
ctx.set_params(ssl_params)
|
32
|
+
ctx.session_cache_mode = CONTEXT_CACHE_MODE
|
33
|
+
ctx.session_new_cb = proc { |_, sess| @new_session = sess }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_new_session
|
38
|
+
time = @new_session.time.to_f + @new_session.timeout
|
39
|
+
if Process.clock_gettime(Process::CLOCK_REALTIME) < time
|
40
|
+
self.session = @new_session
|
41
|
+
end
|
29
42
|
end
|
30
43
|
|
31
44
|
def connect_with_deadline(deadline, exception)
|
@@ -33,8 +46,13 @@ class TCPClient
|
|
33
46
|
end
|
34
47
|
|
35
48
|
def should_verify?(ssl_params)
|
36
|
-
ssl_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE
|
49
|
+
ssl_params[:verify_mode] != ::OpenSSL::SSL::VERIFY_NONE &&
|
50
|
+
context.verify_hostname
|
37
51
|
end
|
52
|
+
|
53
|
+
CONTEXT_CACHE_MODE =
|
54
|
+
::OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
|
55
|
+
::OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
38
56
|
end
|
39
57
|
|
40
58
|
private_constant(:SSLSocket)
|
data/lib/tcp-client/version.rb
CHANGED
data/lib/tcp-client.rb
CHANGED
@@ -10,40 +10,45 @@ require_relative 'tcp-client/default_configuration'
|
|
10
10
|
require_relative 'tcp-client/version'
|
11
11
|
|
12
12
|
class TCPClient
|
13
|
-
def self.open(address, configuration =
|
13
|
+
def self.open(address, configuration = nil)
|
14
14
|
client = new
|
15
15
|
client.connect(Address.new(address), configuration)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
block_given? ? yield(client) : client
|
17
|
+
ensure
|
18
|
+
client.close if block_given?
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.with_deadline(timeout, address, configuration = nil)
|
22
|
+
client = nil
|
23
|
+
raise(NoBlockGivenError) unless block_given?
|
24
|
+
address = Address.new(address)
|
25
|
+
client = new
|
26
|
+
client.with_deadline(timeout) do
|
27
|
+
yield(client.connect(address, configuration))
|
21
28
|
end
|
29
|
+
ensure
|
30
|
+
client&.close
|
22
31
|
end
|
23
32
|
|
24
33
|
attr_reader :address, :configuration
|
25
34
|
|
26
|
-
def initialize
|
27
|
-
@socket = @address = @deadline = @configuration = nil
|
28
|
-
end
|
29
|
-
|
30
35
|
def to_s
|
31
36
|
@address&.to_s || ''
|
32
37
|
end
|
33
38
|
|
34
|
-
def connect(address, configuration, exception: nil)
|
35
|
-
|
36
|
-
|
39
|
+
def connect(address, configuration = nil, timeout: nil, exception: nil)
|
40
|
+
close if @socket
|
41
|
+
raise(NoOpenSSLError) if configuration.ssl? && !defined?(SSLSocket)
|
37
42
|
@address = Address.new(address)
|
38
|
-
@configuration = configuration.dup
|
39
|
-
@socket = create_socket(exception)
|
43
|
+
@configuration = (configuration || Configuration.default).dup
|
44
|
+
@socket = create_socket(timeout, exception)
|
40
45
|
self
|
41
46
|
end
|
42
47
|
|
43
48
|
def close
|
44
49
|
@socket&.close
|
45
50
|
self
|
46
|
-
rescue
|
51
|
+
rescue *NETWORK_ERRORS
|
47
52
|
self
|
48
53
|
ensure
|
49
54
|
@socket = @deadline = nil
|
@@ -55,66 +60,77 @@ class TCPClient
|
|
55
60
|
|
56
61
|
def with_deadline(timeout)
|
57
62
|
previous_deadline = @deadline
|
58
|
-
raise(
|
63
|
+
raise(NoBlockGivenError) unless block_given?
|
59
64
|
@deadline = Deadline.new(timeout)
|
60
|
-
raise(
|
65
|
+
raise(InvalidDeadLineError, timeout) unless @deadline.valid?
|
61
66
|
yield(self)
|
62
67
|
ensure
|
63
68
|
@deadline = previous_deadline
|
64
69
|
end
|
65
70
|
|
66
|
-
def read(nbytes, timeout: nil, exception: nil)
|
67
|
-
raise(
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
71
|
+
def read(nbytes = nil, timeout: nil, exception: nil)
|
72
|
+
raise(NotConnectedError) if closed?
|
73
|
+
deadline = create_deadline(timeout, configuration.read_timeout)
|
74
|
+
return stem_errors { @socket.read(nbytes) } unless deadline.valid?
|
75
|
+
exception ||= configuration.read_timeout_error
|
76
|
+
stem_errors(exception) do
|
77
|
+
@socket.read_with_deadline(nbytes, deadline, exception)
|
78
|
+
end
|
73
79
|
end
|
74
80
|
|
75
81
|
def write(*msg, timeout: nil, exception: nil)
|
76
|
-
raise(
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
raise(NotConnectedError) if closed?
|
83
|
+
deadline = create_deadline(timeout, configuration.write_timeout)
|
84
|
+
return stem_errors { @socket.write(*msg) } unless deadline.valid?
|
85
|
+
exception ||= configuration.write_timeout_error
|
86
|
+
stem_errors(exception) do
|
87
|
+
msg.sum do |chunk|
|
88
|
+
@socket.write_with_deadline(chunk.b, deadline, exception)
|
89
|
+
end
|
90
|
+
end
|
82
91
|
end
|
83
92
|
|
84
93
|
def flush
|
85
|
-
@socket
|
94
|
+
stem_errors { @socket&.flush }
|
86
95
|
self
|
87
96
|
end
|
88
97
|
|
89
98
|
private
|
90
99
|
|
91
|
-
def
|
92
|
-
|
93
|
-
deadline = Deadline.new(@configuration.connect_timeout)
|
94
|
-
socket = TCPSocket.new(@address, @configuration, deadline, exception)
|
95
|
-
@configuration.ssl? ? as_ssl_socket(socket, deadline, exception) : socket
|
100
|
+
def create_deadline(timeout, default)
|
101
|
+
timeout.nil? && @deadline ? @deadline : Deadline.new(timeout || default)
|
96
102
|
end
|
97
103
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
socket.
|
103
|
-
|
104
|
-
|
104
|
+
def create_socket(timeout, exception)
|
105
|
+
deadline = create_deadline(timeout, configuration.connect_timeout)
|
106
|
+
exception ||= configuration.connect_timeout_error
|
107
|
+
stem_errors(exception) do
|
108
|
+
@socket = TCPSocket.new(address, configuration, deadline, exception)
|
109
|
+
return @socket unless configuration.ssl?
|
110
|
+
SSLSocket.new(@socket, address, configuration, deadline, exception)
|
105
111
|
end
|
106
|
-
raise(e, cause: e.cause)
|
107
112
|
end
|
108
113
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
114
|
+
def stem_errors(except = nil)
|
115
|
+
yield
|
116
|
+
rescue *NETWORK_ERRORS => e
|
117
|
+
raise unless configuration.normalize_network_errors
|
118
|
+
(except && e.is_a?(except)) ? raise : raise(NetworkError, e)
|
112
119
|
end
|
113
120
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
121
|
+
NETWORK_ERRORS =
|
122
|
+
[
|
123
|
+
Errno::EADDRNOTAVAIL,
|
124
|
+
Errno::ECONNABORTED,
|
125
|
+
Errno::ECONNREFUSED,
|
126
|
+
Errno::ECONNRESET,
|
127
|
+
Errno::EHOSTUNREACH,
|
128
|
+
Errno::EINVAL,
|
129
|
+
Errno::ENETUNREACH,
|
130
|
+
Errno::EPIPE,
|
131
|
+
IOError,
|
132
|
+
SocketError
|
133
|
+
].tap do |errors|
|
134
|
+
errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
|
135
|
+
end.freeze
|
120
136
|
end
|
data/rakefile.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rake/clean'
|
4
|
-
require 'rake/testtask'
|
5
4
|
require 'bundler/gem_tasks'
|
5
|
+
require 'rspec/core/rake_task'
|
6
6
|
|
7
7
|
$stdout.sync = $stderr.sync = true
|
8
8
|
|
@@ -10,7 +10,4 @@ CLOBBER << 'prj'
|
|
10
10
|
|
11
11
|
task(:default) { exec('rake --tasks') }
|
12
12
|
|
13
|
-
|
14
|
-
task.pattern = 'test/**/*_test.rb'
|
15
|
-
task.warning = task.verbose = true
|
16
|
-
end
|
13
|
+
RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
|
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,24 @@
|
|
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
|
+
# - don't use internal buffering
|
7
|
+
# - use TLS 1.2 or TLS 1.3
|
8
|
+
cfg =
|
9
|
+
TCPClient::Configuration.create(
|
10
|
+
buffered: false,
|
11
|
+
ssl_params: {
|
12
|
+
min_version: :TLS1_2,
|
13
|
+
max_version: :TLS1_3
|
14
|
+
}
|
15
|
+
)
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
# request to Google.com:
|
18
|
+
# - limit all network interactions to 1.5 seconds
|
19
|
+
# - use the Configuration cfg
|
20
|
+
# - send a simple HTTP get request
|
21
|
+
# - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
|
22
|
+
TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
|
23
|
+
p client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
24
|
+
p client.read(12)
|
19
25
|
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require_relative '../lib/tcp-client'
|
5
|
+
|
6
|
+
$stdout.sync = $stderr.sync = true
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.disable_monkey_patching!
|
10
|
+
config.warnings = true
|
11
|
+
config.order = :random
|
12
|
+
end
|