tcp-client 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe13751730310529097b79992b58a5ed212ecc4119c576c04bcf7b0c4a0373c7
4
- data.tar.gz: 558f73ee8e06309c8d6ca52cdc4309ceac853a9587b6891959b67f5d49c89413
3
+ metadata.gz: e88146d7f1f966d6c9e7dce6a876c17dca7383ee88eb76a8e5caf36076a03720
4
+ data.tar.gz: 493d8cbf748789df4efd0301679082023c140f23dbc2a3d52ccfcf8d71a4a1b4
5
5
  SHA512:
6
- metadata.gz: a2a9d54d6de01896b83180e7e94afde9a1e2b8f62ca24c9e91d63a7c809a06286357fdecb5a3ad6fce1dceaaa50067a6f64263c83f4069d07b3ad837db138b9d
7
- data.tar.gz: ac6168903ebb5c953e850fb97d80281c63b70b208335d34611f57829199e623fac3fbea85959d09d8c9ddec1edbe23ffef23cd9e5e94e788b74b0ded665dd5c2
6
+ metadata.gz: 070ba42a0a185a67ae1ac4d1912220ab0aa297a6cdc4147d3afaf8593129f3b25da2ecad7a8569651c6d54dd1ba11c7705ebf8bcc1def2d525388025b45d12b3
7
+ data.tar.gz: 4c34127d89744a537d5b64c578ac4c1e2121c27e9412208f7c0abcf74999bb1e85c1c246bebfea83401c8caaf4c472daee7ff5e599989632076784881131b3ea
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --readme README.md
2
+ --title 'tcp-client Documentation'
3
+ --charset utf-8
4
+ --markup markdown
5
+ 'lib/**/*.rb' - 'LICENSE'
data/README.md CHANGED
@@ -2,16 +2,14 @@
2
2
 
3
3
  A TCP client implementation with working timeout support.
4
4
 
