tcp-client 0.14.0 → 1.0.1

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: b9cfd60cce36d13e734e17197f76d5d887eae87f9b4ec08ef00a43d6ea5d8d5a
4
- data.tar.gz: ff17c9f4cb54ae7a6d00d888c34e45077b17c8225e26d1695854587ecb01f3dc
3
+ metadata.gz: f087df088e9bdfc71ffd19b01d3f9f7a229f4db510a339a08ea7d02c07a32eda
4
+ data.tar.gz: 4b4d332dcdeb98a2bc81f3e7963973da21336e5b2d89199bfc9b0d2c92c9b122
5
5
  SHA512:
6
- metadata.gz: d2c03a28cc57eba3b3468ed911ac24bb7a24279bbe80d6ddea6fca8a65848b5079c409d968599348476e041e04ff891196a90014ce5cee19e5204fdfad78ed15
7
- data.tar.gz: 68551bdbc6e978368cf615c2d68d078ceb20b8857fdd6451efba921efdf37bc25732e366d246126bb42006bffa50fa60540f30b3390f635080c99116b296f575
6
+ metadata.gz: f0c31448b596bc77288424a2863528714db40576f7201f2927172d0c39cac4086fe976a315347d6da78a96ca022a33e31956533caa02c025b18b7b3e3129d97d
7
+ data.tar.gz: 69dd0aa063a8019c07976b6942f0d08df0f31036eb903040e63cfdc40844c0275396a879a2e7f27332ad9843b2a2ba4326fbccabee3d12e1a3b31a587cb5ba9a
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/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
@@ -336,7 +336,7 @@ class TCPClient
336
336
 
337
337
  private
338
338
 
339
- def as_seconds(value) = value&.to_f&.positive? ? value : nil
339
+ def as_seconds(value) = value.to_f.positive? ? value : nil
340
340
 
341
341
  def as_exception(value)
342
342
  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
@@ -7,19 +7,19 @@ rescue LoadError
7
7
  end
8
8
 
9
9
  require_relative 'deadline'
10
- require_relative 'mixin/io_with_deadline'
10
+ require_relative 'with_deadline'
11
11
 
12
12
  class TCPClient
13
13
  class SSLSocket < ::OpenSSL::SSL::SSLSocket
14
- include IOWithDeadlineMixin
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)
@@ -2,16 +2,16 @@
2
2
 
3
3
  require 'socket'
4
4
  require_relative 'deadline'
5
- require_relative 'mixin/io_with_deadline'
5
+ require_relative 'with_deadline'
6
6
 
7
7
  class TCPClient
8
8
  class TCPSocket < ::Socket
9
- include IOWithDeadlineMixin
9
+ include WithDeadline
10
10
 
11
- def initialize(address, configuration, deadline, exception)
11
+ def initialize(address, configuration, deadline)
12
12
  super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
13
13
  configure(configuration)
14
- connect_to(as_addr_in(address), deadline, exception)
14
+ connect_to(as_addr_in(address), deadline)
15
15
  end
16
16
 
17
17
  private
@@ -21,11 +21,9 @@ class TCPClient
21
21
  ::Socket.pack_sockaddr_in(addrinfo.ip_port, addrinfo.ip_address)
22
22
  end
23
23
 
24
- def connect_to(addr, deadline, exception)
24
+ def connect_to(addr, deadline)
25
25
  return connect(addr) unless deadline.valid?
26
- with_deadline(deadline, exception) do
27
- connect_nonblock(addr, exception: false)
28
- end
26
+ with_deadline(deadline) { connect_nonblock(addr, exception: false) }
29
27
  end
30
28
 
31
29
  def configure(configuration)
@@ -2,5 +2,5 @@
2
2
 
3
3
  class TCPClient
4
4
  # The current version number.
5
- VERSION = '0.14.0'
5
+ VERSION = '1.0.1'
6
6
  end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TCPClient
