tcp-client 0.3.2 → 0.4.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: 4b265053761a87007b7de21c3a7a369b778f013ae726bb26220a4fd9aeeaf71c
4
- data.tar.gz: 5a71b5dc86ed4ab9ef481e65c24aef0481c5e9d2c868f3f9975b801936d99691
3
+ metadata.gz: 8d280b6685729b5f2573ec4bfb2f8d57b154275b381ef61084138e03bc5c2c2e
4
+ data.tar.gz: 5bbd6d4d3b1c94fa87e44d9ae03d80e38f444e0776680eb0eb42aed60d915fd6
5
5
  SHA512:
6
- metadata.gz: d16d436959c3e1c29962a5c474de8db3b4638e2d99aaf66fe4046b22e8159298cc433e014b14d9c9b3e01cafff1052743cf90896c396d86e1ccfc87d689553da
7
- data.tar.gz: 70f4ce83fd7c7c8aa4c066fb94e96ef62a5087c3f78e079cd473e8d45ab22e106eea7b3d7bf5965063fecb9b77c58b8548598f4a873d9480c5b501bc7854dea9
6
+ metadata.gz: 8b7b41def4591e42fc6db859a7c5f485fc5416e266728526d59313141cfc4ccd828f7adff563a425647a61fac5bc751a1a01193bc98bef589c9b9393737c92c4
7
+ data.tar.gz: 8d6d5e239dc2ba17a1015a565de2603be0f0421ee0e10fb24a94de86e5cb150d59842fd053cb27a3f58ca58fe43728f321cdfe02bba39a2c723a66ffb2013d0e
data/.gitignore CHANGED
@@ -1,4 +1,4 @@
1
- .local/
1
+ local/
2
2
  tmp/
3
3
  pkg/
4
4
  gems.locked
data/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  A TCP client implementation with working timeout support.
4
4
 
5
5
  ## Description
6
- This gem implements a TCP client with (optional) SSL support. The motivation of this project is the need to have a _really working_ easy to use client which can handle time limits correctly. Unlike other implementations this client respects given/configurable time limits for each method (`connect`, `read`, `write`).
6
+
7
+ 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.
7
8
 
8
9
  ## Sample
9
10
 
@@ -11,15 +12,15 @@ This gem implements a TCP client with (optional) SSL support. The motivation of
11
12
  require 'tcp-client'
12
13
 
13
14
  TCPClient.configure do |cfg|
14
- cfg.connect_timeout = 1 # second to connect the server
15
+ cfg.connect_timeout = 1 # limit connect time the server to 1 second
15
16
  cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
16
17
  end
17
18
 
18
19
  TCPClient.open('www.google.com:443') do |client|
19
- # query should not last longer than 0.5 seconds
20
+ # next sequence should not last longer than 0.5 seconds
20
21
  client.with_deadline(0.5) do
21
22
  # simple HTTP get request
22
- pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
23
+ pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
23
24
 
24
25
  # read "HTTP/1.1 " + 3 byte HTTP status code
25
26
  pp client.read(12)
data/lib/tcp-client.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'tcp-client/errors'
4
4
  require_relative 'tcp-client/address'
5
+ require_relative 'tcp-client/deadline'
5
6
  require_relative 'tcp-client/tcp_socket'
6
7
  require_relative 'tcp-client/ssl_socket'
7
8
  require_relative 'tcp-client/configuration'
@@ -55,9 +56,8 @@ class TCPClient
55
56
  def with_deadline(timeout)
56
57
  previous_deadline = @deadline
57
58
  raise(NoBlockGiven) unless block_given?
58
- tm = timeout&.to_f
59
- raise(InvalidDeadLine) unless tm&.positive?
60
- @deadline = Time.now + tm
59
+ @deadline = Deadline.new(timeout)
60
+ raise(InvalidDeadLine, timeout) unless @deadline.valid?
61
61
  yield(self)
