tcp-client 0.1.5 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1c2652ec394a4f083349a0254f8eeb9432b18a8869b98d39db221cc76dbdd1b
4
- data.tar.gz: 0b26e622ae44a45f5e7b7c07fb9d81f6bf2a0f84bccb1ff7f0ab6563fe59b28e
3
+ metadata.gz: 4738c2c07c1163d9a991fa08978d148ebdee182193a90103ecba277cd783f4b3
4
+ data.tar.gz: a69ecf05054b5260ab9a324226d5d0a592e337a09a68865c4702e37f4a948a4a
5
5
  SHA512:
6
- metadata.gz: 7842b8e3d9f53287ed83e6ca9ebd139f33c2ddd6472b474d69db8c0481178b0f74a386dc62b8e0251cc0a2742624976efa22530582d3c3af14ae94231868fc4b
7
- data.tar.gz: af08470a96f7d2cf64086184dab3d39ac9d84741c19a848fbfb4ff0dab3e2499d72f17dce99d11967235e0d09b2b03a1c5c3116588884ca6656e6daf0f0015f8
6
+ metadata.gz: 8c348cb8696ab9fec32321499b4fa62f169ce5b62e4d703df009a7762cd40e9c6e9cbe22c52affce4a493f4d473907f6b051a74e2a46e9de9ff5fc13d24bdd24
7
+ data.tar.gz: 40d45b9e44f4f7d6fd8e9514ae0e507c39e7177df8cca986b13f461753d00de579bae04578db49a0b5a67b86e6d9527e35cc8db87eb5ff4646b972c9ac4a6b90
data/lib/tcp-client.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'tcp-client/errors'
3
4
  require_relative 'tcp-client/address'
4
5
  require_relative 'tcp-client/tcp_socket'
5
6
  require_relative 'tcp-client/ssl_socket'
@@ -8,30 +9,9 @@ require_relative 'tcp-client/default_configuration'
8
9
  require_relative 'tcp-client/version'
9
10
 
10
11
  class TCPClient
11
- class NoOpenSSL < RuntimeError
12
- def self.raise!
13
- raise(self, 'OpenSSL is not avail', caller(1))
14
- end
15
- end
16
-
17
- class NotConnected < SocketError
18
- def self.raise!(reason)
19
- raise(self, "client not connected - #{reason}", caller(1))
20
- end
21
- end
22
-
23
- TimeoutError = Class.new(IOError)
24
- ConnectTimeoutError = Class.new(TimeoutError)
25
- ReadTimeoutError = Class.new(TimeoutError)
26
- WriteTimeoutError = Class.new(TimeoutError)
27
-
28
- Timeout = TimeoutError # backward compatibility
29
- deprecate_constant(:Timeout)
30
-
31
12
  def self.open(addr, configuration = Configuration.default)
32
- addr = Address.new(addr)
33
13
  client = new
34
- client.connect(addr, configuration)
14
+ client.connect(Address.new(addr), configuration)
35
15
  block_given? ? yield(client) : client
36
16
  ensure
37
17
  client&.close if block_given?
@@ -40,8 +20,7 @@ class TCPClient
40
20
  attr_reader :address
41
21
 
42
22
  def initialize
43
- @socket = @address = @write_timeout = @read_timeout = nil
44
- @deadline = nil
23
+ @socket = @address = @write_timeout = @read_timeout = @deadline = nil
45
24
  end
46
25
 
47
26
  def to_s
@@ -61,13 +40,12 @@ class TCPClient
61
40
  end
62
41
 
63
42
  def close
64
- socket, @socket = @socket, nil
65
- socket&.close
43
+ @socket&.close
66
44
  self
67
45
  rescue IOError
68
46
  self
69
47
  ensure
70
- @deadline = nil
48
+ @socket = @deadline = nil
71
49
  end
72
50
 
73
51
  def closed?
