tcp-client 0.5.1 → 0.9.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/.gitignore +3 -1
- data/README.md +19 -17
- data/lib/tcp-client/address.rb +44 -1
- data/lib/tcp-client/configuration.rb +221 -62
- data/lib/tcp-client/deadline.rb +2 -0
- data/lib/tcp-client/default_configuration.rb +22 -0
- data/lib/tcp-client/errors.rb +84 -9
- data/lib/tcp-client/mixin/io_with_deadline.rb +31 -2
- data/lib/tcp-client/ssl_socket.rb +20 -2
- data/lib/tcp-client/version.rb +4 -1
- data/lib/tcp-client.rb +220 -50
- data/rakefile.rb +5 -8
- 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 +145 -0
- data/spec/tcp-client/configuration_spec.rb +269 -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 +3 -2
- metadata +31 -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: 20df105b07989653ac9e9c877672318e1da4eb6a5171b088b137422ff03d22db
|
4
|
+
data.tar.gz: 393c2bf3f983f1da7dc8e3517b0cd7b170c2cdede3c546b603def84512e93806
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c94f0a7c51cd5b33edf0de9fb02d454e539f95b79891ad81c81adb4832829059fc34b8c3d4e141594dd0b95752807f1c32dbc9c5192914a5ec02345bb056075c
|
7
|
+
data.tar.gz: 1ab3ef36b1534cf697c911c5d4ce01137dc9860598daa34765521cbec48fe6698294402cf0a0b768f2c460212cb3ce9378adb12ba7e65c76f1531e7bd66a1688
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -11,24 +11,26 @@ 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
|
|
31
|
-
|
33
|
+
## Installation
|
32
34
|
|
33
35
|
Use [Bundler](http://gembundler.com/) to use TCPClient in your own project:
|
34
36
|
|
@@ -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
@@ -3,9 +3,44 @@
|
|
3
3
|
require 'socket'
|
4
4
|
|
5
5
|
class TCPClient
|
6
|
+
#
|
7
|
+
# The address used by a TCPClient
|
8
|
+
#
|
6
9
|
class Address
|
7
|
-
|
10
|
+
#
|
11
|
+
# @return [String] the host name
|
12
|
+
#
|
13
|
+
attr_reader :hostname
|
8
14
|
|
15
|
+
#
|
16
|
+
# @return [Addrinfo] the address info
|
17
|
+
#
|
18
|
+
attr_reader :addrinfo
|
19
|
+
|
20
|
+
#
|
21
|
+
# Initializes an address
|
22
|
+
# @overload initialize(addr)
|
23
|
+
# The addr can be specified as
|
24
|
+
# - a valid named address containing the port like +my.host.test:80+
|
25
|
+
# - a valid TCPv4 address like +142.250.181.206:80+
|
26
|
+
# - a valid TCPv6 address like +[2001:16b8:5093:3500:ad77:abe6:eb88:47b6]:80+
|
27
|
+
#
|
28
|
+
# @param addr [String] address string
|
29
|
+
#
|
30
|
+
# @overload initialize(address)
|
31
|
+
# Used to create a copy
|
32
|
+
#
|
33
|
+
# @param address [Address]
|
34
|
+
#
|
35
|
+
# @overload initialize(addrinfo)
|
36
|
+
#
|
37
|
+
# @param addrinfo [Addrinfo] containing the addressed host and port
|
38
|
+
#
|
39
|
+
# @overload initialize(port)
|
40
|
+
# Adresses the port on the local machine.
|
41
|
+
#
|
42
|
+
# @param port [Integer] the addressed port
|
43
|
+
#
|
9
44
|
def initialize(addr)
|
10
45
|
case addr
|
11
46
|
when self.class
|
@@ -20,20 +55,28 @@ class TCPClient
|
|
20
55
|
@addrinfo.freeze
|
21
56
|
end
|
22
57
|
|
58
|
+
#
|
59
|
+
# @return [String] text representation of self as "<host>:<port>"
|
60
|
+
#
|
23
61
|
def to_s
|
24
62
|
return "[#{@hostname}]:#{@addrinfo.ip_port}" if @hostname.index(':') # IP6
|
25
63
|
"#{@hostname}:#{@addrinfo.ip_port}"
|
26
64
|
end
|
27
65
|
|
66
|
+
#
|
67
|
+
# @return [Hash] containing the host and port
|
68
|
+
#
|
28
69
|
def to_h
|
29
70
|
{ host: @hostname, port: @addrinfo.ip_port }
|
30
71
|
end
|
31
72
|
|
73
|
+
# @!visibility private
|
32
74
|
def ==(other)
|
33
75
|
to_h == other.to_h
|
34
76
|
end
|
35
77
|
alias eql? ==
|
36
78
|
|
79
|
+
# @!visibility private
|
37
80
|
def equal?(other)
|
38
81
|
self.class == other.class && self == other
|
39
82
|
end
|
@@ -3,126 +3,285 @@
|
|
3
3
|
require_relative 'errors'
|
4
4
|
|
5
5
|
class TCPClient
|
6
|
+
#
|
7
|
+
# A Configuration is used to configure the behavior of a {TCPClient} instance.
|
8
|
+
#
|
9
|
+
# It allows to specify to monitor timeout, how to handle exceptions, if SSL
|
10
|
+
# should be used and to setup the underlying Socket.
|
11
|
+
#
|
6
12
|
class Configuration
|
13
|
+
#
|
14
|
+
# @overload create(options)
|
15
|
+
# Shorthand to create a new configuration with given options.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# config = TCPClient::Configuration.create(buffered: false)
|
19
|
+
#
|
20
|
+
# @param options [Hash] see {#initialize} for details
|
21
|
+
#
|
22
|
+
# @return [Configuration] the initialized configuration
|
23
|
+
#
|
24
|
+
# @overload create(&block)
|
25
|
+
# Shorthand to initialize a new configuration.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# config = TCPClient::Configuration.create do |cfg|
|
29
|
+
# cfg.buffered = false
|
30
|
+
# cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @yieldparam cfg {Configuration}
|
34
|
+
#
|
35
|
+
# @return [Configuration] the initialized configuration
|
36
|
+
#
|
7
37
|
def self.create(options = {})
|
8
|
-
|
9
|
-
yield(
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
attr_reader :buffered,
|
14
|
-
:keep_alive,
|
15
|
-
:reverse_lookup,
|
16
|
-
:connect_timeout,
|
17
|
-
:read_timeout,
|
18
|
-
:write_timeout,
|
19
|
-
:connect_timeout_error,
|
20
|
-
:read_timeout_error,
|
21
|
-
:write_timeout_error
|
22
|
-
attr_accessor :ssl_params
|
38
|
+
cfg = new(options)
|
39
|
+
yield(cfg) if block_given?
|
40
|
+
cfg
|
41
|
+
end
|
23
42
|
|
43
|
+
#
|
44
|
+
# Intializes the instance with given options.
|
45
|
+
#
|
46
|
+
# @param options [Hash]
|
47
|
+
# @option options [Boolean] :buffered, see {#buffered}
|
48
|
+
# @option options [Boolean] :keep_alive, see {#keep_alive}
|
49
|
+
# @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
|
50
|
+
# @option options [Boolean] :normalize_network_errors, see {#normalize_network_errors}
|
51
|
+
# @option options [Numeric] :connect_timeout, see {#connect_timeout}
|
52
|
+
# @option options [Exception] :connect_timeout_error, see {#connect_timeout_error}
|
53
|
+
# @option options [Numeric] :read_timeout, see {#read_timeout}
|
54
|
+
# @option options [Exception] :read_timeout_error, see {#read_timeout_error}
|
55
|
+
# @option options [Numeric] :write_timeout, see {#write_timeout}
|
56
|
+
# @option options [Exception] :write_timeout_error, see {#write_timeout_error}
|
57
|
+
# @option options [Hash<Symbol, Object>] :ssl_params, see {#ssl_params}
|
58
|
+
#
|
24
59
|
def initialize(options = {})
|
25
60
|
@buffered = @keep_alive = @reverse_lookup = true
|
26
61
|
self.timeout = @ssl_params = nil
|
27
62
|
@connect_timeout_error = ConnectTimeoutError
|
28
63
|
@read_timeout_error = ReadTimeoutError
|
29
64
|
@write_timeout_error = WriteTimeoutError
|
65
|
+
@normalize_network_errors = false
|
30
66
|
options.each_pair { |attribute, value| set(attribute, value) }
|
31
67
|
end
|
32
68
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@ssl_params = Hash[@ssl_params] if @ssl_params
|
41
|
-
self
|
42
|
-
end
|
43
|
-
|
44
|
-
def ssl?
|
45
|
-
@ssl_params ? true : false
|
46
|
-
end
|
47
|
-
|
48
|
-
def ssl=(value)
|
49
|
-
@ssl_params =
|
50
|
-
if Hash === value
|
51
|
-
Hash[value]
|
52
|
-
else
|
53
|
-
value ? {} : nil
|
54
|
-
end
|
55
|
-
end
|
69
|
+
#
|
70
|
+
# Enables/disables use of Socket-level buffers.
|
71
|
+
#
|
72
|
+
# @return [true] if the connection is allowed to use internal buffers (default)
|
73
|
+
# @return [false] if buffering is not allowed
|
74
|
+
#
|
75
|
+
attr_reader :buffered
|
56
76
|
|
57
77
|
def buffered=(value)
|
58
78
|
@buffered = value ? true : false
|
59
79
|
end
|
60
80
|
|
81
|
+
#
|
82
|
+
# Enables/disables use of Socket-level keep alive handling.
|
83
|
+
#
|
84
|
+
# @return [true] if the connection is allowed to use keep alive signals (default)
|
85
|
+
# @return [false] if the connection should not check keep alive
|
86
|
+
#
|
87
|
+
attr_reader :keep_alive
|
88
|
+
|
61
89
|
def keep_alive=(value)
|
62
90
|
@keep_alive = value ? true : false
|
63
91
|
end
|
64
92
|
|
93
|
+
#
|
94
|
+
# Enables/disables address lookup.
|
95
|
+
#
|
96
|
+
# @return [true] if the connection is allowed to lookup the address (default)
|
97
|
+
# @return [false] if the address lookup is not required
|
98
|
+
#
|
99
|
+
attr_reader :reverse_lookup
|
100
|
+
|
65
101
|
def reverse_lookup=(value)
|
66
102
|
@reverse_lookup = value ? true : false
|
67
103
|
end
|
68
104
|
|
69
|
-
|
70
|
-
|
105
|
+
#
|
106
|
+
# Enables/disables if network exceptions should be raised as {NetworkError}.
|
107
|
+
#
|
108
|
+
# @return [true] if all network exceptions should be raised as {NetworkError}
|
109
|
+
# @return [false] if socket/system errors should not be normalzed (default)
|
110
|
+
#
|
111
|
+
attr_reader :normalize_network_errors
|
112
|
+
|
113
|
+
def normalize_network_errors=(value)
|
114
|
+
@normalize_network_errors = value ? true : false
|
71
115
|
end
|
72
116
|
|
73
|
-
|
74
|
-
|
117
|
+
#
|
118
|
+
# @attribute [w] timeout
|
119
|
+
# Shorthand to set timeout value for connect, read and write at once or to disable any timeout monitoring
|
120
|
+
#
|
121
|
+
# @return [Numeric] maximum time in seconds for any action
|
122
|
+
# @return [nil] if all timeout monitoring should be disabled (default)
|
123
|
+
#
|
124
|
+
# @see #connect_timeout
|
125
|
+
# @see #read_timeout
|
126
|
+
# @see #write_timeout
|
127
|
+
#
|
128
|
+
def timeout=(value)
|
129
|
+
@connect_timeout = @write_timeout = @read_timeout = seconds(value)
|
75
130
|
end
|
76
131
|
|
77
|
-
|
78
|
-
|
132
|
+
#
|
133
|
+
# @attribute [w] timeout_error
|
134
|
+
# Shorthand to configure exception class raised when connect, read or write exceeded the configured timeout
|
135
|
+
#
|
136
|
+
# @return [Class] exception class raised
|
137
|
+
#
|
138
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
139
|
+
#
|
140
|
+
# @see #connect_timeout_error
|
141
|
+
# @see #read_timeout_error
|
142
|
+
# @see #write_timeout_error
|
143
|
+
#
|
144
|
+
def timeout_error=(value)
|
145
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
146
|
+
@connect_timeout_error =
|
147
|
+
@read_timeout_error = @write_timeout_error = value
|
79
148
|
end
|
80
149
|
|
81
|
-
|
82
|
-
|
150
|
+
#
|
151
|
+
# Configures maximum time in seconds to establish a connection.
|
152
|
+
#
|
153
|
+
# @return [Numeric] maximum time in seconds to establish a connection
|
154
|
+
# @return [nil] if the connect time should not be checked (default)
|
155
|
+
#
|
156
|
+
attr_reader :connect_timeout
|
157
|
+
|
158
|
+
def connect_timeout=(value)
|
159
|
+
@connect_timeout = seconds(value)
|
83
160
|
end
|
84
161
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
162
|
+
#
|
163
|
+
# @return [Class] exception class raised if a {TCPClient#connect} timed out
|
164
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
165
|
+
#
|
166
|
+
attr_reader :connect_timeout_error
|
167
|
+
|
168
|
+
def connect_timeout_error=(value)
|
169
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
170
|
+
@connect_timeout_error = value
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Configures maximum time in seconds to finish a {TCPClient#read}.
|
175
|
+
#
|
176
|
+
# @return [Numeric] maximum time in seconds to finish a {TCPClient#read} request
|
177
|
+
# @return [nil] if the read time should not be checked (default)
|
178
|
+
#
|
179
|
+
attr_reader :read_timeout
|
180
|
+
|
181
|
+
def read_timeout=(value)
|
182
|
+
@read_timeout = seconds(value)
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# @return [Class] exception class raised if a {TCPClient#read} timed out
|
187
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
188
|
+
#
|
189
|
+
attr_reader :read_timeout_error
|
190
|
+
|
191
|
+
def read_timeout_error=(value)
|
192
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
193
|
+
@read_timeout_error = value
|
89
194
|
end
|
90
195
|
|
91
|
-
|
92
|
-
|
93
|
-
|
196
|
+
#
|
197
|
+
# Configures maximum time in seconds to finish a {TCPClient#write}.
|
198
|
+
#
|
199
|
+
# @return [Numeric] maximum time in seconds to finish a {TCPClient#write} request
|
200
|
+
# @return [nil] if the write time should not be checked (default)
|
201
|
+
#
|
202
|
+
attr_reader :write_timeout
|
203
|
+
|
204
|
+
def write_timeout=(value)
|
205
|
+
@write_timeout = seconds(value)
|
94
206
|
end
|
95
207
|
|
96
|
-
|
97
|
-
|
98
|
-
|
208
|
+
#
|
209
|
+
# @return [Class] exception class raised if a {TCPClient#write} timed out
|
210
|
+
# @raise [NotAnExceptionError] if given argument is not an Exception class
|
211
|
+
#
|
212
|
+
attr_reader :write_timeout_error
|
213
|
+
|
214
|
+
def write_timeout_error=(value)
|
215
|
+
raise(NotAnExceptionError, value) unless exception_class?(value)
|
216
|
+
@write_timeout_error = value
|
99
217
|
end
|
100
218
|
|
101
|
-
|
102
|
-
|
103
|
-
|
219
|
+
#
|
220
|
+
# @attribute ssl?
|
221
|
+
# @return [Boolean] wheter SSL is configured, see {#ssl_params}
|
222
|
+
#
|
223
|
+
def ssl?
|
224
|
+
@ssl_params ? true : false
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# Configures the SSL parameters used to initialize a SSL context.
|
229
|
+
#
|
230
|
+
# @return [Hash<Symbol, Object>] SSL parameters for the SSL context
|
231
|
+
# @return [nil] if no SSL should be used (default)
|
232
|
+
#
|
233
|
+
attr_reader :ssl_params
|
234
|
+
|
235
|
+
def ssl_params=(value)
|
236
|
+
@ssl_params =
|
237
|
+
if value.respond_to?(:to_hash)
|
238
|
+
Hash[value.to_hash]
|
239
|
+
elsif value.respond_to?(:to_h)
|
240
|
+
Hash[value.to_h]
|
241
|
+
else
|
242
|
+
value ? {} : nil
|
243
|
+
end
|
104
244
|
end
|
245
|
+
alias ssl= ssl_params=
|
105
246
|
|
247
|
+
#
|
248
|
+
# @return [Hash] configuration as a Hash
|
249
|
+
#
|
106
250
|
def to_h
|
107
251
|
{
|
108
252
|
buffered: @buffered,
|
109
253
|
keep_alive: @keep_alive,
|
110
254
|
reverse_lookup: @reverse_lookup,
|
111
255
|
connect_timeout: @connect_timeout,
|
112
|
-
read_timeout: @read_timeout,
|
113
|
-
write_timeout: @write_timeout,
|
114
256
|
connect_timeout_error: @connect_timeout_error,
|
257
|
+
read_timeout: @read_timeout,
|
115
258
|
read_timeout_error: @read_timeout_error,
|
259
|
+
write_timeout: @write_timeout,
|
116
260
|
write_timeout_error: @write_timeout_error,
|
117
261
|
ssl_params: @ssl_params
|
118
262
|
}
|
119
263
|
end
|
120
264
|
|
265
|
+
# @!visibility private
|
266
|
+
def freeze
|
267
|
+
@ssl_params.freeze
|
268
|
+
super
|
269
|
+
end
|
270
|
+
|
271
|
+
# @!visibility private
|
272
|
+
def initialize_copy(_org)
|
273
|
+
super
|
274
|
+
@ssl_params = Hash[@ssl_params] if @ssl_params
|
275
|
+
self
|
276
|
+
end
|
277
|
+
|
278
|
+
# @!visibility private
|
121
279
|
def ==(other)
|
122
280
|
to_h == other.to_h
|
123
281
|
end
|
124
282
|
alias eql? ==
|
125
283
|
|
284
|
+
# @!visibility private
|
126
285
|
def equal?(other)
|
127
286
|
self.class == other.class && self == other
|
128
287
|
end
|
@@ -136,7 +295,7 @@ class TCPClient
|
|
136
295
|
def set(attribute, value)
|
137
296
|
public_send("#{attribute}=", value)
|
138
297
|
rescue NoMethodError
|
139
|
-
raise(
|
298
|
+
raise(UnknownAttributeError, attribute)
|
140
299
|
end
|
141
300
|
|
142
301
|
def seconds(value)
|
data/lib/tcp-client/deadline.rb
CHANGED
@@ -6,14 +6,36 @@ class TCPClient
|
|
6
6
|
@default_configuration = Configuration.new
|
7
7
|
|
8
8
|
class << self
|
9
|
+
#
|
10
|
+
# @return [Configuration] used by default if no dedicated configuration was specified
|
11
|
+
#
|
9
12
|
attr_reader :default_configuration
|
10
13
|
|
14
|
+
#
|
15
|
+
# Configure the default configuration which is used if no dedicated
|
16
|
+
# configuration was specified.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# TCPClient.configure do |cfg|
|
20
|
+
# cfg.buffered = false
|
21
|
+
# cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @param options [Hash] see {Configuration#initialize} for details
|
25
|
+
#
|
26
|
+
# @yieldparam cfg {Configuration} the new configuration
|
27
|
+
#
|
28
|
+
# @return [Configuration] the new default configuration
|
29
|
+
#
|
11
30
|
def configure(options = {}, &block)
|
12
31
|
@default_configuration = Configuration.create(options, &block)
|
13
32
|
end
|
14
33
|
end
|
15
34
|
|
16
35
|
class Configuration
|
36
|
+
#
|
37
|
+
# @return [Configuration] used by default if no dedicated configuration was specified
|
38
|
+
#
|
17
39
|
def self.default
|
18
40
|
TCPClient.default_configuration
|
19
41
|
end
|