62
62
  ensure
63
63
  @deadline = previous_deadline
@@ -67,18 +67,18 @@ class TCPClient
67
67
  raise(NotConnected) if closed?
68
68
  timeout.nil? && @deadline and
69
69
  return read_with_deadline(nbytes, @deadline, exception)
70
- timeout = (timeout || @cfg.read_timeout).to_f
71
- return @socket.read(nbytes) unless timeout.positive?
72
- read_with_deadline(nbytes, Time.now + timeout, exception)
70
+ deadline = Deadline.new(timeout || @cfg.read_timeout)
71
+ return @socket.read(nbytes) unless deadline.valid?
72
+ read_with_deadline(nbytes, deadline, exception)
73
73
  end
74
74
 
75
75
  def write(*msg, timeout: nil, exception: nil)
76
76
  raise(NotConnected) if closed?
77
77
  timeout.nil? && @deadline and
78
78
  return write_with_deadline(msg, @deadline, exception)
79
- timeout = (timeout || @cfg.write_timeout).to_f
80
- return @socket.write(*msg) unless timeout.positive?
81
- write_with_deadline(msg, Time.now + timeout, exception)
79
+ deadline = Deadline.new(timeout || @cfg.read_timeout)
80
+ return @socket.write(*msg) unless deadline.valid?
81
+ write_with_deadline(msg, deadline, exception)
82
82
  end
83
83
 
84
84
  def flush
@@ -58,10 +58,7 @@ class TCPClient
58
58
 
59
59
  def from_string(str)
60
60
  idx = str.rindex(':') or return nil, str.to_i
61
- name = str[0, idx]
62
- if name.start_with?('[') && name.end_with?(']')
63
- name = name[1, name.size - 2]
64
- end
61
+ name = str[0, idx].delete_prefix('[').delete_suffix(']')
65
62
  [name, str[idx + 1, str.size - idx].to_i]
66
63
  end
67
64
  end
@@ -13,7 +13,6 @@ class TCPClient
13
13
  attr_reader :buffered,
14
14
  :keep_alive,
15
15
  :reverse_lookup,
16
- :timeout,
17
16
  :connect_timeout,
18
17
  :read_timeout,
19
18
  :write_timeout,
@@ -47,9 +46,12 @@ class TCPClient
47
46
  end
48
47
 
49
48
  def ssl=(value)
50
- return @ssl_params = nil unless value
51
- return @ssl_params = value.dup if Hash === value
52
- @ssl_params ||= {}
49
+ @ssl_params =
50
+ if Hash === value
51
+ value.dup
52
+ else
53
+ value ? {} : nil
54
+ end
53
55
  end
54
56
 
55
57
  def buffered=(value)
@@ -65,8 +67,7 @@ class TCPClient
65
67
  end
66
68
 
67
69
  def timeout=(seconds)
68
- @timeout =
69
- @connect_timeout = @write_timeout = @read_timeout = seconds(seconds)
70
+ @connect_timeout = @write_timeout = @read_timeout = seconds(seconds)
70
71
  end
71
72
 
72
73
  def connect_timeout=(seconds)
@@ -107,7 +108,6 @@ class TCPClient
107
108
  buffered: @buffered,
108
109
  keep_alive: @keep_alive,
109
110
  reverse_lookup: @reverse_lookup,
110
- timeout: @timeout,
111
111
  connect_timeout: @connect_timeout,
112
112
  read_timeout: @read_timeout,
113
113
  write_timeout: @write_timeout,
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TCPClient
4
+ class Deadline
5
+ MONOTONIC = !!defined?(Process::CLOCK_MONOTONIC)
6
+
7
+ def initialize(timeout)
8
+ timeout = timeout&.to_f
9
+ @deadline = timeout&.positive? ? now + timeout : 0
10
+ end
11
+
12
+ def valid?
13
+ @deadline != 0
14
+ end
15
+
16
+ def remaining_time
17
+ (@deadline != 0) && (remaining = @deadline - now) > 0 ? remaining : nil
18
+ end
19
+
20
+ private
21
+
22
+ if MONOTONIC
23
+ def now
24
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
25
+ end
26
+ else
27
+ def now
28
+ ::Time.now
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'configuration'
2
4
 
