tcp-client 0.0.11 → 0.1.5

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: afd4d9a8cc41724a9d20fbdf0794dd8d7d4ed7103728d749ee6d823a49e03866
4
- data.tar.gz: f63e5da4f6cdcfb449d918bddc79a0826fc6cf9c35017a6f1762dcc881f37c79
3
+ metadata.gz: b1c2652ec394a4f083349a0254f8eeb9432b18a8869b98d39db221cc76dbdd1b
4
+ data.tar.gz: 0b26e622ae44a45f5e7b7c07fb9d81f6bf2a0f84bccb1ff7f0ab6563fe59b28e
5
5
  SHA512:
6
- metadata.gz: 8da7b4a3a87585b206341d1aa770ecf08ebc937fcd5117a594804d6bb8d0fd1a8f12cf3e29e2fa2d8841e78496fa7b48ee4041edd4bff15b12cd1ecd43399106
7
- data.tar.gz: 7b61d7ad8d26120a5a39414a93c51bd8fb0dc11eeb7008ea647b5bb8b2ac51af855539f41929f92c3374766765c3588f02009348c216d24dea2d88c4beab5a3c
6
+ metadata.gz: 7842b8e3d9f53287ed83e6ca9ebd139f33c2ddd6472b474d69db8c0481178b0f74a386dc62b8e0251cc0a2742624976efa22530582d3c3af14ae94231868fc4b
7
+ data.tar.gz: af08470a96f7d2cf64086184dab3d39ac9d84741c19a848fbfb4ff0dab3e2499d72f17dce99d11967235e0d09b2b03a1c5c3116588884ca6656e6daf0f0015f8
data/README.md CHANGED
@@ -8,20 +8,22 @@ This gem implements a TCP client with (optional) SSL support. The motivation of
8
8
  ## Sample
9
9
 
10
10
  ```ruby
11
- configuration = TCPClient::Configuration.create do |cfg|
11
+ require 'tcp-client'
12
+
13
+ TCPClient.configure do |cfg|
12
14
  cfg.connect_timeout = 1 # second to connect the server
13
- cfg.write_timeout = 0.25 # seconds to write a single data junk
14
- cfg.read_timeout = 0.5 # seconds to read some bytes
15
- cfg.ssl_params = {ssl_version: :TLSv1_2} # use TLS 1.2
15
+ cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
16
16
  end
17
17
 
18
- # the following request sequence is not allowed to last longer than 2 seconds:
19
- # 1 second to connect (incl. SSL handshake etc.)
20
- # + 0.25 seconds to write data
21
- # + 0.5 seconds to read a response
22
- TCPClient.open('www.google.com:443', configuration) do |client|
23
- pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
24
- pp client.read(12) # "HTTP/1.1 " + 3 byte HTTP status code
18
+ TCPClient.open('www.google.com:443') do |client|
19
+ # query should not last longer than 0.5 seconds
20
+ client.with_deadline(0.5) do
21
+ # simple HTTP get request
22
+ pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
23
+
24
+ # read "HTTP/1.1 " + 3 byte HTTP status code
25
+ pp client.read(12)
26
+ end
25
27
  end
26
28
  ```
27
29
 
data/gems.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source('https://rubygems.org'){ gemspec }
3
+ source('https://rubygems.org') { gemspec }
data/lib/tcp-client.rb CHANGED
@@ -4,6 +4,7 @@ require_relative 'tcp-client/address'
4
4
  require_relative 'tcp-client/tcp_socket'
5
5
  require_relative 'tcp-client/ssl_socket'
6
6
  require_relative 'tcp-client/configuration'
7
+ require_relative 'tcp-client/default_configuration'
7
8
  require_relative 'tcp-client/version'
8
9
 
9
10
  class TCPClient
@@ -19,9 +20,15 @@ class TCPClient
19
20
  end
20
21
  end
21
22
 
22
- Timeout = Class.new(IOError)
23
+ TimeoutError = Class.new(IOError)
24
+ ConnectTimeoutError = Class.new(TimeoutError)
25
+ ReadTimeoutError = Class.new(TimeoutError)
26
+ WriteTimeoutError = Class.new(TimeoutError)
23
27
 
24
- def self.open(addr, configuration = Configuration.new)
28
+ Timeout = TimeoutError # backward compatibility
29
+ deprecate_constant(:Timeout)
30
+
31
+ def self.open(addr, configuration = Configuration.default)
25
32
  addr = Address.new(addr)
26
33
  client = new
27
34
  client.connect(addr, configuration)
@@ -34,20 +41,20 @@ class TCPClient
34
41
 
35
42
  def initialize
36
43
  @socket = @address = @write_timeout = @read_timeout = nil
44
+ @deadline = nil
37
45
  end
38
46
 
39
47
  def to_s
40
48
  @address ? @address.to_s : ''
41
49
  end
42
50
 
43
- def connect(addr, configuration)
51
+ def connect(addr, configuration, exception: ConnectTimeoutError)
44
52
  close