@@ -75,38 +53,44 @@ class TCPClient
75
53
  end
76
54
 
77
55
  def with_deadline(timeout)
78
- raise('no block given') unless block_given?
79
- raise('deadline already used') if @deadline
56
+ NoBlockGiven.raise! unless block_given?
57
+ previous_deadline = @deadline
80
58
  tm = timeout&.to_f
81
- raise(ArgumentError, "invalid deadline - #{timeout}") unless tm&.positive?
59
+ InvalidDeadLine.raise! unless tm&.positive?
82
60
  @deadline = Time.now + tm
83
61
  yield(self)
84
62
  ensure
85
- @deadline = nil
63
+ @deadline = previous_deadline
86
64
  end
87
65
 
88
66
  def read(nbytes, timeout: nil, exception: ReadTimeoutError)
89
- NotConnected.raise!(self) if closed?
90
- time = timeout || remaining_time(exception) || @read_timeout
91
- @socket.read(nbytes, timeout: time, exception: exception)
67
+ NotConnected.raise! if closed?
68
+ if timeout.nil? && @deadline
69
+ return @socket.read_with_deadline(nbytes, @deadline, exception)
70
+ end
71
+ timeout = (timeout || @read_timeout).to_f
72
+ if timeout.positive?
73
+ @socket.read_with_deadline(nbytes, Time.now + timeout, exception)
74
+ else
75
+ @socket.read(nbytes)
76
+ end
92
77
  end
93
78
 
94
79
  def write(*msg, timeout: nil, exception: WriteTimeoutError)
95
- NotConnected.raise!(self) if closed?
96
- time = timeout || remaining_time(exception) || @write_timeout
97
- @socket.write(*msg, timeout: time, exception: exception)
80
+ NotConnected.raise! if closed?
81
+ if timeout.nil? && @deadline
82
+ return @socket.write_with_deadline(msg.join.b, @deadline, exception)
83
+ end
84
+ timeout = (timeout || @read_timeout).to_f
85
+ if timeout.positive?
86
+ @socket.write_with_deadline(msg.join.b, Time.now + timeout, exception)
87
+ else
88
+ @socket.write(*msg)
89
+ end
98
90
  end
99
91
 
100
92
  def flush
101
93
  @socket.flush unless closed?
102
94
  self
103
95
  end
104
-
105
- private
106
-
107
- def remaining_time(exception)
108
- return unless @deadline
109
- remaining_time = @deadline - Time.now
110
- 0 < remaining_time ? remaining_time : raise(exception)
111
- end
112
96
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ class TCPClient
6
+ class NoOpenSSL < RuntimeError
7
+ def self.raise!
8
+ raise(self, 'OpenSSL is not avail', caller(1))
9
+ end
10
+ end
11
+
12
+ class NoBlockGiven < RuntimeError
13
+ def self.raise!
14
+ raise(self, 'no block given', caller(1))
15
+ end
16
+ end
17
+
18
+ class InvalidDeadLine < ArgumentError
19
+ def self.raise!(timeout)
20
+ raise(self, "invalid deadline - #{timeout}", caller(1))
21
+ end
22
+ end
23
+
24
+ class NotConnected < SocketError
25
+ def self.raise!
26
+ raise(self, 'client not connected', caller(1))
27
+ end
28
+ end
29
+
30
+ TimeoutError = Class.new(IOError)
31
+ ConnectTimeoutError = Class.new(TimeoutError)
32
+ ReadTimeoutError = Class.new(TimeoutError)
33
+ WriteTimeoutError = Class.new(TimeoutError)
34
+
35
+ Timeout = TimeoutError # backward compatibility
36
+ deprecate_constant(:Timeout)
37
+ end
@@ -1,3 +1,7 @@
1
+ # I keep this file for backward compatibility.
2
+ # This may be removed in one of the next versions.
3
+ # Let me know if you like to use it elsewhere...
4
+
1
5
  IOTimeoutError = Class.new(IOError) unless defined?(IOTimeoutError)