3
5
  class TCPClient
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module IOWithDeadlineMixin
2
4
  def self.included(mod)
3
5
  methods = mod.instance_methods
@@ -9,25 +11,22 @@ module IOWithDeadlineMixin
9
11
  end
10
12
 
11
13
  def read_with_deadline(bytes_to_read, deadline, exception)
12
- raise(exception) if Time.now > deadline
14
+ raise(exception) unless deadline.remaining_time
13
15
  result = ''.b
14
- return result if bytes_to_read <= 0
15
- loop do
16
+ while result.bytesize < bytes_to_read
16
17
  read =
17
18
  with_deadline(deadline, exception) do
18
19
  read_nonblock(bytes_to_read - result.bytesize, exception: false)
19
20
  end
20
- unless read
21
- close
22
- return result
23
- end
24
- result += read
25
- return result if result.bytesize >= bytes_to_read
21
+ next result += read if read
22
+ close
23
+ break
26
24
  end
25
+ result
27
26
  end
28
27
 
29
28
  def write_with_deadline(data, deadline, exception)
30
- raise(exception) if Time.now > deadline
29
+ raise(exception) unless deadline.remaining_time
31
30
  return 0 if (size = data.bytesize).zero?
32
31
  result = 0
33
32
  loop do
@@ -46,10 +45,10 @@ module IOWithDeadlineMixin
46
45
  loop do
47
46
  case ret = yield
48
47
  when :wait_writable
49
- raise(exception) if (remaining_time = deadline - Time.now) <= 0
48
+ remaining_time = deadline.remaining_time or raise(exception)
50
49
  raise(exception) if wait_writable(remaining_time).nil?
51
50
  when :wait_readable
52
- raise(exception) if (remaining_time = deadline - Time.now) <= 0
51
+ remaining_time = deadline.remaining_time or raise(exception)
53
52
  raise(exception) if wait_readable(remaining_time).nil?
54
53
  else
55
54
  return ret
@@ -65,10 +64,10 @@ module IOWithDeadlineMixin
65
64
  loop do
66
65
  case ret = yield
67
66
  when :wait_writable
68
- raise(exception) if (remaining_time = deadline - Time.now) <= 0
67
+ remaining_time = deadline.remaining_time or raise(exception)
69
68
  raise(exception) if ::IO.select(nil, [self], nil, remaining_time).nil?
70
69
  when :wait_readable
71
- raise(exception) if (remaining_time = deadline - Time.now) <= 0
70
+ remaining_time = deadline.remaining_time or raise(exception)
72
71
  raise(exception) if ::IO.select([self], nil, nil, remaining_time).nil?
73
72
  else
74
73
  return ret
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'openssl'
3
5
  rescue LoadError
4
6
  return
5
7
  end
6
8
 
9
+ require_relative 'deadline'
7
10
  require_relative 'mixin/io_with_deadline'
8
11
 
9
12
  class TCPClient
@@ -12,8 +15,8 @@ class TCPClient
12
15
 
13
16
  def initialize(socket, address, configuration, exception)
14
17
  ssl_params = Hash[configuration.ssl_params]
15
- self.sync_close = true
16
18
  super(socket, create_context(ssl_params))