45
53
  NoOpenSSL.raise! if configuration.ssl? && !defined?(SSLSocket)
46
54
  @address = Address.new(addr)
47
- @socket = TCPSocket.new(@address, configuration, Timeout)
48
- configuration.ssl? && @socket = SSLSocket.new(
49
- @socket, @address, configuration, Timeout
50
- )
55
+ @socket = TCPSocket.new(@address, configuration, exception)
56
+ configuration.ssl? &&
57
+ @socket = SSLSocket.new(@socket, @address, configuration, exception)
51
58
  @write_timeout = configuration.write_timeout
52
59
  @read_timeout = configuration.read_timeout
53
60
  self
@@ -59,24 +66,47 @@ class TCPClient
59
66
  self
60
67
  rescue IOError
61
68
  self
69
+ ensure
70
+ @deadline = nil
62
71
  end
63
72
 
64
73
  def closed?
65
74
  @socket.nil? || @socket.closed?
66
75
  end
67
76
 
68
- def read(nbytes, timeout: @read_timeout)
77
+ def with_deadline(timeout)
78
+ raise('no block given') unless block_given?
79
+ raise('deadline already used') if @deadline
80
+ tm = timeout&.to_f
81
+ raise(ArgumentError, "invalid deadline - #{timeout}") unless tm&.positive?
82
+ @deadline = Time.now + tm
83
+ yield(self)
84
+ ensure
85
+ @deadline = nil
86
+ end
87
+
88
+ def read(nbytes, timeout: nil, exception: ReadTimeoutError)
69
89
  NotConnected.raise!(self) if closed?
70
- @socket.read(nbytes, timeout: timeout, exception: Timeout)
90
+ time = timeout || remaining_time(exception) || @read_timeout
91
+ @socket.read(nbytes, timeout: time, exception: exception)
71
92
  end
72
93
 
73
- def write(*msg, timeout: @write_timeout)
94
+ def write(*msg, timeout: nil, exception: WriteTimeoutError)
74
95
  NotConnected.raise!(self) if closed?
75
- @socket.write(*msg, timeout: timeout, exception: Timeout)
96
+ time = timeout || remaining_time(exception) || @write_timeout
97
+ @socket.write(*msg, timeout: time, exception: exception)
76
98
  end
77
99
 
78
100
  def flush
79
101
  @socket.flush unless closed?
80
102
  self
81
103
  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
82
112
  end
@@ -25,6 +25,19 @@ class TCPClient
25
25
  "#{@hostname}:#{@addrinfo.ip_port}"
26
26
  end
27
27
 
28
+ def to_h
29
+ { host: @hostname, port: @addrinfo.ip_port }
30
+ end
31
+
32
+ def ==(other)
33
+ to_h == other.to_h
34
+ end
35
+ alias eql? ==
36
+
37
+ def equal?(other)
38
+ self.class == other.class && self == other
39
+ end
40
+
28
41
  private
29
42
 
30
43
  def init_from_selfclass(address)
@@ -44,7 +57,7 @@ class TCPClient
44
57
  end
45
58
 
46
59
  def from_string(str)
47
- return [nil, str.to_i] unless idx = str.rindex(':')
60
+ idx = str.rindex(':') or return nil, str.to_i
48
61
  name = str[0, idx]
49
62
  name = name[1, name.size - 2] if name[0] == '[' && name[-1] == ']'
50
63
  [name, str[idx + 1, str.size - idx].to_i]
@@ -1,39 +1,40 @@
1
1
  class TCPClient
2
2
  class Configuration
3
- def self.create
4
- ret = new
3
+ def self.create(options = {})
4
+ ret = new(options)
5
5
  yield(ret) if block_given?
6
6
  ret
7
7
  end
8
8
 
9
- attr_reader :buffered, :keep_alive, :reverse_lookup
9
+ attr_reader :buffered, :keep_alive, :reverse_lookup, :timeout
10
10
  attr_accessor :ssl_params
11
11
 
12
- def initialize
12
+ def initialize(options = {})
13
13
  @buffered = @keep_alive = @reverse_lookup = true
14
14
  self.timeout = @ssl_params = nil
15
+ options.each_pair { |attribute, value| set(attribute, value) }
15
16
  end
16
17
 
17
18
  def ssl?
18
19
  @ssl_params ? true : false
19
20
  end
20
21
 
21
- def ssl=(yn)
22
- return @ssl_params = nil unless yn
23
- return @ssl_params = yn if Hash === yn
22
+ def ssl=(value)
23
+ return @ssl_params = nil unless value
24
+ return @ssl_params = value.dup if Hash === value
24
25
  @ssl_params ||= {}
25
26
  end
26
27
 
27
- def buffered=(yn)
28
- @buffered = yn ? true : false
28
+ def buffered=(value)
29
+ @buffered = value ? true : false
29
30
  end
30
31
 
