tcp-client 0.0.6 → 0.1.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: 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