tcp-client 1.0.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ed9a12e8872f2399ace7bef72451c8f6b2d7821e4de069e095eb1ca7a5fcb96
4
- data.tar.gz: 4b6b744fb885e99ea320cdfbfe21a9ff1aceaa12beedec2f2b068ceb3e932cd2
3
+ metadata.gz: a0318e4b31864c2b3f2b0af3e4d78f8d64b2d51b48eb2dd888d012583afd5f16
4
+ data.tar.gz: 5bf4683093179f1bf2a9c98ac0f208b401042b2303b8552bc98034dd138b5cb0
5
5
  SHA512:
6
- metadata.gz: c4e7417600e74d3709a20b20f5d13804da51d0ff85670ca8a180bd173ae7f07ea513976c2b73c3436a852b5c51a1f6c99a61f917c4be769cd73cb91f9b65684a
7
- data.tar.gz: 197b43ef8e44faec4a6bcd397f71505b3f8f4714a3775248eeaf2984acca4444bd5f8e290cc99be9408870f4a862f1c724cfadd55c114be12a4651027f7d7fd4
6
+ metadata.gz: d582c9da10d45d722a52621f205e471f0f280a5ad0bfe0b7dc3fbb2f019d3bbf9089fe9918fedf8661655c7b20c17d5586c869739f1323528c238c7f8b576f2e
7
+ data.tar.gz: 6d2507dc23e40951647c323ce6bf53be89952fc4d7177d1c4e17300625240937100968127c222b285af7ac117844717f2cd22696a5e0c70e068d9e66e98c507e
data/.yardopts CHANGED
@@ -2,4 +2,9 @@
2
2
  --title 'tcp-client Documentation'
3
3
  --charset utf-8
4
4
  --markup markdown
5
- 'lib/**/*.rb' - 'README.md' 'LICENSE'
5
+ --tag comment
6
+ --hide-tag comment
7
+ lib/**/*.rb
8
+ -
9
+ README.md
10
+ LICENSE
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2017-2021, Mike Blumtritt. All rights reserved.
3
+ Copyright (c) 2017-2025, Mike Blumtritt. All rights reserved.
4
4
 
5
5
  Redistribution and use in source and binary forms, with or without
6
6
  modification, are permitted provided that the following conditions are met:
