tcp-client 0.0.6 → 0.1.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: 9a5a96328918c653aaec2296d19a7434b65c8801f3a1132a152dcd66326bc9ce
4
- data.tar.gz: 5add4cd9d2607a90522b14c04a8b9d60a27bcad30c0f56212aba5df6c5036ac9
3
+ metadata.gz: bb5546419b47d6680b576f39d8c4b382c39d74f07ca43f6e782160122b6ba524
4
+ data.tar.gz: 3e563cd2db3efc765e491ece2fc37a784cb55ef05f6390eb23d1322a926be49e
5
5
  SHA512:
6
- metadata.gz: 859acc902ed173236fac760ce2edbbda4353bf7fb438ecdb3856bdba3076a464a34f542458abc5b1fb2ec157b4f3ab007e0eb67123c551f1b6abedc6f810d978
7
- data.tar.gz: 36b9c267842f00e2d6181d2523b84b776528b7d754fb4eb378fbdb4fc8d33fbb43ac7b9faf5ff613cf34c4687cc2eab74eaff04601fa72196efe3787d622dc7d
6
+ metadata.gz: 712f24a4dc3427cf37126681ba50d53f0aefc9f839920283f611ef22e923f65f53f09fc83ca66cb45930536ca3c13d630dc7b0c5a5f40c6fc6f91f82bf123c3c
7
+ data.tar.gz: e97e52fc2aa1ad3cc548d2f561df97678e00c0e7e617c08afdbb9c6537709fc2743a07f26679cc8007124348cb32f1e8481d6417b7d3e28d247b504fcc951805
data/README.md CHANGED
@@ -8,20 +8,26 @@ 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
15
  cfg.write_timeout = 0.25 # seconds to write a single data junk
14
16
  cfg.read_timeout = 0.5 # seconds to read some bytes
15
- cfg.ssl_params = {ssl_version: :TLSv1_2} # use TLS 1.2
17
+ cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
16
18
  end
17
19
 
18
20
  # the following request sequence is not allowed to last longer than 2 seconds:
19
21
  # 1 second to connect (incl. SSL handshake etc.)
20
22
  # + 0.25 seconds to write data
21
23
  # + 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
24
+
25
+ TCPClient.open('www.google.com:443') do |client|
26
+ # simple HTTP get request
27
+ pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
28
+
29
+ # read "HTTP/1.1 " + 3 byte HTTP status code
30
+ pp client.read(12)
25
31
  end
26
32
  ```
27
33
 
@@ -47,7 +53,7 @@ To install the gem globally use:
47
53
  $ gem install tcp-client
48
54
  ```
49
55
 
50
- After that you need only a single line of code in your project code to have all tools on board:
56
+ After that you need only a single line of code in your project to have all tools on board:
51
57
 