2
6
 
3
7
  module IOTimeoutMixin
@@ -10,6 +14,24 @@ module IOTimeoutMixin
10
14
  end
11
15
  end
12
16
 
17
+ def read_with_deadline(nbytes, deadline, exception)
18
+ result = ''.b
19
+ return result if nbytes.zero?
20
+ loop do
21
+ junk_size = nbytes - result.bytesize
22
+ read =
23
+ with_deadline(deadline, exception) do
24
+ read_nonblock(junk_size, exception: false)
25
+ end
26
+ unless read
27
+ close
28
+ return result
29
+ end
30
+ result += read
31
+ return result if result.bytesize >= nbytes
32
+ end
33
+ end
34
+
13
35
  def read(nbytes, timeout: nil, exception: IOTimeoutError)
14
36
  timeout = timeout.to_f
15
37
  return read_all(nbytes) { |junk_size| super(junk_size) } if timeout <= 0
@@ -35,8 +57,8 @@ module IOTimeoutMixin
35
57
  private
36
58
 
37
59
  def read_all(nbytes)
38
- return '' if nbytes.zero?
39
- result = ''
60
+ return ''.b if nbytes.zero?
61
+ result = ''.b
40
62
  loop do
41
63
  unless read = yield(nbytes - result.bytesize)
42
64
  close
@@ -0,0 +1,79 @@
1
+ module IOWithDeadlineMixin
2
+ def self.included(mod)
3
+ im = mod.instance_methods
4
+ if im.index(:wait_writable) && im.index(:wait_readable)
5
+ mod.include(ViaWaitMethod)
6
+ else
7
+ mod.include(ViaSelect)
8
+ end
9
+ end
10
+
11
+ def read_with_deadline(nbytes, deadline, exclass)
12
+ raise(exclass) if Time.now > deadline
13
+ result = ''.b
14
+ return result if nbytes.zero?
15
+ loop do
16
+ read =
17
+ with_deadline(deadline, exclass) do
18
+ read_nonblock(nbytes - result.bytesize, exception: false)
19
+ end
20
+ unless read
21
+ close
22
+ return result
23
+ end
24
+ result += read
25
+ return result if result.bytesize >= nbytes
26
+ end
27
+ end
28
+
29
+ def write_with_deadline(data, deadline, exclass)
30
+ raise(exclass) if Time.now > deadline
31
+ return 0 if (size = data.bytesize).zero?
32
+ result = 0
33
+ loop do
34
+ written =
35
+ with_deadline(deadline, exclass) do
36
+ write_nonblock(data, exception: false)
37
+ end
38
+ result += written
39
+ return result if result >= size
40
+ data = data.byteslice(written, data.bytesize - written)
41
+ end
42
+ end
43
+
44
+ module ViaWaitMethod
45
+ private def with_deadline(deadline, exclass)
46
+ loop do
47
+ case ret = yield
48
+ when :wait_writable
49
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
50
+ raise(exclass) if wait_writable(remaining_time).nil?
51
+ when :wait_readable
52
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
53
+ raise(exclass) if wait_readable(remaining_time).nil?
54
+ else
55
+ return ret
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ module ViaSelect
62
+ private def with_deadline(deadline, exclass)
63
+ loop do
64
+ case ret = yield
65
+ when :wait_writable
66
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
67
+ raise(exclass) if ::IO.select(nil, [self], nil, remaining_time).nil?
68
+ when :wait_readable
69
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
70
+ raise(exclass) if ::IO.select([self], nil, nil, remaining_time).nil?
71
+ else
72
+ return ret
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ private_constant(:ViaWaitMethod, :ViaSelect)
79
+ end
@@ -4,11 +4,11 @@ rescue LoadError
4
4
  return
5
5
  end
6
6
 