4
+ module WithDeadline
5
+ def read_with_deadline(nbytes, deadline)
6
+ deadline.remaining_time
7
+ return fetch_avail(deadline) if nbytes.nil?
8
+ @read_buffer ||= ''.b
9
+ while @read_buffer.bytesize < nbytes
10
+ read = fetch_next(deadline)
11
+ read ? @read_buffer << read : (break close)
12
+ end
13
+ fetch_slice(nbytes)
14
+ end
15
+
16
+ def read_to_with_deadline(sep, deadline)
17
+ deadline.remaining_time
18
+ @read_buffer ||= ''.b
19
+ while (index = @read_buffer.index(sep)).nil?
20
+ read = fetch_next(deadline)
21
+ read ? @read_buffer << read : (break close)
22
+ end
23
+ return fetch_slice(index + sep.bytesize) if index
24
+ result = @read_buffer
25
+ @read_buffer = nil
26
+ result
27
+ end
28
+
29
+ def write_with_deadline(data, deadline)
30
+ return 0 if (size = data.bytesize).zero?
31
+ deadline.remaining_time
32
+ result = 0
33
+ while true
34
+ written =
35
+ with_deadline(deadline) { write_nonblock(data, exception: false) }
36
+ return result if (result += written) >= size
37
+ data = data.byteslice(written, data.bytesize - written)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def fetch_avail(deadline)
44
+ if (result = @read_buffer || fetch_next(deadline)).nil?
45
+ close
46
+ return ''.b
47
+ end
48
+ @read_buffer = nil
49
+ result
50
+ end
51
+
52
+ def fetch_slice(size)
53
+ return ''.b if size <= 0
54
+ result = @read_buffer.byteslice(0, size)
55
+ rest = @read_buffer.bytesize - result.bytesize
56
+ @read_buffer = rest.zero? ? nil : @read_buffer.byteslice(size, rest)
57
+ result
58
+ end
59
+
60
+ def fetch_next(deadline)
61
+ with_deadline(deadline) { read_nonblock(65_536, exception: false) }
62
+ end
63
+
64
+ def with_deadline(deadline)
65
+ while true
66
+ case ret = yield
67
+ when :wait_writable
68
+ wait_write[deadline.remaining_time] or deadline.timed_out!
69
+ when :wait_readable
70
+ wait_read[deadline.remaining_time] or deadline.timed_out!
71
+ else
72
+ return ret
73
+ end
74
+ end
75
+ rescue Errno::ETIMEDOUT
76
+ deadline.timed_out!
77
+ end
78
+
79
+ def wait_write
80
+ @wait_write ||=
81
+ if defined?(wait_writable)
82
+ ->(t) { wait_writable(t) }
83
+ elsif defined?(to_io)
84
+ ->(t) { to_io.wait_writable(t) }
85
+ else
86
+ ->(t) { ::IO.select(nil, [self], nil, t) }
87
+ end
88
+ end
89
+
90
+ def wait_read
91
+ @wait_read ||=
92
+ if defined?(wait_readable)
93
+ ->(t) { wait_readable(t) }
94
+ elsif defined?(to_io)
95
+ ->(t) { to_io.wait_readable(t) }
96
+ else
97
+ ->(t) { ::IO.select([self], nil, nil, t) }
98
+ end
99
+ end
100
+ end
101
+
102
+ private_constant(:WithDeadline)
103
+ end
data/lib/tcp-client.rb CHANGED
@@ -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,36 +300,46 @@ 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
341
  rescue *NETWORK_ERRORS => e
324
- raise unless configuration.normalize_network_errors
342
+ raise unless @configuration.normalize_network_errors
325
343
  except && e.is_a?(except) ? raise : raise(NetworkError, e)
326
344
  end
327
345
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-09 00:00:00.000000000 Z
11
+ date: 2024-05-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  This gem implements a customizable TCP client class that gives you control
@@ -31,10 +31,10 @@ files:
31
31
  - lib/tcp-client/deadline.rb
32
32
  - lib/tcp-client/default_configuration.rb
33
33
  - lib/tcp-client/errors.rb
34
- - lib/tcp-client/mixin/io_with_deadline.rb
35
34
  - lib/tcp-client/ssl_socket.rb
36
35
  - lib/tcp-client/tcp_socket.rb