52
58
  ```ruby
53
59
  require 'tcp-client'
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
@@ -14,22 +15,26 @@ class TCPClient
14
15
  end
15
16
 
16
17
  class NotConnected < SocketError
17
- def self.raise!(which)
18
- raise(self, format('client not connected - %s', which), caller(1))
18
+ def self.raise!(reason)
19
+ raise(self, "client not connected - #{reason}", caller(1))
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)
28
- return yield(client) if block_given?
29
- client, ret = nil, client
30
- ret
35
+ block_given? ? yield(client) : client
31
36
  ensure
32
- client.close if client
37
+ client&.close if block_given?
33
38
  end
34
39
 
35
40
  attr_reader :address
@@ -46,8 +51,10 @@ class TCPClient
46
51
  close
47
52
  NoOpenSSL.raise! if configuration.ssl? && !defined?(SSLSocket)
48
53
  @address = Address.new(addr)
49
- @socket = TCPSocket.new(@address, configuration, Timeout)
50
- @socket = SSLSocket.new(@socket, @address, configuration, Timeout) if configuration.ssl?
54
+ @socket = TCPSocket.new(@address, configuration, ConnectTimeoutError)
55
+ configuration.ssl? &&
56
+ @socket =
57
+ SSLSocket.new(@socket, @address, configuration, ConnectTimeoutError)
51
58
  @write_timeout = configuration.write_timeout
52
59
  @read_timeout = configuration.read_timeout
53
60
  self
@@ -55,7 +62,7 @@ class TCPClient
55
62
 
56
63
  def close
57
64
  socket, @socket = @socket, nil
58
- socket.close if socket
65
+ socket&.close
59
66
  self
60
67
  rescue IOError
61
68
  self
@@ -66,11 +73,13 @@ class TCPClient
66
73
  end
67
74
 
68
75
  def read(nbytes, timeout: @read_timeout)
69
- closed? ? NotConnected.raise!(self) : @socket.read(nbytes, timeout: timeout, exception: Timeout)
76
+ NotConnected.raise!(self) if closed?
77
+ @socket.read(nbytes, timeout: timeout, exception: ReadTimeoutError)
70
78
  end
71
79
 
72
80
  def write(*msg, timeout: @write_timeout)
73
- closed? ? NotConnected.raise!(self) : @socket.write(*msg, timeout: timeout, exception: Timeout)
81
+ NotConnected.raise!(self) if closed?
82
+ @socket.write(*msg, timeout: timeout, exception: WriteTimeoutError)
74
83
  end
75
84
 
76
85
  def flush
@@ -4,7 +4,7 @@ require 'socket'
4
4
 
5
5
  class TCPClient
6
6
  class Address
7
- attr_reader :to_s, :hostname, :addrinfo
7
+ attr_reader :hostname, :addrinfo
8
8
 
9
9
  def initialize(addr)
10
10
  case addr
@@ -20,29 +20,31 @@ class TCPClient
20
20
  @addrinfo.freeze
21
21
  end
22
22
 
23
- private
24
-
25
- def init_from_string(str)
26
- @hostname, port = from_string(str.to_s)
27
- return init_from_addrinfo(Addrinfo.tcp(nil, port)) unless @hostname
28
- @addrinfo = Addrinfo.tcp(@hostname, port)
29
- @to_s = @hostname.index(':') ? "[#{@hostname}]:#{port}" : "#{@hostname}:#{port}"
23
+ def to_s
24
+ return "[#{@hostname}]:#{@addrinfo.ip_port}" if @hostname.index(':') # IP6
25
+ "#{@hostname}:#{@addrinfo.ip_port}"
30
26
  end
31
27
 
28
+ private
29
+
32
30
  def init_from_selfclass(address)
33
- @to_s = address.to_s
34
31
  @hostname = address.hostname
35
32
  @addrinfo = address.addrinfo
36
33
  end
37
34
 
38
35
  def init_from_addrinfo(addrinfo)
39
- @hostname, port = addrinfo.getnameinfo(Socket::NI_NUMERICSERV)
40
- @to_s = "#{@hostname}:#{port}"
36
+ @hostname, _port = addrinfo.getnameinfo(Socket::NI_NUMERICSERV)
41
37
  @addrinfo = addrinfo
42
38
  end
43
39
 
40
+ def init_from_string(str)
41
+ @hostname, port = from_string(str.to_s)
42
+ return init_from_addrinfo(Addrinfo.tcp(nil, port)) unless @hostname
43
+ @addrinfo = Addrinfo.tcp(@hostname, port)
44
+ end
45
+
44
46
  def from_string(str)
45
- return [nil, str.to_i] unless idx = str.rindex(':')
47
+ return nil, str.to_i unless idx = str.rindex(':')
46
48
  name = str[0, idx]
47
49
  name = name[1, name.size - 2] if name[0] == '[' && name[-1] == ']'
48
50
  [name, str[idx + 1, str.size - idx].to_i]
@@ -1,7 +1,7 @@
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
@@ -9,9 +9,10 @@ class TCPClient
9
9
  attr_reader :buffered, :keep_alive, :reverse_lookup
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?
@@ -67,8 +68,14 @@ class TCPClient
67
68
 
68
69
  private
69
70
 
71
+ def set(attribute, value)
72
+ public_send("#{attribute}=", value)
73
+ rescue NoMethodError
74
+ raise(ArgumentError, "unknown attribute - #{attribute}")
75
+ end
76
+
70
77
  def seconds(value)
71
- value && value > 0 ? value : nil
78
+ value&.positive? ? value : nil
72
79
  end
73
80
  end
74
81
  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
@@ -3,31 +3,39 @@ IOTimeoutError = Class.new(IOError) unless defined?(IOTimeoutError)
3
3
  module IOTimeoutMixin
4
4
  def self.included(mod)
5
5
  im = mod.instance_methods
6
- mod.include(im.index(:wait_writable) && im.index(:wait_readable) ? WithDeadlineMethods : WidthDeadlineIO)
6
+ if im.index(:wait_writable) && im.index(:wait_readable)
7
+ mod.include(DeadlineMethods)
8
+ else
9
+ mod.include(DeadlineIO)
10
+ end
7
11
  end
8
12
 
9
13
  def read(nbytes, timeout: nil, exception: IOTimeoutError)
10
14
  timeout = timeout.to_f
11
- 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
12
16
  deadline = Time.now + timeout
13
17
  read_all(nbytes) do |junk_size|
14
- with_deadline(deadline, exception){ read_nonblock(junk_size, exception: false) }
18
+ with_deadline(deadline, exception) do
19
+ read_nonblock(junk_size, exception: false)
20
+ end
15
21
  end
16
22
  end
17
23
 
18
24
  def write(*msgs, timeout: nil, exception: IOTimeoutError)
19
25
  timeout = timeout.to_f
20
- return write_all(msgs.join){ |junk| super(junk) } if timeout <= 0
26
+ return write_all(msgs.join.b) { |junk| super(junk) } if timeout <= 0
21
27
  deadline = Time.now + timeout
22
- write_all(msgs.join) do |junk|
23
- with_deadline(deadline, exception){ write_nonblock(junk, exception: false) }
28
+ write_all(msgs.join.b) do |junk|
29
+ with_deadline(deadline, exception) do
30
+ write_nonblock(junk, exception: false)
31
+ end
24
32
  end
25
33
  end
26
34
 
27
35
  private
28
36
 
29
37
  def read_all(nbytes)
30
- return '' if 0 == nbytes
38
+ return '' if nbytes.zero?
31
39
  result = ''
32
40
  loop do
33
41
  unless read = yield(nbytes - result.bytesize)
@@ -40,7 +48,7 @@ module IOTimeoutMixin
40
48
  end
41
49
 
42
50
  def write_all(data)
43
- return 0 if 0 == (size = data.bytesize)
51
+ return 0 if (size = data.bytesize).zero?
44
52
  result = 0
45
53
  loop do
46
54
  written = yield(data)
@@ -50,18 +58,18 @@ module IOTimeoutMixin
50
58
  end
51
59
  end
52
60
 
53
- module WithDeadlineMethods
61
+ module DeadlineMethods
54
62
  private
55
63
 
56
64
  def with_deadline(deadline, exclass)
57
65
  loop do
58
66
  case ret = yield
59
67
  when :wait_writable
60
- remaining_time = deadline - Time.now
61
- raise(exclass) if remaining_time <= 0 || wait_writable(remaining_time).nil?
68
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
69
+ raise(exclass) if wait_writable(remaining_time).nil?
62
70
  when :wait_readable
63
- remaining_time = deadline - Time.now
64
- raise(exclass) if remaining_time <= 0 || wait_readable(remaining_time).nil?
71
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
72
+ raise(exclass) if wait_readable(remaining_time).nil?
65
73
  else
66
74
  return ret
67
75
  end
@@ -69,18 +77,18 @@ module IOTimeoutMixin
69
77
  end
70
78
  end
71
79
 
72
- module WidthDeadlineIO
80
+ module DeadlineIO
73
81
  private
74
82
 
75
83
  def with_deadline(deadline, exclass)
76
84
  loop do
77
85
  case ret = yield
78
86
  when :wait_writable
79
- remaining_time = deadline - Time.now
80
- raise(exclass) if remaining_time <= 0 || ::IO.select(nil, [self], nil, remaining_time).nil?
87
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
88
+ raise(exclass) if ::IO.select(nil, [self], nil, remaining_time).nil?
81
89
  when :wait_readable
82
- remaining_time = deadline - Time.now
83
- raise(exclass) if remaining_time <= 0 || ::IO.select([self], nil, nil, remaining_time).nil?
90
+ raise(exclass) if (remaining_time = deadline - Time.now) <= 0
91
+ raise(exclass) if ::IO.select([self], nil, nil, remaining_time).nil?
84
92
  else
85
93
  return ret
86
94
  end
@@ -88,5 +96,5 @@ module IOTimeoutMixin
88
96
  end
89
97
  end
90
98
 
91
- private_constant :WithDeadlineMethods, :WidthDeadlineIO
99
+ private_constant(:DeadlineMethods, :DeadlineIO)
92
100
  end
@@ -31,10 +31,16 @@ class TCPClient
31
31
 
32
32
  def connect_to(address, check, timeout, exception)
33
33
  self.hostname = address.hostname
34
- timeout ? with_deadline(Time.now + timeout, exception){ connect_nonblock(exception: false) } : connect
34
+ if timeout
35
+ with_deadline(Time.now + timeout, exception) do
36
+ connect_nonblock(exception: false)
37
+ end
38
+ else
39
+ connect
40
+ end
35
41
  post_connection_check(address.hostname) if check
36
42
  end
37
43
  end
38
44
 
39
- private_constant :SSLSocket
45
+ private_constant(:SSLSocket)
40
46
  end
@@ -14,9 +14,15 @@ class TCPClient
14
14
  private
15
15
 
16
16
  def connect_to(address, timeout, exception)
17
- addr = ::Socket.pack_sockaddr_in(address.addrinfo.ip_port, address.addrinfo.ip_address)
17
+ addr =
18
+ ::Socket.pack_sockaddr_in(
19
+ address.addrinfo.ip_port,
20
+ address.addrinfo.ip_address
21
+ )
18
22
  return connect(addr) unless timeout
19
- with_deadline(Time.now + timeout, exception){ connect_nonblock(addr, exception: false) }
23
+ with_deadline(Time.now + timeout, exception) do
24
+ connect_nonblock(addr, exception: false)
25
+ end
20
26
  end
21
27
 
22
28
  def configure(configuration)
@@ -29,5 +35,5 @@ class TCPClient
29
35
  end
30
36
  end
31
37
 
32
- private_constant :TCPSocket
38
+ private_constant(:TCPSocket)
33
39
  end
@@ -1,3 +1,3 @@
1
1
  class TCPClient
2
- VERSION = '0.0.6'.freeze
2
+ VERSION = '0.1.0'.freeze
3
3
  end
data/rakefile.rb CHANGED
@@ -1,5 +1,12 @@
1
- require 'bundler/gem_tasks'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/clean'
2
4
  require 'rake/testtask'
5
+ require 'bundler/gem_tasks'
6
+
7
+ STDOUT.sync = STDERR.sync = true
8
+
9
+ CLOBBER << 'prj'
3
10
 
4
11
  Rake::TestTask.new(:test) do |t|
5
12
  t.ruby_opts = %w[-w]
@@ -8,5 +15,5 @@ Rake::TestTask.new(:test) do |t|
8
15
  end
9
16
 
10
17
  task :default do
11
- exec('rake -T')
18
+ exec("#{$PROGRAM_NAME} --tasks")
12
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,21 @@
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
5
  cfg.write_timeout = 0.25 # seconds to write a single data junk
6
6
  cfg.read_timeout = 0.5 # seconds to read some bytes
7
- cfg.ssl_params = {ssl_version: :TLSv1_2} # use TLS 1.2
7
+ cfg.ssl_params = { ssl_version: :TLSv1_2 } # use TLS 1.2
8
8
  end
9
9
 
10
10
  # the following request sequence is not allowed to last longer than 2 seconds:
11
11
  # 1 second to connect (incl. SSL handshake etc.)
12
12
  # + 0.25 seconds to write data
13
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
14
+
15
+ TCPClient.open('www.google.com:443') 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)
17
21
  end
data/tcp-client.gemspec CHANGED
@@ -1,31 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require File.expand_path('../lib/tcp-client/version', __FILE__)
3
+ require_relative './lib/tcp-client/version'
4
4
 
5
5
  GemSpec = Gem::Specification.new do |spec|
6
6
  spec.name = 'tcp-client'
7
7
  spec.version = TCPClient::VERSION
8
8
  spec.summary = 'A TCP client implementation with working timeout support.'
9
- spec.description = <<~EOS
9
+ spec.description = <<~DESCRIPTION
10
10
  This gem implements a TCP client with (optional) SSL support. The
11
11
  motivation of this project is the need to have a _really working_
12
12
  easy to use client which can handle time limits correctly. Unlike
13
13
  other implementations this client respects given/configurable time
14
14
  limits for each method (`connect`, `read`, `write`).
15
- EOS
15
+ DESCRIPTION
16
16
  spec.author = 'Mike Blumtritt'
17
- spec.email = 'mike.blumtritt@invision.de'
17
+ spec.email = 'mike.blumtritt@pm.me'
18
18
  spec.homepage = 'https://github.com/mblumtritt/tcp-client'
19
- spec.metadata = {'issue_tracker' => 'https://github.com/mblumtritt/tcp-client/issues'}
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
+ }
20
23
  spec.rubyforge_project = spec.name
21
24
 
22
25
  spec.add_development_dependency 'bundler'
23
- spec.add_development_dependency 'rake'
24
26
  spec.add_development_dependency 'minitest'
27
+ spec.add_development_dependency 'rake'
25
28
 
26
29
  spec.platform = Gem::Platform::RUBY
27
- spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
28
30
  spec.required_ruby_version = '>= 2.5.0'
31
+ spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
29
32
 
30
33
  spec.require_paths = %w[lib]
31
34
 
@@ -33,6 +36,5 @@ GemSpec = Gem::Specification.new do |spec|
33
36
  spec.test_files = all_files.grep(%r{^(spec|test)/})
34
37
  spec.files = all_files - spec.test_files
35
38
 
36
- spec.has_rdoc = false
37
39
  spec.extra_rdoc_files = %w[README.md]
38
40
  end
@@ -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)
@@ -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)
@@ -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,23 +1,27 @@
1
1
  require_relative 'test_helper'
2
2
 
3
- class TCPClientTest < Test
3
+ class TCPClientTest < MiniTest::Test
4
+ parallelize_me!
5
+
6
+ attr_reader :config
7
+
8
+ def setup
9
+ @config = TCPClient::Configuration.create(buffered: false)
10
+ end
11
+
4
12
  def test_defaults
5
13
  subject = TCPClient.new
6
14
  assert(subject.closed?)
7
15
  assert_equal('', subject.to_s)
8
16
  assert_nil(subject.address)
9
17
  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
18
+ assert_raises(TCPClient::NotConnected) { subject.write('hello world!') }
19
+ assert_raises(TCPClient::NotConnected) { subject.read(42) }
16
20
  end
17
21
 
18
22
  def create_nonconnected_client
19
23
  client = TCPClient.new
20
- client.connect('', TCPClient::Configuration.new)
24
+ client.connect('', config)
21
25
  rescue Errno::EADDRNOTAVAIL
22
26
  ensure
23
27
  return client
@@ -32,17 +36,12 @@ class TCPClientTest < Test
32
36
  assert_equal('localhost', subject.address.hostname)
33
37
  assert_instance_of(Addrinfo, subject.address.addrinfo)
34
38
  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
39
+ assert_raises(TCPClient::NotConnected) { subject.write('hello world!') }
40
+ assert_raises(TCPClient::NotConnected) { subject.read(42) }
41
41
  end
42
42
 
43
43
  def test_connected_state
44
- server = TCPServer.new(1234)
45
- TCPClient.open('localhost:1234', TCPClient::Configuration.new) do |subject|
44
+ TCPClient.open('localhost:1234') do |subject|
46
45
  refute(subject.closed?)
47
46
  assert_equal('localhost:1234', subject.to_s)
48
47
  refute_nil(subject.address)
@@ -56,55 +55,65 @@ class TCPClientTest < Test
56
55
  assert(subject.closed?)
57
56
  assert_same(address_when_opened, subject.address)
58
57
  end
59
- ensure
60
- server.close if server
61
58
  end
62
59
 
63
- def check_read_write_timeout(addr, timeout)
64
- TCPClient.open(addr) do |subject|
60
+ def check_read_timeout(timeout)
61
+ TCPClient.open('localhost:1234', config) do |subject|
65
62
  refute(subject.closed?)
66
63
  start_time = nil
67
- assert_raises(TCPClient::Timeout) do
64
+ assert_raises(TCPClient::ReadTimeoutError) do
68
65
  start_time = Time.now
69
- # we need to send 1MB to avoid any TCP stack buffering
70
- subject.write('?' * (1024 * 1024), timeout: timeout)
66
+ subject.read(42, timeout: timeout)
71
67
  end
72
68
  assert_in_delta(timeout, Time.now - start_time, 0.02)
73
- assert_raises(TCPClient::Timeout) do
69
+ end
70
+ end
71
+
72
+ def test_read_timeout
73
+ check_read_timeout(0.5)
74
+ check_read_timeout(1)
75
+ check_read_timeout(1.5)
76
+ end
77
+
78
+ def check_write_timeout(timeout)
79
+ TCPClient.open('localhost:1234', config) do |subject|
80
+ refute(subject.closed?)
81
+ start_time = nil
82
+ assert_raises(TCPClient::WriteTimeoutError) do
74
83
  start_time = Time.now
75
- subject.read(42, timeout: timeout)
84
+
85
+ # send 1MB to avoid any TCP stack buffering
86
+ args = Array.new(2024, '?' * 1024)
87
+ subject.write(*args, timeout: timeout)
76
88
  end
77
89
  assert_in_delta(timeout, Time.now - start_time, 0.02)
78
90
  end
79
91
  end
80
92
 
81
- def test_read_write_timeout
82
- server = TCPServer.new(1235) # this server will never read/write client data
83
- [0.5, 1, 1.5].each do |timeout|
84
- check_read_write_timeout(':1235', timeout)
85
- end
86
- ensure
87
- server.close if server
93
+ def test_write_timeout
94
+ check_write_timeout(0.1)
95
+ check_write_timeout(0.25)
88
96
  end
89
97
 
90
- def check_connect_timeout(addr, config, timeout)
98
+ def check_connect_timeout(ssl_config)
91
99
  start_time = nil
92
- assert_raises(TCPClient::Timeout) do
100
+ assert_raises(TCPClient::ConnectTimeoutError) do
93
101
  start_time = Time.now
94
- TCPClient.new.connect(addr, config)
102
+ TCPClient.new.connect('localhost:1234', ssl_config)
95
103
  end
96
- assert_in_delta(timeout, Time.now - start_time, 0.02)
104
+ assert_in_delta(ssl_config.connect_timeout, Time.now - start_time, 0.02)
97
105
  end
98
106
 
99
107
  def test_connect_ssl_timeout
100
- server = TCPServer.new(1236)
101
- config = TCPClient::Configuration.new
102
- config.ssl = true
103
- [0.5, 1, 1.5].each do |timeout|
104
- config.timeout = timeout
105
- check_connect_timeout('localhost:1236', config, timeout)
106
- end
107
- ensure
108
- server.close if server
108
+ ssl_config = TCPClient::Configuration.new(ssl: true)
109
+
110
+ ssl_config.connect_timeout = 0.5
111
+ check_connect_timeout(ssl_config)
112
+
113
+ ssl_config.connect_timeout = 1
114
+ check_connect_timeout(ssl_config)
115
+
116
+ ssl_config.connect_timeout = 1.5
117
+ check_connect_timeout(ssl_config)
109
118
  end
110
119
  end
data/test/test_helper.rb CHANGED
@@ -4,6 +4,6 @@ require_relative '../lib/tcp-client'
4
4
 
5
5
  $stdout.sync = $stderr.sync = true
6
6
 
7
- class Test < Minitest::Test
8
- parallelize_me!
9
- end
7
+ # this pseudo-server never reads or writes anything
8
+ DummyServer = TCPServer.new('localhost', 1234)
9
+ 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.6
4
+ version: 0.1.0
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: 2018-02-21 00:00:00.000000000 Z
11
+ date: 2021-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: minitest
42
+ name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -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@invision.de
61
+ email: mike.blumtritt@pm.me
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,14 +82,16 @@ 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
87
89
  homepage: https://github.com/mblumtritt/tcp-client
88
90
  licenses: []
89
91
  metadata:
90
- issue_tracker: https://github.com/mblumtritt/tcp-client/issues
91
- post_install_message:
92
+ source_code_uri: https://github.com/mblumtritt/tcp-client
93
+ bug_tracker_uri: https://github.com/mblumtritt/tcp-client/issues
94
+ post_install_message:
92
95
  rdoc_options: []
93
96
  require_paths:
94
97
  - lib
@@ -103,14 +106,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
106
  - !ruby/object:Gem::Version
104
107
  version: 1.3.6
105
108
  requirements: []
106
- rubyforge_project: tcp-client
107
- rubygems_version: 2.7.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