@@ -26,3 +26,4 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
26
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
27
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
28
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
data/README.md CHANGED
@@ -54,7 +54,7 @@ gem install tcp-client
54
54
  You can use [Bundler](http://gembundler.com/) to add TCPClient to your own project:
55
55
 
56
56
  ```shell
57
- bundle add 'tcp-client'
57
+ bundle add tcp-client
58
58
  ```
59
59
 
60
60
  After that you only need one line of code to have everything together
@@ -100,10 +100,11 @@ class TCPClient
100
100
  # @return [Address] itself
101
101
  #
102
102
  def freeze
103
- return super if frozen?
104
- solve
105
- @addrinfo.freeze
106
- @host.freeze
103
+ unless frozen?
104
+ solve
105
+ @addrinfo.freeze
106
+ @host.freeze
107
+ end
107
108
  super
108
109
  end
109
110
 
@@ -43,7 +43,6 @@ class TCPClient
43
43
  #
44
44
  def initialize(options = nil)
45
45
  @buffered = @keep_alive = @reverse_lookup = true
46
- self.timeout = @ssl_params = nil
47
46
  @connect_timeout_error = ConnectTimeoutError
48
47
  @read_timeout_error = ReadTimeoutError
49
48
  @write_timeout_error = WriteTimeoutError
@@ -107,7 +106,7 @@ class TCPClient
107
106
  def ssl_params=(value)
108
107
  @ssl_params =
109
108
  if value.respond_to?(:to_hash)
110
- Hash[value.to_hash]
109
+ value.to_hash.dup
111
110
  elsif value.respond_to?(:to_h)
112
111
  value.nil? ? nil : value.to_h.dup
113
112
  else
@@ -336,7 +335,7 @@ class TCPClient
336
335
 
337
336
  private
338
337
 
339
- def as_seconds(value) = value&.to_f&.positive? ? value : nil
338
+ def as_seconds(value) = value.to_f.positive? ? value : nil
340
339
 
341
340
  def as_exception(value)
342
341
  return value if value.is_a?(Class) && value < Exception
@@ -2,15 +2,26 @@
2
2
 
3
3
  class TCPClient
4
4
  class Deadline
5
- def initialize(timeout)
6
- timeout = timeout&.to_f
7
- @deadline = timeout&.positive? ? now + timeout : nil
5
+ attr_accessor :exception
6
+
7
+ def initialize(timeout, exception)
8
+ @timeout = timeout.to_f
9
+ @exception = exception
10
+ @deadline = @timeout.positive? ? now + @timeout : nil
8
11
  end
9
12
 
10
13
  def valid? = !@deadline.nil?
11
14
 
12
15
  def remaining_time
13
- @deadline && (remaining = @deadline - now) > 0 ? remaining : nil
16
+ (remaining = @deadline - now) > 0 ? remaining : timed_out!(caller(1))
17
+ end
18
+
19
+ def timed_out!(call_stack = nil)
20
+ raise(
21
+ @exception,
22
+ "execution expired - #{@timeout} seconds",
23
+ call_stack || caller(1)
24
+ )
14
25
  end
15
26
 
16
27
  private
@@ -129,19 +129,4 @@ class TCPClient
129
129
  #
130
130
  def action = :write
131
131
  end
132
-
133
- NoOpenSSL = NoOpenSSLError # @!visibility private
134
- NoBlockGiven = NoBlockGivenError # @!visibility private
135
- InvalidDeadLine = InvalidDeadLineError # @!visibility private
136
- UnknownAttribute = UnknownAttributeError # @!visibility private
137
- NotAnException = NotAnExceptionError # @!visibility private
138
- NotConnected = NotConnectedError # @!visibility private
139
- deprecate_constant(
140
- :NoOpenSSL,
141
- :NoBlockGiven,
142
- :InvalidDeadLine,
143
- :UnknownAttribute,
144
- :NotAnException,
145
- :NotConnected
146
- )
147
132
  end
@@ -13,13 +13,13 @@ class TCPClient
13
13
  class SSLSocket < ::OpenSSL::SSL::SSLSocket
14
14
  include WithDeadline
15
15
 
16
- def initialize(socket, address, configuration, deadline, exception)
16
+ def initialize(socket, address, configuration, deadline)
17
17
  ssl_params = Hash[configuration.ssl_params]
18
18
  super(socket, create_context(ssl_params))
19
19
  self.sync_close = true
20
20
  self.hostname = address.hostname
21
21
  check_new_session if @new_session
22
- deadline.valid? ? connect_with_deadline(deadline, exception) : connect
22
+ deadline.valid? ? connect_with_deadline(deadline) : connect
23
23
  post_connection_check(address.hostname) if should_verify?(ssl_params)
24
24
  end
25
25
 
@@ -41,8 +41,8 @@ class TCPClient
41
41
  end
42
42
  end
43
43
 
44
- def connect_with_deadline(deadline, exception)
45
- with_deadline(deadline, exception) { connect_nonblock(exception: false) }
44
+ def connect_with_deadline(deadline)
45
+ with_deadline(deadline) { connect_nonblock(exception: false) }
46
46
  end
47
47
 
48
48
  def should_verify?(ssl_params)
@@ -53,6 +53,8 @@ class TCPClient
53
53
  CONTEXT_CACHE_MODE =
54
54
  ::OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
55
55
  ::OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
56
+
57
+ private_constant(:CONTEXT_CACHE_MODE)
56
58
  end
57
59
 
58
60
  private_constant(:SSLSocket)
@@ -8,24 +8,21 @@ class TCPClient
8
8
  class TCPSocket < ::Socket
9
9
  include WithDeadline
10
10
 
11
- def initialize(address, configuration, deadline, exception)
12
- super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
11
+ def initialize(address, configuration, deadline)
12
+ addrinfo = address.addrinfo
13
+ super(addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
13
14
  configure(configuration)
14
- connect_to(as_addr_in(address), deadline, exception)
15
+ connect_to(
16
+ ::Socket.pack_sockaddr_in(addrinfo.ip_port, addrinfo.ip_address),
17
+ deadline
18
+ )
15
19
  end
16
20
 
17
21
  private
18
22
 
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)
23
+ def connect_to(addr, deadline)
25
24
  return connect(addr) unless deadline.valid?
26
- with_deadline(deadline, exception) do
27
- connect_nonblock(addr, exception: false)
28
- end
25
+ with_deadline(deadline) { connect_nonblock(addr, exception: false) }
29
26
  end
30
27
 
31
28
  def configure(configuration)
@@ -2,5 +2,5 @@
2
2
 
3
3
  class TCPClient
4
4
  # The current version number.
5
- VERSION = '1.0.0'
5
+ VERSION = '1.0.2'
6
6
  end
@@ -2,22 +2,22 @@
2
2
 
3
3
  class TCPClient
4
4
  module WithDeadline
5
- def read_with_deadline(nbytes, deadline, exception)
6
- raise(exception) unless deadline.remaining_time
7
- return fetch_avail(deadline, exception) if nbytes.nil?
5
+ def read_with_deadline(nbytes, deadline)
6
+ deadline.remaining_time
7
+ return fetch_avail(deadline) if nbytes.nil?
8
8
  @read_buffer ||= ''.b
9
- while @read_buffer.bytesize < nbytes
10
- read = fetch_next(deadline, exception)
9
+ while (diff = nbytes - @read_buffer.bytesize) > 0
10
+ read = fetch_next(deadline, diff)
11
11
  read ? @read_buffer << read : (break close)
12
12
  end
13
13
  fetch_slice(nbytes)
14
14
  end
15
15
 
16
- def read_to_with_deadline(sep, deadline, exception)
17
- raise(exception) unless deadline.remaining_time
16
+ def read_to_with_deadline(sep, deadline)
17
+ deadline.remaining_time
18
18
  @read_buffer ||= ''.b
19
19
  while (index = @read_buffer.index(sep)).nil?
20
- read = fetch_next(deadline, exception)
20
+ read = fetch_next(deadline)
21
21
  read ? @read_buffer << read : (break close)
22
22
  end
23
23
  return fetch_slice(index + sep.bytesize) if index
@@ -26,15 +26,13 @@ class TCPClient
26
26
  result
27
27
  end
28
28
 
29
- def write_with_deadline(data, deadline, exception)
29
+ def write_with_deadline(data, deadline)
30
30
  return 0 if (size = data.bytesize).zero?
31
- raise(exception) unless deadline.remaining_time
31
+ deadline.remaining_time
32
32
  result = 0
33
33
  while true
34
34
  written =
35
- with_deadline(deadline, exception) do
36
- write_nonblock(data, exception: false)
37
- end
35
+ with_deadline(deadline) { write_nonblock(data, exception: false) }
38
36
  return result if (result += written) >= size
39
37
  data = data.byteslice(written, data.bytesize - written)
40
38
  end
@@ -42,8 +40,8 @@ class TCPClient
42
40
 
43
41
  private
44
42
 
45
- def fetch_avail(deadline, exception)
46
- if (result = @read_buffer || fetch_next(deadline, exception)).nil?
43
+ def fetch_avail(deadline)
44
+ if (result = @read_buffer || fetch_next(deadline)).nil?
47
45
  close
48
46
  return ''.b
49
47
  end
@@ -59,27 +57,23 @@ class TCPClient
59
57
  result
60
58
  end
61
59
 
62
- def fetch_next(deadline, exception)
63
- with_deadline(deadline, exception) do
64
- read_nonblock(65_536, exception: false)
65
- end
60
+ def fetch_next(deadline, size = 65_536)
61
+ with_deadline(deadline) { read_nonblock(size, exception: false) }
66
62
  end
67
63
 
68
- def with_deadline(deadline, exception)
64
+ def with_deadline(deadline)
69
65
  while true
70
66
  case ret = yield
71
67
  when :wait_writable
72
- remaining_time = deadline.remaining_time or raise(exception)
73
- wait_write[remaining_time] or raise(exception)
68
+ wait_write[deadline.remaining_time] or deadline.timed_out!
74
69
  when :wait_readable
75
- remaining_time = deadline.remaining_time or raise(exception)
76
- wait_read[remaining_time] or raise(exception)
70
+ wait_read[deadline.remaining_time] or deadline.timed_out!
77
71
  else
78
72
  return ret
79
73
  end
80
74
  end
81
75
  rescue Errno::ETIMEDOUT
82
- raise(exception)
76
+ deadline.timed_out!
83
77
  end
84
78
 
85
79
  def wait_write
data/lib/tcp-client.rb CHANGED
@@ -125,7 +125,7 @@ class TCPClient
125
125
  def close
126
126
  @socket&.close
127
127
  self
128
- rescue *NETWORK_ERRORS
128
+ rescue *@@net_errors
129
129
  self
130
130
  ensure
131
131
  @socket = @deadline = nil
@@ -192,11 +192,15 @@ class TCPClient
192
192
  #
193
193
  def read(nbytes = nil, timeout: nil, exception: nil)
194
194
  raise(NotConnectedError) if closed?
195
- deadline = create_deadline(timeout, configuration.read_timeout)
195
+ deadline =
196
+ create_deadline(
197
+ timeout,
198
+ @configuration.read_timeout,
199
+ exception || @configuration.read_timeout_error
200
+ )
196
201
  return stem_errors { @socket.read(nbytes) } unless deadline.valid?
197
- exception ||= configuration.read_timeout_error
198
- stem_errors(exception) do
199
- @socket.read_with_deadline(nbytes, deadline, exception)
202
+ stem_errors(deadline.exception) do
203
+ @socket.read_with_deadline(nbytes, deadline)
200
204
  end
201
205
  end
202
206
 
@@ -221,13 +225,17 @@ class TCPClient
221
225
  #
222
226
  def readline(separator = $/, chomp: false, timeout: nil, exception: nil)
223
227
  raise(NotConnectedError) if closed?
224
- deadline = create_deadline(timeout, configuration.read_timeout)
228
+ deadline =
229
+ create_deadline(
230
+ timeout,
231
+ @configuration.read_timeout,
232
+ exception || @configuration.read_timeout_error
233
+ )
225
234
  deadline.valid? or
226
235
  return stem_errors { @socket.readline(separator, chomp: chomp) }
227
- exception ||= configuration.read_timeout_error
228
236
  line =
229
- stem_errors(exception) do
230
- @socket.read_to_with_deadline(separator, deadline, exception)
237
+ stem_errors(deadline.exception) do
238
+ @socket.read_to_with_deadline(separator, deadline)
231
239
  end
232
240
  chomp ? line.chomp : line
233
241
  end
@@ -265,7 +273,7 @@ class TCPClient
265
273
  def with_deadline(timeout)
266
274
  previous_deadline = @deadline
267
275
  raise(NoBlockGivenError) unless block_given?
268
- @deadline = Deadline.new(timeout)
276
+ @deadline = Deadline.new(timeout, TimeoutError)
269
277
  raise(InvalidDeadLineError, timeout) unless @deadline.valid?
270
278
  yield(self)
271
279
  ensure
@@ -292,54 +300,62 @@ class TCPClient
292
300
  #
293
301
  def write(*messages, timeout: nil, exception: nil)
294
302
  raise(NotConnectedError) if closed?
295
- deadline = create_deadline(timeout, configuration.write_timeout)
303
+ deadline =
304
+ create_deadline(
305
+ timeout,
306
+ @configuration.write_timeout,
307
+ exception || @configuration.write_timeout_error
308
+ )
296
309
  return stem_errors { @socket.write(*messages) } unless deadline.valid?
297
- exception ||= configuration.write_timeout_error
298
- stem_errors(exception) do
299
- messages.sum do |chunk|
300
- @socket.write_with_deadline(chunk.b, deadline, exception)
301
- end
310
+ stem_errors(deadline.exception) do
311
+ messages.sum { |chunk| @socket.write_with_deadline(chunk.b, deadline) }
302
312
  end
303
313
  end
304
314
 
305
315
  private
306
316
 
307
- def create_deadline(timeout, default)
308
- timeout.nil? && @deadline ? @deadline : Deadline.new(timeout || default)
317
+ def create_deadline(timeout, timeout_default, exception)
318
+ if timeout.nil? && @deadline
319
+ @deadline.exception = exception
320
+ return @deadline
321
+ end
322
+ Deadline.new(timeout || timeout_default, exception)
309
323
  end
310
324
 
311
325
  def create_socket(timeout, exception)
312
- deadline = create_deadline(timeout, configuration.connect_timeout)
313
- exception ||= configuration.connect_timeout_error
314
- stem_errors(exception) do
315
- @socket = TCPSocket.new(address, configuration, deadline, exception)
316
- return @socket unless configuration.ssl?
317
- SSLSocket.new(@socket, address, configuration, deadline, exception)
326
+ deadline =
327
+ create_deadline(
328
+ timeout,
329
+ @configuration.connect_timeout,
330
+ exception || @configuration.connect_timeout_error
331
+ )
332
+ stem_errors(deadline.exception) do
333
+ @socket = TCPSocket.new(address, @configuration, deadline)
334
+ return @socket unless @configuration.ssl?
335
+ SSLSocket.new(@socket, address, @configuration, deadline)
318
336
  end
319
337
  end
320
338
 
321
339
  def stem_errors(except = nil)
322
340
  yield
323
- rescue *NETWORK_ERRORS => e
324
- raise unless configuration.normalize_network_errors
341
+ rescue *@@net_errors => e
342
+ raise unless @configuration.normalize_network_errors
325
343
  except && e.is_a?(except) ? raise : raise(NetworkError, e)
326
344
  end
327
345
 
328
- NETWORK_ERRORS =
329
- [
330
- Errno::EADDRNOTAVAIL,
331
- Errno::ECONNABORTED,
332
- Errno::ECONNREFUSED,
333
- Errno::ECONNRESET,
334
- Errno::EHOSTUNREACH,
335
- Errno::EINVAL,
336
- Errno::ENETUNREACH,
337
- Errno::EPIPE,
338
- IOError,
339
- SocketError
340
- ].tap do |errors|
341
- errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
342
- end
343
- .freeze
344
- private_constant(:NETWORK_ERRORS)
346
+ @@net_errors = [
347
+ Errno::EADDRNOTAVAIL,
348
+ Errno::ECONNABORTED,
349
+ Errno::ECONNREFUSED,
350
+ Errno::EHOSTUNREACH,
351
+ Errno::ENETUNREACH,
352
+ Errno::ECONNRESET,
353
+ Errno::EINVAL,
354
+ Errno::EPIPE,
355
+ Errno::EPERM,
356
+ SocketError,
357
+ IOError
358
+ ]
359
+ @@net_errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
360
+ @@net_errors.freeze
345
361
  end
metadata CHANGED
@@ -1,21 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-05-03 00:00:00.000000000 Z
10
+ date: 2025-03-20 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |
14
13
  This gem implements a customizable TCP client class that gives you control
15
14
  over time limits. You can set time limits for individual read or write calls
16
15
  or set a deadline for entire call sequences.
17
16
  It has a very small footprint, no dependencies and is easily useable.
18
- email:
19
17
  executables: []
20
18
  extensions: []
21
19
  extra_rdoc_files:
@@ -44,7 +42,6 @@ metadata:
44
42
  bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
45
43
  documentation_uri: https://rubydoc.info/gems/tcp-client
46
44
  rubygems_mfa_required: 'true'
47
- post_install_message:
48
45
  rdoc_options: []
49
46
  require_paths:
50
47
  - lib
@@ -59,8 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
56
  - !ruby/object:Gem::Version
60
57
  version: '0'
61
58
  requirements: []
62
- rubygems_version: 3.5.9
63
- signing_key:
59
+ rubygems_version: 3.6.6
64
60
  specification_version: 4
65
61
  summary: Use your TCP connections with working timeout.
66
62
  test_files: []