19
+ self.sync_close = true
17
20
  connect_to(
18
21
  address,
19
22
  ssl_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE,
@@ -32,13 +35,13 @@ class TCPClient
32
35
 
33
36
  def connect_to(address, check, timeout, exception)
34
37
  self.hostname = address.hostname
35
- timeout = timeout.to_f
36
- if timeout.zero?
37
- connect
38
- else
39
- with_deadline(Time.now + timeout, exception) do
38
+ deadline = Deadline.new(timeout)
39
+ if deadline.valid?
40
+ with_deadline(deadline, exception) do
40
41
  connect_nonblock(exception: false)
41
42
  end
43
+ else
44
+ connect
42
45
  end
43
46
  post_connection_check(address.hostname) if check
44
47
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
4
+ require_relative 'deadline'
2
5
  require_relative 'mixin/io_with_deadline'
3
6
 
4
7
  class TCPClient
@@ -19,9 +22,9 @@ class TCPClient
19
22
  address.addrinfo.ip_port,
20
23
  address.addrinfo.ip_address
21
24
  )
22
- timeout = timeout.to_f
23
- return connect(addr) if timeout.zero?
24
- with_deadline(Time.now + timeout, exception) do
25
+ deadline = Deadline.new(timeout)
26
+ return connect(addr) unless deadline.valid?
27
+ with_deadline(deadline, exception) do
25
28
  connect_nonblock(addr, exception: false)
26
29
  end
27
30
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class TCPClient
2
- VERSION = '0.3.2'.freeze
4
+ VERSION = '0.4.0'
3
5
  end
data/rakefile.rb CHANGED
@@ -11,7 +11,6 @@ CLOBBER << 'prj'
11
11
  task(:default) { exec('rake --tasks') }
12
12
 
13
13
  Rake::TestTask.new(:test) do |task|
14
- task.test_files = FileList['test/**/*_test.rb']
15
- task.ruby_opts = %w[-w]
16
- task.verbose = true
14
+ task.pattern = 'test/**/*_test.rb'
15
+ task.warning = task.verbose = true
17
16
  end
data/sample/google.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../lib/tcp-client'
2
4
 