31
- def keep_alive=(yn)
32
- @keep_alive = yn ? true : false
32
+ def keep_alive=(value)
33
+ @keep_alive = value ? true : false
33
34
  end
34
35
 
35
- def reverse_lookup=(yn)
36
- @reverse_lookup = yn ? true : false
36
+ def reverse_lookup=(value)
37
+ @reverse_lookup = value ? true : false
37
38
  end
38
39
 
39
40
  def timeout=(seconds)
@@ -65,10 +66,38 @@ class TCPClient
65
66
  @read_timeout = seconds(seconds)
66
67
  end
67
68
 
69
+ def to_h
70
+ {
71
+ buffered: @buffered,
72
+ keep_alive: @keep_alive,
73
+ reverse_lookup: @reverse_lookup,
74
+ timeout: @timeout,
75
+ connect_timeout: @connect_timeout,
76
+ read_timeout: @read_timeout,
77
+ write_timeout: @write_timeout,
78
+ ssl_params: @ssl_params
79
+ }
80
+ end
81
+
82
+ def ==(other)
83
+ to_h == other.to_h
84
+ end
85
+ alias eql? ==
86
+
87
+ def equal?(other)
88
+ self.class == other.class && self == other
89
+ end
90
+
68
91
  private
69
92
 
93
+ def set(attribute, value)
94
+ public_send("#{attribute}=", value)
95
+ rescue NoMethodError
96
+ raise(ArgumentError, "unknown attribute - #{attribute}")
97
+ end
98
+
70
99
  def seconds(value)
71
- value&.positive? ? value : nil
100
+ value&.to_f&.positive? ? value : nil
72
101
  end
73
102
  end
74
103
  end
@@ -0,0 +1,21 @@
1
+ require_relative 'configuration'
2
+
3
+ class TCPClient
4
+ @default_configuration = Configuration.new
5
+
6
+ class << self
7
+ attr_reader :default_configuration
8
+
9
+ def configure(options = {})
10
+ cfg = Configuration.new(options)
11
+ yield(cfg) if block_given?
12
+ @default_configuration = cfg
13
+ end
14
+ end
15
+
16
+ class Configuration
17
+ def self.default
18
+ TCPClient.default_configuration
19
+ end
20
+ end
21
+ end
@@ -12,7 +12,7 @@ module IOTimeoutMixin
12
12
 
13
13
  def read(nbytes, timeout: nil, exception: IOTimeoutError)
14
14
  timeout = timeout.to_f
15
- return read_all(nbytes){ |junk_size| super(junk_size) } if timeout <= 0
15
+ return read_all(nbytes) { |junk_size| super(junk_size) } if timeout <= 0
16
16
  deadline = Time.now + timeout
17
17
  read_all(nbytes) do |junk_size|
18
18
  with_deadline(deadline, exception) do
@@ -23,9 +23,9 @@ module IOTimeoutMixin
23
23
 
24
24
  def write(*msgs, timeout: nil, exception: IOTimeoutError)
25
25
  timeout = timeout.to_f
26
- return write_all(msgs.join){ |junk| super(junk) } if timeout <= 0
26
+ return write_all(msgs.join.b) { |junk| super(junk) } if timeout <= 0
27
27
  deadline = Time.now + timeout
28
- write_all(msgs.join) do |junk|
28
+ write_all(msgs.join.b) do |junk|
29
29
  with_deadline(deadline, exception) do
30
30
  write_nonblock(junk, exception: false)
31
31
  end
@@ -59,9 +59,7 @@ module IOTimeoutMixin
59
59
  end
60
60
 
61
61
  module DeadlineMethods
62
- private
63
-
64
- def with_deadline(deadline, exclass)
62
+ private def with_deadline(deadline, exclass)
65
63
  loop do
66
64
  case ret = yield
67
65
  when :wait_writable
@@ -78,9 +76,7 @@ module IOTimeoutMixin
78
76
  end
79
77
 
80
78
  module DeadlineIO
81
- private
82
-
83
- def with_deadline(deadline, exclass)
79
+ private def with_deadline(deadline, exclass)
84
80
  loop do
85
81
  case ret = yield
86
82
  when :wait_writable
@@ -14,9 +14,11 @@ class TCPClient
14
14
  private
15
15
 
16
16
  def connect_to(address, timeout, exception)
17
- addr = ::Socket.pack_sockaddr_in(
18
- address.addrinfo.ip_port, address.addrinfo.ip_address
19
- )
17
+ addr =
18
+ ::Socket.pack_sockaddr_in(
19
+ address.addrinfo.ip_port,
20
+ address.addrinfo.ip_address
21
+ )
20
22
  return connect(addr) unless timeout
21
23
  with_deadline(Time.now + timeout, exception) do
22
24
  connect_nonblock(addr, exception: false)
@@ -1,3 +1,3 @@
1
1
  class TCPClient
2
- VERSION = '0.0.11'.freeze
2
+ VERSION = '0.1.5'.freeze
3
3
  end