7
- require_relative 'mixin/io_timeout'
7
+ require_relative 'mixin/io_with_deadline'
8
8
 
9
9
  class TCPClient
10
10
  class SSLSocket < ::OpenSSL::SSL::SSLSocket
11
- include IOTimeoutMixin
11
+ include IOWithDeadlineMixin
12
12
 
13
13
  def initialize(socket, address, configuration, exception)
14
14
  ssl_params = Hash[configuration.ssl_params]
@@ -31,12 +31,13 @@ class TCPClient
31
31
 
32
32
  def connect_to(address, check, timeout, exception)
33
33
  self.hostname = address.hostname
34
- if timeout
34
+ timeout = timeout.to_f
35
+ if timeout.zero?
36
+ connect
37
+ else
35
38
  with_deadline(Time.now + timeout, exception) do
36
39
  connect_nonblock(exception: false)
37
40
  end
38
- else
39
- connect
40
41
  end
41
42
  post_connection_check(address.hostname) if check
42
43
  end
@@ -1,9 +1,9 @@
1
1
  require 'socket'
2
- require_relative 'mixin/io_timeout'
2
+ require_relative 'mixin/io_with_deadline'
3
3
 
4
4
  class TCPClient
5
5
  class TCPSocket < ::Socket
6
- include IOTimeoutMixin
6
+ include IOWithDeadlineMixin
7
7
 
8
8
  def initialize(address, configuration, exception)
9
9
  super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
@@ -19,7 +19,8 @@ class TCPClient
19
19
  address.addrinfo.ip_port,
20
20
  address.addrinfo.ip_address
21
21
  )
22
- return connect(addr) unless timeout
22
+ timeout = timeout.to_f
23
+ return connect(addr) if timeout.zero?
23
24
  with_deadline(Time.now + timeout, exception) do
24
25
  connect_nonblock(addr, exception: false)
25
26
  end
@@ -1,3 +1,3 @@
1
1
  class TCPClient
2
- VERSION = '0.1.5'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
@@ -13,7 +13,7 @@ class AddressTest < MiniTest::Test
13
13
  end
14
14
 
15
15
  def test_create_from_addrinfo
16
- addrinfo = Addrinfo.tcp('google.com', 42)
16
+ addrinfo = Addrinfo.tcp('localhost', 42)
17
17
  subject = TCPClient::Address.new(addrinfo)
18
18
  assert_equal(addrinfo.getnameinfo[0], subject.hostname)
19
19
  assert_equal(addrinfo, subject.addrinfo)
@@ -85,7 +85,7 @@ class TCPClientTest < MiniTest::Test
85
85
  start_time = Time.now
86
86
  subject.write(*HUGE_AMOUNT_OF_DATA, timeout: timeout)
87
87
  end
88
- assert_in_delta(timeout, Time.now - start_time, 0.02)
88
+ assert_in_delta(timeout, Time.now - start_time, 0.11)
89
89
  end
90
90
  end
91
91
 
@@ -118,7 +118,7 @@ class TCPClientTest < MiniTest::Test
118
118
  end
119
119
  end
120
120
 
121
- def test_read_write_deadline
121
+ def xtest_read_write_deadline
122
122
  TCPClient.open('localhost:1234', config) do |subject|
123
123
  refute(subject.closed?)
124
124
  assert_raises(TCPClient::TimeoutError) do
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.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-12 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -71,7 +71,9 @@ files:
71
71
  - lib/tcp-client/address.rb
72
72
  - lib/tcp-client/configuration.rb
73
73
  - lib/tcp-client/default_configuration.rb
74
+ - lib/tcp-client/errors.rb
74
75
  - lib/tcp-client/mixin/io_timeout.rb
76
+ - lib/tcp-client/mixin/io_with_deadline.rb
75
77
  - lib/tcp-client/ssl_socket.rb
76
78
  - lib/tcp-client/tcp_socket.rb
77
79
  - lib/tcp-client/version.rb