5
- [![Gem Version](https://badge.fury.io/rb/tcp-client.svg)](https://badge.fury.io/rb/tcp-client)
5
+ - Gem: [rubygems.org](https://rubygems.org/gems/tcp-client)
6
+ - Source: [github.com](https://github.com/mblumtritt/tcp-client)
7
+ - Help: [rubydoc.info](https://rubydoc.info/github/mblumtritt/tcp-client/main/index)
6
8
 
7
9
  ## Description
8
10
 
9
11
  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.
10
12
 
11
- ## Help
12
-
13
- The latest help can be found at [rubydoc.info](https://rubydoc.info/github/mblumtritt/tcp-client/main/index)
14
-
15
13
  ## Sample
16
14
 
17
15
  ```ruby
@@ -29,11 +27,14 @@ cfg = TCPClient::Configuration.create(
29
27
  # - limit all network interactions to 1.5 seconds
30
28
  # - use the Configuration cfg
31
29
  # - send a simple HTTP get request
32
- # - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
33
- TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
34
- client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") # >= 40
35
- client.read(12) # => "HTTP/1.1 200"
36
- end
30
+ # - read the returned message and headers
31
+ response =
32
+ TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
33
+ client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") #=> 40
34
+ client.readline("\r\n\r\n") #=> see response
35
+ end
36
+
37
+ puts(response)
37
38
  ```
38
39
 
39
40
  For more samples see [the samples dir](https://github.com/mblumtritt/tcp-client/tree/main/sample)
data/gems.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source('https://rubygems.org') { gemspec }
3
+ source 'https://rubygems.org'
4
+ gemspec
@@ -21,23 +21,30 @@ class TCPClient
21
21
  # Initializes an address
22
22
  # @overload initialize(addr)
23
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
24
  #
28
- # @param addr [String] address string
25
+ # - a valid named address containing the port like "my.host.test:80"
26
+ # - a valid TCPv4 address like "142.250.181.206:80"
27
+ # - a valid TCPv6 address like
28
+ # "[2001:16b8:5093:3500:ad77:abe6:eb88:47b6]:80"
29
29
  #
30
- # @overload initialize(address)
31
- # Used to create a copy
30
+ # @example create an Address instance with a host name and port
31
+ # Address.new('www.google.com:80')
32
+ #
33
+ # @param addr [String] address containing host and port name
32
34
  #
33
- # @param address [Address]
34
35
  #
35
36
  # @overload initialize(addrinfo)
36
37
  #
38
+ # @example create an Address with an Addrinfo
39
+ # Address.new(Addrinfo.tcp('www.google.com', 'http'))
40
+ #
37
41
  # @param addrinfo [Addrinfo] containing the addressed host and port
38
42
  #
39
43
  # @overload initialize(port)
40
- # Adresses the port on the local machine.
44
+ # Addresses the port on the local machine.
45
+ #
46
+ # @example create an Address for localhost on port 80
47
+ # Address.new(80)
41
48
  #
42
49
  # @param port [Integer] the addressed port
43
50
  #
@@ -56,18 +63,27 @@ class TCPClient
56
63
  end
57
64
 
58
65
  #
59
- # @return [String] text representation of self as "<host>:<port>"
66
+ # @attribute [r] port
67
+ # @return [Integer] the port number
68
+ #
69
+ def port
70
+ @addrinfo.ip_port
71
+ end
72
+
73
+ #
74
+ # @return [String] text representation of self as "host:port"
60
75
  #
61
76
  def to_s
62
- return "[#{@hostname}]:#{@addrinfo.ip_port}" if @hostname.index(':') # IP6
63
- "#{@hostname}:#{@addrinfo.ip_port}"
77
+ hostname.index(':') ? "[#{hostname}]:#{port}" : "#{hostname}:#{port}"
64
78
  end
65
79
 
66
80
  #
67
- # @return [Hash] containing the host and port
81
+ # Convert `self` to a Hash containing host and port attribute.
82
+ #
83
+ # @return [Hash] host and port
68
84
  #
69
85
  def to_h
70
- { host: @hostname, port: @addrinfo.ip_port }
86
+ { host: hostname, port: port }
71
87
  end
72
88
 
73
89
  # @!visibility private
@@ -89,7 +105,7 @@ class TCPClient
89
105
  end
90
106
 
91
107
  def init_from_addrinfo(addrinfo)
92
- @hostname, _port = addrinfo.getnameinfo(Socket::NI_NUMERICSERV)
108
+ @hostname = addrinfo.getnameinfo(Socket::NI_NUMERICSERV).first
93
109
  @addrinfo = addrinfo
94
110
  end
95
111
 
@@ -102,7 +118,7 @@ class TCPClient
102
118
  def from_string(str)
103
119
  idx = str.rindex(':') or return nil, str.to_i
104
120
  name = str[0, idx].delete_prefix('[').delete_suffix(']')
105
- [name, str[idx + 1, str.size - idx].to_i]
121
+ [name.empty? ? nil : name, str[idx + 1, str.size - idx].to_i]
106
122
  end
107
123
  end
108
124
  end
@@ -6,55 +6,55 @@ class TCPClient
6
6
  #
7
7
  # A Configuration is used to configure the behavior of a {TCPClient} instance.
8
8
  #
9
- # It allows to specify to monitor timeout, how to handle exceptions, if SSL
9
+ # It allows to specify the monitor timeout, how to handle exceptions, if SSL
10
10
  # should be used and to setup the underlying Socket.
11
11
  #
12
12
  class Configuration
13
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.
14
+ # Shorthand to create a new configuration.
26
15
  #
16
+ # @overload create()
27
17
  # @example
28
18
  # config = TCPClient::Configuration.create do |cfg|
29
19
  # cfg.buffered = false
30
20
  # cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
31
21
  # end
32
22
  #
33
- # @yieldparam cfg {Configuration}
23
+ # @yieldparam configuration {Configuration}
34
24
  #
35
- # @return [Configuration] the initialized configuration
25
+ # @overload create(options)
26
+ # @example
27
+ # config = TCPClient::Configuration.create(buffered: false)
28
+ #
29
+ # @param options [{Symbol => Object}] see {#initialize} for details
30
+ #
31
+ # @return [Configuration] the initialized configuration
36
32
  #
37
33
  def self.create(options = {})
38
- cfg = new(options)
39
- yield(cfg) if block_given?
40
- cfg
34
+ configuration = new(options)
35
+ yield(configuration) if block_given?
36
+ configuration
41
37
  end
42
38
 
43
39
  #
44
- # Intializes the instance with given options.
40
+ # Initializes the instance with given options.
45
41
  #
46
- # @param options [Hash]
42
+ # @param options [{Symbol => Object}]
47
43
  # @option options [Boolean] :buffered, see {#buffered}
48
44
  # @option options [Boolean] :keep_alive, see {#keep_alive}
49
45
  # @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
50
- # @option options [Boolean] :normalize_network_errors, see {#normalize_network_errors}
46
+ # @option options [{Symbol => Object}] :ssl_params, see {#ssl_params}
51
47
  # @option options [Numeric] :connect_timeout, see {#connect_timeout}
52
- # @option options [Exception] :connect_timeout_error, see {#connect_timeout_error}
48
+ # @option options [Class<Exception>] :connect_timeout_error, see
49
+ # {#connect_timeout_error}
53
50
  # @option options [Numeric] :read_timeout, see {#read_timeout}
54
- # @option options [Exception] :read_timeout_error, see {#read_timeout_error}
51
+ # @option options [Class<Exception>] :read_timeout_error, see
52
+ # {#read_timeout_error}
55
53
  # @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}
54
+ # @option options [Class<Exception>] :write_timeout_error, see
55
+ # {#write_timeout_error}
56
+ # @option options [Boolean] :normalize_network_errors, see
57
+ # {#normalize_network_errors}
58
58
  #
59
59
  def initialize(options = {})
60
60
  @buffered = @keep_alive = @reverse_lookup = true
@@ -66,11 +66,13 @@ class TCPClient
66
66
  options.each_pair { |attribute, value| set(attribute, value) }
67
67
  end
68
68
 
69
+ # @!group Instance Attributes Socket Level
70
+
69
71
  #
70
- # Enables/disables use of Socket-level buffers.
72
+ # Enables/disables use of Socket-level buffering
71
73
  #
72
- # @return [true] if the connection is allowed to use internal buffers (default)
73
- # @return [false] if buffering is not allowed
74
+ # @return [Boolean] whether the connection is allowed to use internal
75
+ # buffers (default) or not
74
76
  #
75
77
  attr_reader :buffered
76
78
 
@@ -81,8 +83,8 @@ class TCPClient
81
83
  #
82
84
  # Enables/disables use of Socket-level keep alive handling.
83
85
  #
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
+ # @return [Boolean] whether the connection is allowed to use keep alive
87
+ # signals (default) or not
86
88
  #
87
89
  attr_reader :keep_alive
88
90
 
@@ -93,8 +95,8 @@ class TCPClient
93
95
  #
94
96
  # Enables/disables address lookup.
95
97
  #
96
- # @return [true] if the connection is allowed to lookup the address (default)
97
- # @return [false] if the address lookup is not required
98
+ # @return [Boolean] whether the connection is allowed to lookup the address
99
+ # (default) or not
98
100
  #
99
101
  attr_reader :reverse_lookup
100
102
 
@@ -103,55 +105,45 @@ class TCPClient
103
105
  end
104
106
 
105
107
  #
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)
108
+ # @!parse attr_reader :ssl?
109
+ # @return [Boolean] whether SSL is configured, see {#ssl_params}
110
110
  #
111
- attr_reader :normalize_network_errors
112
-
113
- def normalize_network_errors=(value)
114
- @normalize_network_errors = value ? true : false
111
+ def ssl?
112
+ @ssl_params ? true : false
115
113
  end
116
114
 
117
115
  #
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)
116
+ # Parameters used to initialize a SSL context. SSL/TLS will only be used if
117
+ # this attribute is not `nil`.
123
118
  #
124
- # @see #connect_timeout
125
- # @see #read_timeout
126
- # @see #write_timeout
119
+ # @return [{Symbol => Object}] SSL parameters for the SSL context
120
+ # @return [nil] if no SSL should be used (default)
127
121
  #
128
- def timeout=(value)
129
- @connect_timeout = @write_timeout = @read_timeout = seconds(value)
130
- end
122
+ attr_reader :ssl_params
131
123
 
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
124
+ def ssl_params=(value)
125
+ @ssl_params =
126
+ if value.respond_to?(:to_hash)
127
+ Hash[value.to_hash]
128
+ elsif value.respond_to?(:to_h)
129
+ value.nil? ? nil : Hash[value.to_h]
130
+ else
131
+ value ? {} : nil
132
+ end
148
133
  end
134
+ alias ssl= ssl_params=
135
+
136
+ # @!endgroup
149
137
 
138
+ # @!group Instance Attributes Timeout Monitoring
139
+
140
+ #
141
+ # The maximum time in seconds to establish a connection.
150
142
  #
151
- # Configures maximum time in seconds to establish a connection.
143
+ # @return [Numeric] maximum time in seconds
144
+ # @return [nil] if the connect time should not be monitored (default)
152
145
  #
153
- # @return [Numeric] maximum time in seconds to establish a connection
154
- # @return [nil] if the connect time should not be checked (default)
146
+ # @see TCPClient#connect
155
147
  #
156
148
  attr_reader :connect_timeout
157
149
 
@@ -160,7 +152,10 @@ class TCPClient
160
152
  end
161
153
 
162
154
  #
163
- # @return [Class] exception class raised if a {TCPClient#connect} timed out
155
+ # The exception class which will be raised if {TCPClient#connect} can not
156
+ # be finished in time.
157
+ #
158
+ # @return [Class<Exception>] exception class raised
164
159
  # @raise [NotAnExceptionError] if given argument is not an Exception class
165
160
  #
166
161
  attr_reader :connect_timeout_error
@@ -171,10 +166,12 @@ class TCPClient
171
166
  end
172
167
 
173
168
  #
174
- # Configures maximum time in seconds to finish a {TCPClient#read}.
169
+ # The maximum time in seconds to read from a connection.
170
+ #
171
+ # @return [Numeric] maximum time in seconds
172
+ # @return [nil] if the read time should not be monitored (default)
175
173
  #
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)
174
+ # @see TCPClient#read
178
175
  #
179
176
  attr_reader :read_timeout
180
177
 
@@ -183,7 +180,10 @@ class TCPClient
183
180
  end
184
181
 
185
182
  #
186
- # @return [Class] exception class raised if a {TCPClient#read} timed out
183
+ # The exception class which will be raised if {TCPClient#read} can not be
184
+ # finished in time.
185
+ #
186
+ # @return [Class<Exception>] exception class raised
187
187
  # @raise [NotAnExceptionError] if given argument is not an Exception class
188
188
  #
189
189
  attr_reader :read_timeout_error
@@ -194,10 +194,12 @@ class TCPClient
194
194
  end
195
195
 
196
196
  #
197
- # Configures maximum time in seconds to finish a {TCPClient#write}.
197
+ # The maximum time in seconds to write to a connection.
198
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)
199
+ # @return [Numeric] maximum time in seconds
200
+ # @return [nil] if the write time should not be monitored (default)
201
+ #
202
+ # @see TCPClient#write
201
203
  #
202
204
  attr_reader :write_timeout
203
205
 
@@ -206,7 +208,10 @@ class TCPClient
206
208
  end
207
209
 
208
210
  #
209
- # @return [Class] exception class raised if a {TCPClient#write} timed out
211
+ # The exception class which will be raised if {TCPClient#write} can not be
212
+ # finished in time.
213
+ #
214
+ # @return [Class<Exception>] exception class raised
210
215
  # @raise [NotAnExceptionError] if given argument is not an Exception class
211
216
  #
212
217
  attr_reader :write_timeout_error
@@ -217,48 +222,76 @@ class TCPClient
217
222
  end
218
223
 
219
224
  #
220
- # @attribute ssl?
221
- # @return [Boolean] wheter SSL is configured, see {#ssl_params}
225
+ # @attribute [w] timeout
226
+ # Shorthand to set maximum time in seconds for all timeout monitoring.
222
227
  #
223
- def ssl?
224
- @ssl_params ? true : false
228
+ # @return [Numeric] maximum time in seconds for any action
229
+ # @return [nil] if all timeout monitoring should be disabled (default)
230
+ #
231
+ # @see #connect_timeout
232
+ # @see #read_timeout
233
+ # @see #write_timeout
234
+ #
235
+ def timeout=(value)
236
+ @connect_timeout = @write_timeout = @read_timeout = seconds(value)
225
237
  end
226
238
 
227
239
  #
228
- # Configures the SSL parameters used to initialize a SSL context.
240
+ # @attribute [w] timeout_error
241
+ # Shorthand to set the exception class which will by raised by any reached
242
+ # timeout.
229
243
  #
230
- # @return [Hash<Symbol, Object>] SSL parameters for the SSL context
231
- # @return [nil] if no SSL should be used (default)
244
+ # @return [Class<Exception>] exception class raised
232
245
  #
233
- attr_reader :ssl_params
246
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
247
+ #
248
+ # @see #connect_timeout_error
249
+ # @see #read_timeout_error
250
+ # @see #write_timeout_error
251
+ #
252
+ def timeout_error=(value)
253
+ raise(NotAnExceptionError, value) unless exception_class?(value)
254
+ @connect_timeout_error =
255
+ @read_timeout_error = @write_timeout_error = value
256
+ end
234
257
 
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
258
+ # @!endgroup
259
+
260
+ #
261
+ # Enables/disables if network exceptions should be raised as {NetworkError}.
262
+ #
263
+ # This allows to handle all network/socket related exceptions like
264
+ # `SocketError`, `OpenSSL::SSL::SSLError`, `IOError`, etc. in a uniform
265
+ # manner. If this option is set to true all these error cases are raised as
266
+ # {NetworkError} and can be easily captured.
267
+ #
268
+ # @return [Boolean] whether all network exceptions should be raised as
269
+ # {NetworkError}, or not (default)
270
+ #
271
+ attr_reader :normalize_network_errors
272
+
273
+ def normalize_network_errors=(value)
274
+ @normalize_network_errors = value ? true : false
244
275
  end
245
- alias ssl= ssl_params=
246
276
 
247
277
  #
248
- # @return [Hash] configuration as a Hash
278
+ # @return [{Symbol => Object}] Hash containing all attributes
279
+ #
280
+ # @see #initialize
249
281
  #
250
282
  def to_h
251
283
  {
252
284
  buffered: @buffered,
253
285
  keep_alive: @keep_alive,
254
286
  reverse_lookup: @reverse_lookup,
287
+ ssl_params: @ssl_params,
255
288
  connect_timeout: @connect_timeout,
256
289
  connect_timeout_error: @connect_timeout_error,
257
290
  read_timeout: @read_timeout,
258
291
  read_timeout_error: @read_timeout_error,
259
292
  write_timeout: @write_timeout,
260
293
  write_timeout_error: @write_timeout_error,
261
- ssl_params: @ssl_params
294
+ normalize_network_errors: @normalize_network_errors
262
295
  }
263
296
  end
264
297
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  class TCPClient
4
4
  class Deadline
5
- MONOTONIC = defined?(Process::CLOCK_MONOTONIC) ? true : false
6
-
7
5
  def initialize(timeout)
8
6
  timeout = timeout&.to_f
9
7
  @deadline = timeout&.positive? ? now + timeout : 0
@@ -19,7 +17,7 @@ class TCPClient
19
17
 
20
18
  private
21
19
 
22
- if MONOTONIC
20
+ if defined?(Process::CLOCK_MONOTONIC)
23
21
  def now
24
22
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
25
23
  end
@@ -7,13 +7,17 @@ class TCPClient
7
7
 
8
8
  class << self
9
9
  #
10
- # @return [Configuration] used by default if no dedicated configuration was specified
10
+ # The default configuration.
11
+ # This is used by default if no dedicated configuration was specified to
12
+ # {.open} or {#connect}.
13
+ #
14
+ # @return [Configuration]
11
15
  #
12
16
  attr_reader :default_configuration
13
17
 
14
18
  #
15
- # Configure the default configuration which is used if no dedicated
16
- # configuration was specified.
19
+ # Configure the {.default_configuration} which is used if no dedicated
20
+ # configuration was specified to {.open} or {#connect}.
17
21
  #
18
22
  # @example
19
23
  # TCPClient.configure do |cfg|
@@ -33,11 +37,19 @@ class TCPClient
33
37
  end
34
38
 
35
39
  class Configuration
36
- #
37
- # @return [Configuration] used by default if no dedicated configuration was specified
38
- #
39
- def self.default
40
- TCPClient.default_configuration
40
+ class << self
41
+ #
42
+ # @!parse attr_reader :default
43
+ # @return [Configuration] used by default if no dedicated configuration
44
+ # was specified
45
+ #
46
+ # @see TCPClient.open
47
+ # @see TCPClient.with_deadline
48
+ # @see TCPClient#connect
49
+ #
50
+ def default
51
+ TCPClient.default_configuration
52
+ end
41
53
  end
42
54
  end
43
55
  end