data/rakefile.rb CHANGED
@@ -4,7 +4,9 @@ require 'rake/clean'
4
4
  require 'rake/testtask'
5
5
  require 'bundler/gem_tasks'
6
6
 
7
- STDOUT.sync = STDERR.sync = true
7
+ $stdout.sync = $stderr.sync = true
8
+
9
+ task(:default) { exec('rake --tasks') }
8
10
 
9
11
  CLOBBER << 'prj'
10
12
 
@@ -13,7 +15,3 @@ Rake::TestTask.new(:test) do |t|
13
15
  t.verbose = true
14
16
  t.test_files = FileList['test/**/*_test.rb']
15
17
  end
16
-
17
- task :default do
18
- exec("#{$PROGRAM_NAME} --tasks")
19
- end
data/sample/google.rb CHANGED
@@ -1,16 +1,21 @@
1
1
  require_relative '../lib/tcp-client'
2
2
 
3
- configuration = TCPClient::Configuration.create do |cfg|
4
- cfg.connect_timeout = 0.5 # seconds to connect the server
5
- cfg.write_timeout = 0.25 # seconds to write a single data junk
6
- cfg.read_timeout = 0.5 # seconds to read some bytes
7
- end
3
+ TCPClient.configure(
4
+ connect_timeout: 0.5, # seconds to connect the server
5
+ write_timeout: 0.25, # seconds to write a single data junk
6
+ read_timeout: 0.5 # seconds to read some bytes
7
+ )
8
8
 
9
- # the following request sequence is not allowed to last longer than 1.25 seconds:
10
- # 0.5 seconds to connect
9
+ # the following request sequence is not allowed
10
+ # to last longer than 1.25 seconds:
11
+ # 0.5 seconds to connect
11
12
  # + 0.25 seconds to write data
12
13
  # + 0.5 seconds to read a response
13
- TCPClient.open('www.google.com:80', configuration) do |client|
14
- pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
15
- pp client.read(12) # "HTTP/1.1 " + 3 byte HTTP status code
14
+
15
+ TCPClient.open('www.google.com:80') do |client|
16
+ # simple HTTP get request
17
+ pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
18
+
19
+ # read "HTTP/1.1 " + 3 byte HTTP status code
20
+ pp client.read(12)
16
21
  end
data/sample/google_ssl.rb CHANGED
@@ -1,17 +1,17 @@
1
1
  require_relative '../lib/tcp-client'
2
2
 
3
- configuration = TCPClient::Configuration.create do |cfg|
3
+ TCPClient.configure do |cfg|
4
4
  cfg.connect_timeout = 1 # second to connect the server
5
- cfg.write_timeout = 0.25 # seconds to write a single data junk
6
- cfg.read_timeout = 0.5 # seconds to read some bytes
7
- cfg.ssl_params = {ssl_version: :TLSv1_2} # use TLS 1.2
5
+ cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
8
6
  end
9
7
 
10
- # the following request sequence is not allowed to last longer than 2 seconds:
11
- # 1 second to connect (incl. SSL handshake etc.)
12
- # + 0.25 seconds to write data
13
- # + 0.5 seconds to read a response
14
- TCPClient.open('www.google.com:443', configuration) do |client|
15
- pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
16
- pp client.read(12) # "HTTP/1.1 " + 3 byte HTTP status code
8
+ TCPClient.open('www.google.com:443') do |client|
9
+ # query should not last longer than 0.5 seconds
10
+ client.with_deadline(0.5) do
11
+ # simple HTTP get request
12
+ pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
13
+
14
+ # read "HTTP/1.1 " + 3 byte HTTP status code
15
+ pp client.read(12)
16
+ end
17
17
  end
data/tcp-client.gemspec CHANGED
@@ -5,6 +5,10 @@ require_relative './lib/tcp-client/version'
5
5
  GemSpec = Gem::Specification.new do |spec|
6
6
  spec.name = 'tcp-client'
7
7
  spec.version = TCPClient::VERSION
8
+ spec.author = 'Mike Blumtritt'
9
+
10
+ spec.required_ruby_version = '>= 2.7.0'
11
+
8
12
  spec.summary = 'A TCP client implementation with working timeout support.'
9
13
  spec.description = <<~DESCRIPTION
10
14
  This gem implements a TCP client with (optional) SSL support. The
@@ -13,27 +17,17 @@ GemSpec = Gem::Specification.new do |spec|
13
17
  other implementations this client respects given/configurable time
14
18
  limits for each method (`connect`, `read`, `write`).
15
19
  DESCRIPTION
16
- spec.author = 'Mike Blumtritt'
17
- spec.email = 'mike.blumtritt@pm.me'
18
20
  spec.homepage = 'https://github.com/mblumtritt/tcp-client'
