tcp-client 0.3.2 → 0.4.0

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