tcp-client 0.7.0 → 0.9.2
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 +3 -1
- data/.yardopts +5 -0
- data/README.md +26 -17
- data/gems.rb +2 -1
- data/lib/tcp-client/address.rb +51 -1
- data/lib/tcp-client/configuration.rb +256 -56
- data/lib/tcp-client/default_configuration.rb +36 -2
- data/lib/tcp-client/errors.rb +85 -8
- data/lib/tcp-client/mixin/io_with_deadline.rb +2 -1
- data/lib/tcp-client/ssl_socket.rb +19 -2
- data/lib/tcp-client/version.rb +1 -1
- data/lib/tcp-client.rb +241 -42
- data/rakefile.rb +5 -9
- data/sample/google_ssl.rb +7 -6
- data/spec/helper.rb +12 -0
- data/spec/tcp-client/address_spec.rb +145 -0
- data/spec/tcp-client/configuration_spec.rb +270 -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 +12 -11
- metadata +32 -18
- data/test/helper.rb +0 -41
- data/test/tcp-client/address_test.rb +0 -65
- data/test/tcp-client/configuration_test.rb +0 -141
- 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 -9
- data/test/tcp_client_test.rb +0 -184
data/lib/tcp-client/errors.rb
CHANGED
@@ -1,76 +1,153 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class TCPClient
|
4
|
+
#
|
5
|
+
# Raised when a SSL connection should be establshed but the OpenSSL gem is not available.
|
6
|
+
#
|
4
7
|
class NoOpenSSLError < RuntimeError
|
5
8
|
def initialize
|
6
9
|
super('OpenSSL is not available')
|
7
10
|
end
|
8
11
|
end
|
9
12
|
|
13
|
+
#
|
14
|
+
# Raised when a method requires a callback block but no such block is specified.
|
15
|
+
#
|
10
16
|
class NoBlockGivenError < ArgumentError
|
11
17
|
def initialize
|
12
18
|
super('no block given')
|
13
19
|
end
|
14
20
|
end
|
15
21
|
|
22
|
+
#
|
23
|
+
# Raised when an invalid timeout value was specified.
|
24
|
+
#
|
16
25
|
class InvalidDeadLineError < ArgumentError
|
26
|
+
#
|
27
|
+
# @param timeout [Object] the invalid value
|
28
|
+
#
|
17
29
|
def initialize(timeout)
|
18
30
|
super("invalid deadline - #{timeout}")
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
34
|
+
#
|
35
|
+
# Raised by {Configuration} when an undefined attribute should be set.
|
36
|
+
#
|
22
37
|
class UnknownAttributeError < ArgumentError
|
38
|
+
#
|
39
|
+
# @param attribute [Object] the undefined atttribute
|
40
|
+
#
|
23
41
|
def initialize(attribute)
|
24
42
|
super("unknown attribute - #{attribute}")
|
25
43
|
end
|
26
44
|
end
|
27
45
|
|
46
|
+
#
|
47
|
+
# Raised when a given timeout exception parameter is not an exception class.
|
48
|
+
#
|
28
49
|
class NotAnExceptionError < TypeError
|
50
|
+
#
|
51
|
+
# @param object [Object] the invalid object
|
52
|
+
#
|
29
53
|
def initialize(object)
|
30
54
|
super("exception class required - #{object.inspect}")
|
31
55
|
end
|
32
56
|
end
|
33
57
|
|
34
|
-
|
58
|
+
#
|
59
|
+
# Base exception class for all network related errors.
|
60
|
+
#
|
61
|
+
# Will be raised for any system level network error when {Configuration.normalize_network_errors} is configured.
|
62
|
+
#
|
63
|
+
# You should catch this exception class when you like to handle any relevant {TCPClient} error.
|
64
|
+
#
|
65
|
+
class NetworkError < StandardError
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Raised when a {TCPClient} instance should read/write from/to the network but is not connected.
|
70
|
+
#
|
71
|
+
class NotConnectedError < NetworkError
|
35
72
|
def initialize
|
36
73
|
super('client not connected')
|
37
74
|
end
|
38
75
|
end
|
39
76
|
|
40
|
-
|
77
|
+
#
|
78
|
+
# Base exception class for a detected timeout.
|
79
|
+
#
|
80
|
+
# You should catch this exception class when you like to handle any timeout error.
|
81
|
+
#
|
82
|
+
class TimeoutError < NetworkError
|
83
|
+
#
|
84
|
+
# Initializes the instance with an optional message.
|
85
|
+
#
|
86
|
+
# The message will be generated from {#action} when not specified.
|
87
|
+
#
|
88
|
+
# @overload initialize
|
89
|
+
# @overload initialize(message)
|
90
|
+
#
|
91
|
+
# @param message [#to_s] the error message
|
92
|
+
#
|
41
93
|
def initialize(message = nil)
|
42
94
|
super(message || "unable to #{action} in time")
|
43
95
|
end
|
44
96
|
|
97
|
+
#
|
98
|
+
# @attribute [r] action
|
99
|
+
# @return [Symbol] the action which timed out
|
100
|
+
#
|
45
101
|
def action
|
46
102
|
:process
|
47
103
|
end
|
48
104
|
end
|
49
105
|
|
106
|
+
#
|
107
|
+
# Raised by default whenever a {TCPClient.connect} timed out.
|
108
|
+
#
|
50
109
|
class ConnectTimeoutError < TimeoutError
|
110
|
+
#
|
111
|
+
# @attribute [r] action
|
112
|
+
# @return [Symbol] the action which timed out: `:connect`
|
113
|
+
#
|
51
114
|
def action
|
52
115
|
:connect
|
53
116
|
end
|
54
117
|
end
|
55
118
|
|
119
|
+
#
|
120
|
+
# Raised by default whenever a {TCPClient#read} timed out.
|
121
|
+
#
|
56
122
|
class ReadTimeoutError < TimeoutError
|
123
|
+
#
|
124
|
+
# @attribute [r] action
|
125
|
+
# @return [Symbol] the action which timed out: :read`
|
126
|
+
#
|
57
127
|
def action
|
58
128
|
:read
|
59
129
|
end
|
60
130
|
end
|
61
131
|
|
132
|
+
#
|
133
|
+
# Raised by default whenever a {TCPClient#write} timed out.
|
134
|
+
#
|
62
135
|
class WriteTimeoutError < TimeoutError
|
136
|
+
#
|
137
|
+
# @attribute [r] action
|
138
|
+
# @return [Symbol] the action which timed out: `:write`
|
139
|
+
#
|
63
140
|
def action
|
64
141
|
:write
|
65
142
|
end
|
66
143
|
end
|
67
144
|
|
68
|
-
NoOpenSSL = NoOpenSSLError
|
69
|
-
NoBlockGiven = NoBlockGivenError
|
70
|
-
InvalidDeadLine = InvalidDeadLineError
|
71
|
-
UnknownAttribute = UnknownAttributeError
|
72
|
-
NotAnException = NotAnExceptionError
|
73
|
-
NotConnected = NotConnectedError
|
145
|
+
NoOpenSSL = NoOpenSSLError # @!visibility private
|
146
|
+
NoBlockGiven = NoBlockGivenError # @!visibility private
|
147
|
+
InvalidDeadLine = InvalidDeadLineError # @!visibility private
|
148
|
+
UnknownAttribute = UnknownAttributeError # @!visibility private
|
149
|
+
NotAnException = NotAnExceptionError # @!visibility private
|
150
|
+
NotConnected = NotConnectedError # @!visibility private
|
74
151
|
deprecate_constant(
|
75
152
|
:NoOpenSSL,
|
76
153
|
:NoBlockGiven,
|
@@ -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,9 +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 &&
|
37
50
|
context.verify_hostname
|
38
51
|
end
|
52
|
+
|
53
|
+
CONTEXT_CACHE_MODE =
|
54
|
+
::OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
|
55
|
+
::OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
39
56
|
end
|
40
57
|
|
41
58
|
private_constant(:SSLSocket)
|
data/lib/tcp-client/version.rb
CHANGED
data/lib/tcp-client.rb
CHANGED
@@ -9,8 +9,48 @@ require_relative 'tcp-client/configuration'
|
|
9
9
|
require_relative 'tcp-client/default_configuration'
|
10
10
|
require_relative 'tcp-client/version'
|
11
11
|
|
12
|
+
#
|
13
|
+
# Client class to communicate with a server via TCP w/o SSL.
|
14
|
+
#
|
15
|
+
# All connect/read/write actions can be monitored to ensure that all actions
|
16
|
+
# terminate before given time limits - or raise an exception.
|
17
|
+
#
|
18
|
+
# @example request to Google.com and limit network interactions to 1.5 seconds
|
19
|
+
# TCPClient.with_deadline(1.5, 'www.google.com:443') do |client|
|
20
|
+
# client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
21
|
+
# client.read(12)
|
22
|
+
# end
|
23
|
+
# # => "HTTP/1.1 200"
|
24
|
+
#
|
25
|
+
#
|
12
26
|
class TCPClient
|
13
|
-
|
27
|
+
#
|
28
|
+
# Creates a new instance which is connected to the server on the given
|
29
|
+
# `address`.
|
30
|
+
#
|
31
|
+
# If no `configuration` is given, the {.default_configuration} will be used.
|
32
|
+
#
|
33
|
+
# If an optional block is given, then the block's result is returned and the
|
34
|
+
# connection will be closed when the block execution ends.
|
35
|
+
# This can be used to create an ad-hoc connection which is garanteed to be
|
36
|
+
# closed.
|
37
|
+
#
|
38
|
+
# If no block is giiven the connected client instance is returned.
|
39
|
+
# This can be used as a shorthand to create & connect a client.
|
40
|
+
#
|
41
|
+
# @param address [Address, String, Addrinfo, Integer] the address to connect
|
42
|
+
# to, see {Address#initialize} for valid formats
|
43
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
44
|
+
# this instance
|
45
|
+
#
|
46
|
+
# @yieldparam client [TCPClient] the connected client
|
47
|
+
# @yieldreturn [Object] any result
|
48
|
+
#
|
49
|
+
# @return [Object, TCPClient] the block result or the connected client
|
50
|
+
#
|
51
|
+
# @see #connect
|
52
|
+
#
|
53
|
+
def self.open(address, configuration = nil)
|
14
54
|
client = new
|
15
55
|
client.connect(Address.new(address), configuration)
|
16
56
|
block_given? ? yield(client) : client
|
@@ -18,11 +58,35 @@ class TCPClient
|
|
18
58
|
client.close if block_given?
|
19
59
|
end
|
20
60
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
61
|
+
#
|
62
|
+
# Yields a new instance which is connected to the server on the given
|
63
|
+
# `address`.It limits all {#read} and {#write} actions within the block to
|
64
|
+
# the given time.
|
65
|
+
#
|
66
|
+
# It ensures to close the connection when the block execution ends and returns
|
67
|
+
# the block`s result.
|
68
|
+
#
|
69
|
+
# This can be used to create an ad-hoc connection which is garanteed to be
|
70
|
+
# closed and which {#read}/{#write} call sequence should not last longer than
|
71
|
+
# the `timeout`.
|
72
|
+
#
|
73
|
+
# If no `configuration` is given, the {.default_configuration} will be used.
|
74
|
+
#
|
75
|
+
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
76
|
+
# {#write} calls within the block
|
77
|
+
# @param address [Address, String, Addrinfo, Integer] the address to connect
|
78
|
+
# to, see {Address#initialize} for valid formats
|
79
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
80
|
+
# this instance
|
81
|
+
#
|
82
|
+
# @yieldparam client [TCPClient] the connected client
|
83
|
+
# @yieldreturn [Object] any result
|
84
|
+
#
|
85
|
+
# @return [Object] the block's result
|
86
|
+
#
|
87
|
+
# @see #with_deadline
|
88
|
+
#
|
89
|
+
def self.with_deadline(timeout, address, configuration = nil)
|
26
90
|
client = nil
|
27
91
|
raise(NoBlockGivenError) unless block_given?
|
28
92
|
address = Address.new(address)
|
@@ -34,38 +98,142 @@ class TCPClient
|
|
34
98
|
client&.close
|
35
99
|
end
|
36
100
|
|
37
|
-
|
101
|
+
#
|
102
|
+
# @return [Address] the address used by this client instance
|
103
|
+
#
|
104
|
+
attr_reader :address
|
38
105
|
|
39
|
-
|
40
|
-
|
106
|
+
#
|
107
|
+
# @return [Configuration] the configuration used by this client instance
|
108
|
+
#
|
109
|
+
attr_reader :configuration
|
110
|
+
|
111
|
+
#
|
112
|
+
# @!parse attr_reader :closed?
|
113
|
+
# @return [Boolean] true when the connection is closed, false when connected
|
114
|
+
#
|
115
|
+
def closed?
|
116
|
+
@socket.nil? || @socket.closed?
|
41
117
|
end
|
42
118
|
|
43
|
-
|
44
|
-
|
119
|
+
#
|
120
|
+
# Close the current connection if connected.
|
121
|
+
#
|
122
|
+
# @return [self]
|
123
|
+
#
|
124
|
+
def close
|
125
|
+
@socket&.close
|
126
|
+
self
|
127
|
+
rescue *NETWORK_ERRORS
|
128
|
+
self
|
129
|
+
ensure
|
130
|
+
@socket = @deadline = nil
|
45
131
|
end
|
46
132
|
|
47
|
-
|
133
|
+
#
|
134
|
+
# Establishes a new connection to a given `address`.
|
135
|
+
#
|
136
|
+
# It accepts a connection-specific configuration or uses the
|
137
|
+
# {.default_configuration}. The {#configuration} used by this instance will
|
138
|
+
# be a copy of the configuration used for this method call. This allows to
|
139
|
+
# configure the behavior per connection.
|
140
|
+
#
|
141
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
142
|
+
# `connect_timeout` and `connect_timeout_error` values.
|
143
|
+
#
|
144
|
+
# @param address [Address, String, Addrinfo, Integer] the address to connect
|
145
|
+
# to, see {Address#initialize} for valid formats
|
146
|
+
# @param configuration [Configuration] the {Configuration} to be used for
|
147
|
+
# this instance
|
148
|
+
# @param timeout [Numeric] maximum time in seconds to connect
|
149
|
+
# @param exception [Class] exception class to be used when the connect timeout
|
150
|
+
# reached
|
151
|
+
#
|
152
|
+
# @return [self]
|
153
|
+
#
|
154
|
+
# @raise {NoOpenSSLError} if SSL should be used but OpenSSL is not avail
|
155
|
+
#
|
156
|
+
# @see NetworkError
|
157
|
+
#
|
158
|
+
def connect(address, configuration = nil, timeout: nil, exception: nil)
|
48
159
|
close if @socket
|
49
|
-
raise(NoOpenSSLError) if configuration.ssl? && !defined?(SSLSocket)
|
50
160
|
@address = Address.new(address)
|
51
|
-
@configuration = configuration.dup
|
161
|
+
@configuration = (configuration || Configuration.default).dup
|
162
|
+
raise(NoOpenSSLError) if @configuration.ssl? && !defined?(SSLSocket)
|
52
163
|
@socket = create_socket(timeout, exception)
|
53
164
|
self
|
54
165
|
end
|
55
166
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
167
|
+
#
|
168
|
+
# Flush all internal buffers (write all through).
|
169
|
+
#
|
170
|
+
# @return [self]
|
171
|
+
#
|
172
|
+
def flush
|
173
|
+
stem_errors { @socket&.flush }
|
60
174
|
self
|
61
|
-
ensure
|
62
|
-
@socket = @deadline = nil
|
63
175
|
end
|
64
176
|
|
65
|
-
|
66
|
-
|
177
|
+
#
|
178
|
+
# Read the given `nbytes` or the next available buffer from server.
|
179
|
+
#
|
180
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
181
|
+
# `read_timeout` and `read_timeout_error` values of the used {#configuration}.
|
182
|
+
#
|
183
|
+
# @param nbytes [Integer] the number of bytes to read
|
184
|
+
# @param timeout [Numeric] maximum time in seconds to read
|
185
|
+
# @param exception [Class] exception class to be used when the read timeout
|
186
|
+
# reached
|
187
|
+
#
|
188
|
+
# @return [String] the read buffer
|
189
|
+
#
|
190
|
+
# @raise [NotConnectedError] if {#connect} was not called before
|
191
|
+
#
|
192
|
+
# @see NetworkError
|
193
|
+
#
|
194
|
+
def read(nbytes = nil, timeout: nil, exception: nil)
|
195
|
+
raise(NotConnectedError) if closed?
|
196
|
+
deadline = create_deadline(timeout, configuration.read_timeout)
|
197
|
+
return stem_errors { @socket.read(nbytes) } unless deadline.valid?
|
198
|
+
exception ||= configuration.read_timeout_error
|
199
|
+
stem_errors(exception) do
|
200
|
+
@socket.read_with_deadline(nbytes, deadline, exception)
|
201
|
+
end
|
67
202
|
end
|
68
203
|
|
204
|
+
#
|
205
|
+
# @return [String] the currently used address as text.
|
206
|
+
#
|
207
|
+
# @see Address#to_s
|
208
|
+
#
|
209
|
+
def to_s
|
210
|
+
@address&.to_s || ''
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Execute a block with a given overall time limit.
|
215
|
+
#
|
216
|
+
# When you like to ensure that a complete {#read}/{#write} communication
|
217
|
+
# sequence with the server is finished before a given amount of time you use
|
218
|
+
# this method.
|
219
|
+
#
|
220
|
+
# @example ensure to send SMTP welcome message and receive a 4 byte answer
|
221
|
+
# answer = client.with_deadline(2.5) do
|
222
|
+
# client.write('HELO')
|
223
|
+
# client.read(4)
|
224
|
+
# end
|
225
|
+
# # answer is EHLO when server speaks fluent SMPT
|
226
|
+
#
|
227
|
+
# @param timeout [Numeric] maximum time in seconds for all {#read} and
|
228
|
+
# {#write} calls within the block
|
229
|
+
#
|
230
|
+
# @yieldparam client [TCPClient] self
|
231
|
+
# @yieldreturn [Object] any result
|
232
|
+
#
|
233
|
+
# @return [Object] the block`s result
|
234
|
+
#
|
235
|
+
# @raise [NoBlockGivenError] if the block is missing
|
236
|
+
#
|
69
237
|
def with_deadline(timeout)
|
70
238
|
previous_deadline = @deadline
|
71
239
|
raise(NoBlockGivenError) unless block_given?
|
@@ -76,29 +244,34 @@ class TCPClient
|
|
76
244
|
@deadline = previous_deadline
|
77
245
|
end
|
78
246
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
247
|
+
#
|
248
|
+
# Write the given `messages` to the server.
|
249
|
+
#
|
250
|
+
# The optional `timeout` and `exception` parameters allow to override the
|
251
|
+
# `write_timeout` and `write_timeout_error` values of the used
|
252
|
+
# {#configuration}.
|
253
|
+
#
|
254
|
+
# @param messages [String] one or more messages to write
|
255
|
+
# @param timeout [Numeric] maximum time in seconds to write
|
256
|
+
# @param exception [Class] exception class to be used when the write timeout
|
257
|
+
# reached
|
258
|
+
#
|
259
|
+
# @return [Integer] bytes written
|
260
|
+
#
|
261
|
+
# @raise [NotConnectedError] if {#connect} was not called before
|
262
|
+
#
|
263
|
+
def write(*messages, timeout: nil, exception: nil)
|
88
264
|
raise(NotConnectedError) if closed?
|
89
265
|
deadline = create_deadline(timeout, configuration.write_timeout)
|
90
|
-
return @socket.write(*
|
266
|
+
return stem_errors { @socket.write(*messages) } unless deadline.valid?
|
91
267
|
exception ||= configuration.write_timeout_error
|
92
|
-
|
93
|
-
|
268
|
+
stem_errors(exception) do
|
269
|
+
messages.sum do |chunk|
|
270
|
+
@socket.write_with_deadline(chunk.b, deadline, exception)
|
271
|
+
end
|
94
272
|
end
|
95
273
|
end
|
96
274
|
|
97
|
-
def flush
|
98
|
-
@socket&.flush
|
99
|
-
self
|
100
|
-
end
|
101
|
-
|
102
275
|
private
|
103
276
|
|
104
277
|
def create_deadline(timeout, default)
|
@@ -108,8 +281,34 @@ class TCPClient
|
|
108
281
|
def create_socket(timeout, exception)
|
109
282
|
deadline = create_deadline(timeout, configuration.connect_timeout)
|
110
283
|
exception ||= configuration.connect_timeout_error
|
111
|
-
|
112
|
-
|
113
|
-
|
284
|
+
stem_errors(exception) do
|
285
|
+
@socket = TCPSocket.new(address, configuration, deadline, exception)
|
286
|
+
return @socket unless configuration.ssl?
|
287
|
+
SSLSocket.new(@socket, address, configuration, deadline, exception)
|
288
|
+
end
|
114
289
|
end
|
290
|
+
|
291
|
+
def stem_errors(except = nil)
|
292
|
+
yield
|
293
|
+
rescue *NETWORK_ERRORS => e
|
294
|
+
raise unless configuration.normalize_network_errors
|
295
|
+
(except && e.is_a?(except)) ? raise : raise(NetworkError, e)
|
296
|
+
end
|
297
|
+
|
298
|
+
NETWORK_ERRORS =
|
299
|
+
[
|
300
|
+
Errno::EADDRNOTAVAIL,
|
301
|
+
Errno::ECONNABORTED,
|
302
|
+
Errno::ECONNREFUSED,
|
303
|
+
Errno::ECONNRESET,
|
304
|
+
Errno::EHOSTUNREACH,
|
305
|
+
Errno::EINVAL,
|
306
|
+
Errno::ENETUNREACH,
|
307
|
+
Errno::EPIPE,
|
308
|
+
IOError,
|
309
|
+
SocketError
|
310
|
+
].tap do |errors|
|
311
|
+
errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
|
312
|
+
end.freeze
|
313
|
+
private_constant(:NETWORK_ERRORS)
|
115
314
|
end
|
data/rakefile.rb
CHANGED
@@ -1,16 +1,12 @@
|
|
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
|
+
require 'yard'
|
6
7
|
|
7
8
|
$stdout.sync = $stderr.sync = true
|
8
|
-
|
9
|
-
CLOBBER << 'prj'
|
10
|
-
|
9
|
+
CLOBBER << 'prj' << 'doc' << '.yardoc'
|
11
10
|
task(:default) { exec('rake --tasks') }
|
12
|
-
|
13
|
-
Rake::
|
14
|
-
task.pattern = 'test/**/*_test.rb'
|
15
|
-
task.warning = task.verbose = true
|
16
|
-
end
|
11
|
+
RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
|
12
|
+
YARD::Rake::YardocTask.new { |task| task.stats_options = %w[--list-undoc] }
|
data/sample/google_ssl.rb
CHANGED
@@ -2,23 +2,24 @@
|
|
2
2
|
|
3
3
|
require_relative '../lib/tcp-client'
|
4
4
|
|
5
|
-
# create a configuration
|
6
|
-
# - use TLS 1.2
|
5
|
+
# create a configuration:
|
7
6
|
# - don't use internal buffering
|
7
|
+
# - use TLS 1.2 or TLS 1.3
|
8
8
|
cfg =
|
9
9
|
TCPClient::Configuration.create(
|
10
10
|
buffered: false,
|
11
11
|
ssl_params: {
|
12
|
-
|
12
|
+
min_version: :TLS1_2,
|
13
|
+
max_version: :TLS1_3
|
13
14
|
}
|
14
15
|
)
|
15
16
|
|
16
|
-
# request to Google:
|
17
|
-
# - limit all interactions to
|
17
|
+
# request to Google.com:
|
18
|
+
# - limit all network interactions to 1.5 seconds
|
18
19
|
# - use the Configuration cfg
|
19
20
|
# - send a simple HTTP get request
|
20
21
|
# - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
|
21
|
-
TCPClient.with_deadline(
|
22
|
+
TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
|
22
23
|
p client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
|
23
24
|
p client.read(12)
|
24
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
|