19
- spec.metadata = {
20
- 'source_code_uri' => 'https://github.com/mblumtritt/tcp-client',
21
- 'bug_tracker_uri' => 'https://github.com/mblumtritt/tcp-client/issues'
22
- }
23
- spec.rubyforge_project = spec.name
21
+
22
+ spec.metadata['source_code_uri'] = 'https://github.com/mblumtritt/tcp-client'
23
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/mblumtritt/tcp-client/issues'
24
24
 
25
25
  spec.add_development_dependency 'bundler'
26
26
  spec.add_development_dependency 'minitest'
27
27
  spec.add_development_dependency 'rake'
28
28
 
29
- spec.platform = Gem::Platform::RUBY
30
- spec.required_ruby_version = '>= 2.5.0'
31
- spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
32
-
33
- spec.require_paths = %w[lib]
34
-
35
- all_files = %x(git ls-files -z).split(0.chr)
36
- spec.test_files = all_files.grep(%r{^(spec|test)/})
29
+ all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
30
+ spec.test_files = all_files.grep(%r{^test/})
37
31
  spec.files = all_files - spec.test_files
38
32
 
39
33
  spec.extra_rdoc_files = %w[README.md]
@@ -1,6 +1,8 @@
1
1
  require_relative '../test_helper'
2
2
 
3
- class AddressTest < Test
3
+ class AddressTest < MiniTest::Test
4
+ parallelize_me!
5
+
4
6
  def test_create_from_integer
5
7
  subject = TCPClient::Address.new(42)
6
8
  assert_equal('localhost:42', subject.to_s)
@@ -52,4 +54,12 @@ class AddressTest < Test
52
54
  assert(subject.addrinfo.ip?)
53
55
  assert(subject.addrinfo.ipv6?)
54
56
  end
57
+
58
+ def test_compare
59
+ a = TCPClient::Address.new('localhost:42')
60
+ b = TCPClient::Address.new('localhost:42')
61
+ assert_equal(a, b)
62
+ assert(a == b)
63
+ assert(a === b)
64
+ end
55
65
  end
@@ -1,6 +1,8 @@
1
1
  require_relative '../test_helper'
2
2
 
3
- class ConfigurationTest < Test
3
+ class ConfigurationTest < MiniTest::Test
4
+ parallelize_me!
5
+
4
6
  def test_defaults
5
7
  subject = TCPClient::Configuration.new
6
8
  assert(subject.buffered)
@@ -13,11 +15,12 @@ class ConfigurationTest < Test
13
15
  end
14
16
 
15
17
  def test_configure
16
- subject = TCPClient::Configuration.create do |cfg|
17
- cfg.buffered = cfg.keep_alive = cfg.reverse_lookup = false
18
- cfg.timeout = 42
19
- cfg.ssl = true
20
- end
18
+ subject =
19
+ TCPClient::Configuration.create do |cfg|
20
+ cfg.buffered = cfg.keep_alive = cfg.reverse_lookup = false
21
+ cfg.timeout = 42
22
+ cfg.ssl = true
23
+ end
21
24
  refute(subject.buffered)
22
25
  refute(subject.keep_alive)
23
26
  refute(subject.reverse_lookup)
@@ -27,6 +30,35 @@ class ConfigurationTest < Test
27
30
  assert(subject.ssl?)
28
31
  end
29
32
 
33
+ def test_options
34
+ subject =
35
+ TCPClient::Configuration.new(
36
+ buffered: false,
37
+ keep_alive: false,
38
+ reverse_lookup: false,
39
+ connect_timeout: 1,
40
+ read_timeout: 2,
41
+ write_timeout: 3,
42
+ ssl: true
43
+ )
44
+ refute(subject.buffered)
45
+ refute(subject.keep_alive)
46
+ refute(subject.reverse_lookup)
47
+ assert_same(1, subject.connect_timeout)
48
+ assert_same(2, subject.read_timeout)
49
+ assert_same(3, subject.write_timeout)
50
+ assert(subject.ssl?)
51
+ end
52
+
53
+ def test_invalid_option
54
+ err =
55
+ assert_raises(ArgumentError) do
56
+ TCPClient::Configuration.new(unknown_attr: :argument)
57
+ end
58
+ assert_includes(err.message, 'attribute')
59
+ assert_includes(err.message, 'unknown_attr')
60
+ end
61
+
30
62
  def test_ssl_params
31
63
  subject = TCPClient::Configuration.new
32
64
  refute(subject.ssl?)
@@ -40,11 +72,12 @@ class ConfigurationTest < Test
40
72
  end
41
73
 
42
74
  def test_timeout_overwrite
43
- subject = TCPClient::Configuration.create do |cfg|
44
- cfg.connect_timeout = 1
45
- cfg.read_timeout = 2
46
- cfg.write_timeout = 3
47
- end
75
+ subject =
76
+ TCPClient::Configuration.create do |cfg|
77
+ cfg.connect_timeout = 1
78
+ cfg.read_timeout = 2
79
+ cfg.write_timeout = 3
80
+ end
48
81
  assert_same(1, subject.connect_timeout)
49
82
  assert_same(2, subject.read_timeout)
