tcp-client 0.14.0 → 1.0.1

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: 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