37
36
  - lib/tcp-client/version.rb
37
+ - lib/tcp-client/with_deadline.rb
38
38
  - lib/tcp_client.rb
39
39
  homepage: https://github.com/mblumtritt/tcp-client
40
40
  licenses:
@@ -59,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
61
  requirements: []
62
- rubygems_version: 3.5.6
62
+ rubygems_version: 3.5.10
63
63
  signing_key:
64
64
  specification_version: 4
65
65
  summary: Use your TCP connections with working timeout.
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class TCPClient
4
- module IOWithDeadlineMixin
5
- class << self
6
- private
7
-
8
- def included(mod)
9
- return if defined?(mod.wait_writable) && defined?(mod.wait_readable)
10
- mod.include(defined?(mod.to_io) ? WaitWithIO : WaitWithSelect)
11
- end
12
- end
13
-
14
- def read_with_deadline(nbytes, deadline, exception)
15
- raise(exception) unless deadline.remaining_time
16
- return fetch_avail(deadline, exception) if nbytes.nil?
17
- @read_buffer ||= ''.b
18
- while @read_buffer.bytesize < nbytes
19
- read = fetch_next(deadline, exception)
20
- read ? @read_buffer << read : (break close)
21
- end
22
- fetch_slice(nbytes)
23
- end
24
-
25
- def read_to_with_deadline(sep, deadline, exception)
26
- raise(exception) unless deadline.remaining_time
27
- @read_buffer ||= ''.b
28
- while (index = @read_buffer.index(sep)).nil?
29
- read = fetch_next(deadline, exception)
30
- read ? @read_buffer << read : (break close)
31
- end
32
- return fetch_slice(index + sep.bytesize) if index
33
- result = @read_buffer
34
- @read_buffer = nil
35
- result
36
- end
37
-
38
- def write_with_deadline(data, deadline, exception)
39
- return 0 if (size = data.bytesize).zero?
40
- raise(exception) unless deadline.remaining_time
41
- result = 0
42
- while true
43
- written =
44
- with_deadline(deadline, exception) do
45
- write_nonblock(data, exception: false)
46
- end
47
- return result if (result += written) >= size
48
- data = data.byteslice(written, data.bytesize - written)
49
- end
50
- end
51
-
52
- private
53
-
54
- def fetch_avail(deadline, exception)
55
- if (result = @read_buffer || fetch_next(deadline, exception)).nil?
56
- close
57
- return ''.b
58
- end
59
- @read_buffer = nil
60
- result
61
- end
62
-
63
- def fetch_slice(size)
64
- return ''.b if size <= 0
65
- result = @read_buffer.byteslice(0, size)
66
- rest = @read_buffer.bytesize - result.bytesize
67
- @read_buffer = rest.zero? ? nil : @read_buffer.byteslice(size, rest)
68
- result
69
- end
70
-
71
- def fetch_next(deadline, exception)
72
- with_deadline(deadline, exception) do
73
- read_nonblock(65_536, exception: false)
74
- end
75
- end
76
-
77
- def with_deadline(deadline, exception)
78
- while true
79
- case ret = yield
80
- when :wait_writable
81
- remaining_time = deadline.remaining_time or raise(exception)
82
- wait_writable(remaining_time) or raise(exception)
83
- when :wait_readable
84
- remaining_time = deadline.remaining_time or raise(exception)
85
- wait_readable(remaining_time) or raise(exception)
86
- else
87
- return ret
88
- end
89
- end
90
- rescue Errno::ETIMEDOUT
91
- raise(exception)
92
- end
93
-
94
- module WaitWithIO
95
- def wait_writable(time) = to_io.wait_writable(time)
96
- def wait_readable(time) = to_io.wait_readable(time)
97
- end
98
-
99
- module WaitWithSelect
100
- def wait_writable(time) = ::IO.select(nil, [self], nil, time)
101
- def wait_readable(time) = ::IO.select([self], nil, nil, time)
102
- end
103
-
104
- private_constant(:WaitWithIO, :WaitWithSelect)
105
- end
106
-
107
- private_constant(:IOWithDeadlineMixin)
108
- end