50
83
  assert_same(3, subject.write_timeout)
@@ -54,4 +87,12 @@ class ConfigurationTest < Test
54
87
  assert_same(42, subject.read_timeout)
55
88
  assert_same(42, subject.write_timeout)
56
89
  end
90
+
91
+ def test_compare
92
+ a = TCPClient::Configuration.new
93
+ b = TCPClient::Configuration.new
94
+ assert_equal(a, b)
95
+ assert(a == b)
96
+ assert(a === b)
97
+ end
57
98
  end
@@ -0,0 +1,59 @@
1
+ require_relative '../test_helper'
2
+
3
+ class DefauktConfigurationTest < MiniTest::Test
4
+ def test_default
5
+ subject = TCPClient.configure # reset to defaults
6
+
7
+ assert_same(
8
+ TCPClient.default_configuration,
9
+ TCPClient::Configuration.default
10
+ )
11
+ assert(subject.buffered)
12
+ assert(subject.keep_alive)
13
+ assert(subject.reverse_lookup)
14
+ refute(subject.ssl?)
15
+ assert_nil(subject.connect_timeout)
16
+ assert_nil(subject.read_timeout)
17
+ assert_nil(subject.write_timeout)
18
+ end
19
+
20
+ def test_configure_options
21
+ TCPClient.configure(
22
+ buffered: false,
23
+ keep_alive: false,
24
+ reverse_lookup: false,
25
+ ssl: true,
26
+ connect_timeout: 1,
27
+ read_timeout: 2,
28
+ write_timeout: 3
29
+ )
30
+ subject = TCPClient.default_configuration
31
+ refute(subject.buffered)
32
+ refute(subject.keep_alive)
33
+ refute(subject.reverse_lookup)
34
+ assert(subject.ssl?)
35
+ assert_same(1, subject.connect_timeout)
36
+ assert_same(2, subject.read_timeout)
37
+ assert_same(3, subject.write_timeout)
38
+ end
39
+
40
+ def test_configure_block
41
+ TCPClient.configure do |cfg|
42
+ cfg.buffered = false
43
+ cfg.keep_alive = false
44
+ cfg.reverse_lookup = false
45
+ cfg.ssl = true
46
+ cfg.connect_timeout = 1
47
+ cfg.read_timeout = 2
48
+ cfg.write_timeout = 3
49
+ end
50
+ subject = TCPClient.default_configuration
51
+ refute(subject.buffered)
52
+ refute(subject.keep_alive)
53
+ refute(subject.reverse_lookup)
54
+ assert(subject.ssl?)
55
+ assert_same(1, subject.connect_timeout)
56
+ assert_same(2, subject.read_timeout)
57
+ assert_same(3, subject.write_timeout)
58
+ end
59
+ end
@@ -1,6 +1,8 @@
1
1
  require_relative '../test_helper'
2
2
 
3
- class VersionTest < Test
3
+ class VersionTest < MiniTest::Test
4
+ parallelize_me!
5
+
4
6
  def test_format
5
7
  assert_match(/\d+\.\d+\.\d+/, TCPClient::VERSION)
6
8
  end
@@ -1,26 +1,32 @@
1
1
  require_relative 'test_helper'
2
2
 
3
- class TCPClientTest < Test
3
+ class TCPClientTest < MiniTest::Test
4
+ parallelize_me!
5
+
6
+ HUGE_AMOUNT_OF_DATA = Array.new(2024, '?' * 1024).freeze
7
+
8
+ attr_reader :config
9
+
10
+ def setup
11
+ @config = TCPClient::Configuration.create(buffered: false)
12
+ end
13
+
4
14
  def test_defaults
5
15
  subject = TCPClient.new
6
16
  assert(subject.closed?)
7
17
  assert_equal('', subject.to_s)
8
18
  assert_nil(subject.address)
9
19
  subject.close
10
- assert_raises(TCPClient::NotConnected) do
11
- subject.write('hello world!')
12
- end
13
- assert_raises(TCPClient::NotConnected) do
14
- subject.read(42)
15
- end
20
+ assert_raises(TCPClient::NotConnected) { subject.write('hello world!') }
21
+ assert_raises(TCPClient::NotConnected) { subject.read(42) }
16
22
  end
17
23
 
18
24
  def create_nonconnected_client
19
25
  client = TCPClient.new
20
- client.connect('', TCPClient::Configuration.new)
26
+ client.connect('', config)
27
+ client
21
28
  rescue Errno::EADDRNOTAVAIL
22
- ensure
23
- return client
29
+ client
24
30
  end
25
31
 
26
32
  def test_failed_state
@@ -32,84 +38,120 @@ class TCPClientTest < Test
32
38
  assert_equal('localhost', subject.address.hostname)
33
39
  assert_instance_of(Addrinfo, subject.address.addrinfo)
34
40
  assert_same(0, subject.address.addrinfo.ip_port)