3
5
  TCPClient.configure(
@@ -6,15 +8,14 @@ TCPClient.configure(
6
8
  read_timeout: 0.5 # seconds to read some bytes
7
9
  )
8
10
 
9
- # the following request sequence is not allowed
10
- # to last longer than 1.25 seconds:
11
+ # the following sequence is not allowed to last longer than 1.25 seconds:
11
12
  # 0.5 seconds to connect
12
13
  # + 0.25 seconds to write data
13
14
  # + 0.5 seconds to read a response
14
15
 
15
16
  TCPClient.open('www.google.com:80') do |client|
16
17
  # simple HTTP get request
17
- pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
18
+ pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
18
19
 
19
20
  # read "HTTP/1.1 " + 3 byte HTTP status code
20
21
  pp client.read(12)
data/sample/google_ssl.rb CHANGED
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../lib/tcp-client'
2
4
 
3
5
  TCPClient.configure do |cfg|
4
- cfg.connect_timeout = 1 # second to connect the server
6
+ cfg.connect_timeout = 1 # limit connect time the server to 1 second
5
7
  cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
6
8
  end
7
9
 
8
10
  TCPClient.open('www.google.com:443') do |client|
9
- # query should not last longer than 0.5 seconds
11
+ # next sequence should not last longer than 0.5 seconds
10
12
  client.with_deadline(0.5) do
11
13
  # simple HTTP get request
12
- pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
14
+ pp client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
13
15
 
14
16
  # read "HTTP/1.1 " + 3 byte HTTP status code
15
17
  pp client.read(12)
data/tcp-client.gemspec CHANGED
@@ -11,11 +11,13 @@ GemSpec = Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = 'A TCP client implementation with working timeout support.'
13
13
  spec.description = <<~DESCRIPTION
14
- This gem implements a TCP client with (optional) SSL support. The
15
- motivation of this project is the need to have a _really working_
16
- easy to use client which can handle time limits correctly. Unlike
17
- other implementations this client respects given/configurable time
18
- limits for each method (`connect`, `read`, `write`).
14
+ This Gem implements a TCP client with (optional) SSL support.
15
+ It is an easy to use, versatile configurable client that can correctly
16
+ handle time limits.
17
+ Unlike other implementations, this client respects
18
+ predefined/configurable time limits for each method
19
+ (`connect`, `read`, `write`). Deadlines for a sequence of read/write
20
+ actions can also be monitored.
19
21
  DESCRIPTION
20
22
  spec.homepage = 'https://github.com/mblumtritt/tcp-client'
21
23
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../test_helper'
2
4
 
3
5
  class AddressTest < MiniTest::Test
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../test_helper'
2
4
 
3
5
  class ConfigurationTest < MiniTest::Test
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class Deadlineest < MiniTest::Test
6
+ parallelize_me!
7
+
8
+ def test_validity
9
+ assert(TCPClient::Deadline.new(1).valid?)
10
+ assert(TCPClient::Deadline.new(0.0001).valid?)
11
+
12
+ refute(TCPClient::Deadline.new(0).valid?)
13
+ refute(TCPClient::Deadline.new(nil).valid?)
14
+ end
15
+
16
+ def test_remaining_time
17
+ assert(TCPClient::Deadline.new(1).remaining_time > 0)
18
+
19
+ assert_nil(TCPClient::Deadline.new(0).remaining_time)
20
+ assert_nil(TCPClient::Deadline.new(nil).remaining_time)
21
+
22
+ deadline = TCPClient::Deadline.new(0.2)
23
+ sleep(0.2)
24
+ assert_nil(deadline.remaining_time)
25
+ end
26
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../test_helper'
2
4
 
3
5
  class VersionTest < MiniTest::Test
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'test_helper'
2
4
 
3
5
  class TCPClientTest < MiniTest::Test
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.3.2
4
+ version: 0.4.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-07-11 00:00:00.000000000 Z
11
+ date: 2021-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,11 +53,13 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description: |
56
- This gem implements a TCP client with (optional) SSL support. The
57
- motivation of this project is the need to have a _really working_
58
- easy to use client which can handle time limits correctly. Unlike
59
- other implementations this client respects given/configurable time
60
- limits for each method (`connect`, `read`, `write`).
56
+ This Gem implements a TCP client with (optional) SSL support.
57
+ It is an easy to use, versatile configurable client that can correctly
58
+ handle time limits.
59
+ Unlike other implementations, this client respects
60
+ predefined/configurable time limits for each method
61
+ (`connect`, `read`, `write`). Deadlines for a sequence of read/write
62
+ actions can also be monitored.
61
63
  email:
62
64
  executables: []
63
65
  extensions: []
@@ -70,6 +72,7 @@ files:
70
72
  - lib/tcp-client.rb
71
73
  - lib/tcp-client/address.rb
72
74
  - lib/tcp-client/configuration.rb
75
+ - lib/tcp-client/deadline.rb
73
76
  - lib/tcp-client/default_configuration.rb
74
77
  - lib/tcp-client/errors.rb
75
78
  - lib/tcp-client/mixin/io_with_deadline.rb
@@ -83,6 +86,7 @@ files:
83
86
  - tcp-client.gemspec
84
87
  - test/tcp-client/address_test.rb
85
88
  - test/tcp-client/configuration_test.rb
89
+ - test/tcp-client/deadline_test.rb
86
90
  - test/tcp-client/default_configuration_test.rb
87
91
  - test/tcp-client/version_test.rb
88
92
  - test/tcp_client_test.rb
@@ -114,6 +118,7 @@ summary: A TCP client implementation with working timeout support.
114
118
  test_files:
115
119
  - test/tcp-client/address_test.rb
116
120
  - test/tcp-client/configuration_test.rb
121
+ - test/tcp-client/deadline_test.rb
117
122
  - test/tcp-client/default_configuration_test.rb
118
123
  - test/tcp-client/version_test.rb
119
124
  - test/tcp_client_test.rb