35
- assert_raises(TCPClient::NotConnected) do
36
- subject.write('hello world!')
37
- end
38
- assert_raises(TCPClient::NotConnected) do
39
- subject.read(42)
40
- end
41
+ assert_raises(TCPClient::NotConnected) { subject.write('hello world!') }
42
+ assert_raises(TCPClient::NotConnected) { subject.read(42) }
41
43
  end
42
44
 
43
- def with_dummy_server(port)
44
- # this server will never receive or send any data
45
- server = TCPServer.new('localhost', port)
46
- yield
47
- ensure
48
- server&.close
45
+ def test_connected_state
46
+ TCPClient.open('localhost:1234', config) do |subject|
47
+ refute(subject.closed?)
48
+ assert_equal('localhost:1234', subject.to_s)
49
+ refute_nil(subject.address)
50
+ address_when_opened = subject.address
51
+ assert_equal('localhost:1234', subject.address.to_s)
52
+ assert_equal('localhost', subject.address.hostname)
53
+ assert_instance_of(Addrinfo, subject.address.addrinfo)
54
+ assert_same(1234, subject.address.addrinfo.ip_port)
55
+
56
+ subject.close
57
+ assert(subject.closed?)
58
+ assert_same(address_when_opened, subject.address)
59
+ end
49
60
  end
50
61
 
51
- def test_connected_state
52
- with_dummy_server(1234) do
53
- TCPClient.open('localhost:1234') do |subject|
54
- refute(subject.closed?)
55
- assert_equal('localhost:1234', subject.to_s)
56
- refute_nil(subject.address)
57
- address_when_opened = subject.address
58
- assert_equal('localhost:1234', subject.address.to_s)
59
- assert_equal('localhost', subject.address.hostname)
60
- assert_instance_of(Addrinfo, subject.address.addrinfo)
61
- assert_same(1234, subject.address.addrinfo.ip_port)
62
-
63
- subject.close
64
- assert(subject.closed?)
65
- assert_same(address_when_opened, subject.address)
62
+ def check_read_timeout(timeout)
63
+ TCPClient.open('localhost:1234', config) do |subject|
64
+ refute(subject.closed?)
65
+ start_time = nil
66
+ assert_raises(TCPClient::ReadTimeoutError) do
67
+ start_time = Time.now
68
+ subject.read(42, timeout: timeout)
66
69
  end
70
+ assert_in_delta(timeout, Time.now - start_time, 0.11)
67
71
  end
68
72
  end
69
73
 
70
- def check_read_write_timeout(addr, timeout)
71
- TCPClient.open(addr) do |subject|
74
+ def test_read_timeout
75
+ check_read_timeout(0.5)
76
+ check_read_timeout(1)
77
+ check_read_timeout(1.5)
78
+ end
79
+
80
+ def check_write_timeout(timeout)
81
+ TCPClient.open('localhost:1234', config) do |subject|
72
82
  refute(subject.closed?)
73
83
  start_time = nil
74
- assert_raises(TCPClient::Timeout) do
84
+ assert_raises(TCPClient::WriteTimeoutError) do
75
85
  start_time = Time.now
76
- # send 1MB to avoid any TCP stack buffering
77
- subject.write('?' * (1024 * 1024), timeout: timeout)
86
+ subject.write(*HUGE_AMOUNT_OF_DATA, timeout: timeout)
78
87
  end
79
88
  assert_in_delta(timeout, Time.now - start_time, 0.02)
80
- assert_raises(TCPClient::Timeout) do
81
- start_time = Time.now
82
- subject.read(42, timeout: timeout)
89
+ end
90
+ end
91
+
92
+ def test_write_timeout
93
+ check_write_timeout(0.01)
94
+ check_write_timeout(0.25)
95
+ end
96
+
97
+ def test_write_deadline
98
+ TCPClient.open('localhost:1234', config) do |subject|
99
+ refute(subject.closed?)
100
+ assert_raises(TCPClient::WriteTimeoutError) do
101
+ subject.with_deadline(0.25) do |*args|
102
+ assert_equal([subject], args)
103
+ loop { subject.write('some data here') }
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def test_read_deadline
110
+ TCPClient.open('localhost:1234', config) do |subject|
111
+ refute(subject.closed?)
112
+ assert_raises(TCPClient::ReadTimeoutError) do
113
+ subject.with_deadline(0.25) do |*args|
114
+ assert_equal([subject], args)
115
+ loop { subject.read(0) }
116
+ end
83
117
  end
84
- assert_in_delta(timeout, Time.now - start_time, 0.02)
85
118
  end
86
119
  end
87
120
 
88
- def test_read_write_timeout
89
- with_dummy_server(1235) do
90
- [0.5, 1, 1.5].each do |timeout|
91
- check_read_write_timeout('localhost:1235', timeout)
121
+ def test_read_write_deadline
122
+ TCPClient.open('localhost:1234', config) do |subject|
123
+ refute(subject.closed?)
124
+ assert_raises(TCPClient::TimeoutError) do
125
+ subject.with_deadline(0.25) do |*args|
126
+ assert_equal([subject], args)
127
+ loop do
128
+ subject.write('HUGE_AMOUNT_OF_DATA')
129
+ subject.read(0)
130
+ end
131
+ end
92
132
  end
93
133
  end
94
134
  end
95
135
 
96
- def check_connect_timeout(addr, config)
136
+ def check_connect_timeout(ssl_config)
97
137
  start_time = nil
98
- assert_raises(TCPClient::Timeout) do
138
+ assert_raises(TCPClient::ConnectTimeoutError) do
99
139
  start_time = Time.now
100
- TCPClient.new.connect(addr, config)
140
+ TCPClient.new.connect('localhost:1234', ssl_config)
101
141
  end
102
- assert_in_delta(config.connect_timeout, Time.now - start_time, 0.02)
142
+ assert_in_delta(ssl_config.connect_timeout, Time.now - start_time, 0.11)
103
143
  end
104
144
 
105
145
  def test_connect_ssl_timeout
106
- with_dummy_server(1236) do
107
- config = TCPClient::Configuration.new
108
- config.ssl = true
109
- [0.5, 1, 1.5].each do |timeout|
110
- config.timeout = timeout
111
- check_connect_timeout('localhost:1236', config)
112
- end
113
- end
146
+ ssl_config = TCPClient::Configuration.new(ssl: true)
147
+
148
+ ssl_config.connect_timeout = 0.5
149
+ check_connect_timeout(ssl_config)
150
+
151
+ ssl_config.connect_timeout = 1
152
+ check_connect_timeout(ssl_config)
153
+
154
+ ssl_config.connect_timeout = 1.5
155
+ check_connect_timeout(ssl_config)
114
156
  end
115
157
  end
data/test/test_helper.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'minitest/autorun'
2
4
  require 'minitest/parallel'
3
5
  require_relative '../lib/tcp-client'
4
6
 
5
7
  $stdout.sync = $stderr.sync = true
6
8
 
7
- class Test < Minitest::Test
8
- parallelize_me!
9
- end
9
+ # this pseudo-server never reads or writes anything
10
+ DummyServer = TCPServer.new('localhost', 1234)
11
+ Minitest.after_run { DummyServer.close }
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.0.11
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-19 00:00:00.000000000 Z
11
+ date: 2021-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,7 +58,7 @@ description: |
58
58
  easy to use client which can handle time limits correctly. Unlike
59
59
  other implementations this client respects given/configurable time
60
60
  limits for each method (`connect`, `read`, `write`).
61
- email: mike.blumtritt@pm.me
61
+ email:
62
62
  executables: []
63
63
  extensions: []
64
64
  extra_rdoc_files:
@@ -70,6 +70,7 @@ files:
70
70
  - lib/tcp-client.rb
71
71
  - lib/tcp-client/address.rb
72
72
  - lib/tcp-client/configuration.rb
73
+ - lib/tcp-client/default_configuration.rb
73
74
  - lib/tcp-client/mixin/io_timeout.rb
74
75
  - lib/tcp-client/ssl_socket.rb
75
76
  - lib/tcp-client/tcp_socket.rb
@@ -81,6 +82,7 @@ files:
81
82
  - tcp-client.gemspec
82
83
  - test/tcp-client/address_test.rb
83
84
  - test/tcp-client/configuration_test.rb
85
+ - test/tcp-client/default_configuration_test.rb
84
86
  - test/tcp-client/version_test.rb
85
87
  - test/tcp_client_test.rb
86
88
  - test/test_helper.rb
@@ -89,7 +91,7 @@ licenses: []
89
91
  metadata:
90
92
  source_code_uri: https://github.com/mblumtritt/tcp-client
91
93
  bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
92
- post_install_message:
94
+ post_install_message:
93
95
  rdoc_options: []
94
96
  require_paths:
95
97
  - lib
@@ -97,20 +99,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
99
  requirements:
98
100
  - - ">="
99
101
  - !ruby/object:Gem::Version
100
- version: 2.5.0
102
+ version: 2.7.0
101
103
  required_rubygems_version: !ruby/object:Gem::Requirement
102
104
  requirements:
103
105
  - - ">="
104
106
  - !ruby/object:Gem::Version
105
- version: 1.3.6
107
+ version: '0'
106
108
  requirements: []
107
- rubygems_version: 3.0.3
108
- signing_key:
109
+ rubygems_version: 3.2.9
110
+ signing_key:
109
111
  specification_version: 4
110
112
  summary: A TCP client implementation with working timeout support.
111
113
  test_files:
112
114
  - test/tcp-client/address_test.rb
113
115
  - test/tcp-client/configuration_test.rb
116
+ - test/tcp-client/default_configuration_test.rb
114
117
  - test/tcp-client/version_test.rb
115
118
  - test/tcp_client_test.rb
116
119
  - test/test_helper.rb