tcp-client 0.0.2 → 0.0.4

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: c78407e818a50dbd68dcee0fe5b5cebcb24c05e8067a85e58d041708d7f17d6d
4
- data.tar.gz: 02d404e555b0aeab3536654171563a90c33d6f38a9d00a4931e44915d099667f
3
+ metadata.gz: 1100d0c6684061225cff8107b369fe38d56454f3e5bb4c7efd4a7e2c07adc491
4
+ data.tar.gz: 584cda11bc5398b9dd1574ea1e459a4ec963cf73195a5a200bdd520aa9725ece
5
5
  SHA512:
6
- metadata.gz: 564d3dd03cf6a63997e58cc2cee355f285682af61efda74caed413bfa09b672331de4cd29f1a8434403d128f45175ffbc9ca21818c6031e3a4fb5d15e19476f3
7
- data.tar.gz: b563240e1fed9791c4f0714e1fe9d5711441488b482e17e2a37de2e6e0828bf004d15a24db69b7b4ffc1ef2bdc79c936f3de87a4213f9ae4832c1985aa820988
6
+ metadata.gz: eafb90f60c331022b7115d291ede9334a6963842ba453329dff11d2ae9c208c5cc8acf2afaa9f4b377d20a3bddb6c60ab7baeccc0bee77a3b17665a2df18587f
7
+ data.tar.gz: 7d174d7172e9d1da102da679741f655e8bda71f888e3b68868d1ec993cd9f82ba8ed6f185be58483296c77868eefce4c2897b79e2fcb1fe71b52c6774b05fb6a
data/.gitignore CHANGED
@@ -1,4 +1,4 @@
1
- _local/
1
+ .local/
2
2
  tmp/
3
3
  pkg/
4
4
  gems.locked
data/README.md CHANGED
@@ -11,14 +11,14 @@ This gem implements a TCP client with (optional) SSL support. The motivation of
11
11
  configuration = TCPClient::Configuration.create do |cfg|
12
12
  cfg.connect_timeout = 1 # second to connect the server
13
13
  cfg.write_timeout = 0.25 # seconds to write a single data junk
14
- cfg.read_timeout = 0.25 # seconds to read some bytes
15
- cfg.ssl_params = {} # use SSL, but without any specific parameters
14
+ cfg.read_timeout = 0.5 # seconds to read some bytes
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 1.5 seconds:
18
+ # the following request sequence is not allowed to last longer than 2 seconds:
19
19
  # 1 second to connect (incl. SSL handshake etc.)
20
20
  # + 0.25 seconds to write data
21
- # + 0.25 seconds to read
21
+ # + 0.5 seconds to read
22
22
  # a response
23
23
  TCPClient.open('www.google.com:443', configuration) do |client|
24
24
  pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
@@ -19,6 +19,8 @@ class TCPClient
19
19
  end
20
20
  end
21
21
 
22
+ Timeout = Class.new(IOError)
23
+
22
24
  def self.open(addr, configuration = Configuration.new)
23
25
  addr = Address.new(addr)
24
26
  client = new
@@ -44,8 +46,8 @@ class TCPClient
44
46
  close
45
47
  NoOpenSSL.raise! if configuration.ssl? && !defined?(SSLSocket)
46
48
  @address = Address.new(addr)
47
- @socket = TCPSocket.new(@address, configuration)
48
- @socket = SSLSocket.new(@socket, @address, configuration) if configuration.ssl?
49
+ @socket = TCPSocket.new(@address, configuration, Timeout)
50
+ @socket = SSLSocket.new(@socket, @address, configuration, Timeout) if configuration.ssl?
49
51
  @write_timeout = configuration.write_timeout
50
52
  @read_timeout = configuration.read_timeout
51
53
  self
@@ -64,11 +66,10 @@ class TCPClient
64
66
  end
65
67
 
66
68
  def read(nbytes, timeout: @read_timeout)
67
- closed? ? NotConnected.raise!(self) : @socket.read(nbytes, timeout: timeout)
69
+ closed? ? NotConnected.raise!(self) : @socket.read(nbytes, timeout: timeout, exception: Timeout)
68
70
  end
69
71
 
70
- def write(*args, timeout: @write_timeout)
71
- closed? ? NotConnected.raise!(self) : @socket.write(*args, timeout: timeout)
72
+ def write(*msg, timeout: @write_timeout)
73
+ closed? ? NotConnected.raise!(self) : @socket.write(*msg, timeout: timeout, exception: Timeout)
72
74
  end
73
75
  end
74
-
@@ -13,7 +13,7 @@ class TCPClient
13
13
  when Integer
14
14
  init_from_addrinfo(Addrinfo.tcp(nil, addr))
15
15
  when Addrinfo
16
- init_from_addrinfo(add)
16
+ init_from_addrinfo(addr)
17
17
  else
18
18
  init_from_string(addr)
19
19
  end
@@ -24,8 +24,9 @@ class TCPClient
24
24
 
25
25
  def init_from_string(str)
26
26
  @hostname, port = from_string(str.to_s)
27
+ return init_from_addrinfo(Addrinfo.tcp(nil, port)) unless @hostname
27
28
  @addrinfo = Addrinfo.tcp(@hostname, port)
28
- @to_s = "#{@hostname}:#{port}"
29
+ @to_s = @hostname.index(':') ? "[#{@hostname}]:#{port}" : "#{@hostname}:#{port}"
29
30
  end
30
31
 
31
32
  def init_from_selfclass(address)
@@ -6,21 +6,21 @@ module IOTimeoutMixin
6
6
  mod.include(im.index(:wait_writable) && im.index(:wait_readable) ? WithDeadlineMethods : WidthDeadlineIO)
7
7
  end
8
8
 
9
- def read(nbytes, timeout: nil)
9
+ def read(nbytes, timeout: nil, exception: IOTimeoutError)
10
10
  timeout = timeout.to_f
11
11
  return read_all(nbytes){ |junk_size| super(junk_size) } if timeout <= 0
12
12
  deadline = Time.now + timeout
13
13
  read_all(nbytes) do |junk_size|
14
- with_deadline(deadline){ read_nonblock(junk_size, exception: false) }
14
+ with_deadline(deadline, exception){ read_nonblock(junk_size, exception: false) }
15
15
  end
16
16
  end
17
17
 
18
- def write(*args, timeout: nil)
18
+ def write(*msgs, timeout: nil, exception: IOTimeoutError)
19
19
  timeout = timeout.to_f
20
- return write_all(args.join){ |junk| super(junk) } if timeout <= 0
20
+ return write_all(msgs.join){ |junk| super(junk) } if timeout <= 0
21
21
  deadline = Time.now + timeout
22
- write_all(args.join) do |junk|
23
- with_deadline(deadline){ write_nonblock(junk, exception: false) }
22
+ write_all(msgs.join) do |junk|
23
+ with_deadline(deadline, exception){ write_nonblock(junk, exception: false) }
24
24
  end
25
25
  end
26
26
 
@@ -53,15 +53,15 @@ module IOTimeoutMixin
53
53
  module WithDeadlineMethods
54
54
  private
55
55
 
56
- def with_deadline(deadline)
56
+ def with_deadline(deadline, exclass)
57
57
  loop do
58
58
  case ret = yield
59
59
  when :wait_writable
60
60
  remaining_time = deadline - Time.now
61
- raise(IOTimeoutError) if remaining_time <= 0 || wait_writable(remaining_time).nil?
61
+ raise(exclass) if remaining_time <= 0 || wait_writable(remaining_time).nil?
62
62
  when :wait_readable
63
63
  remaining_time = deadline - Time.now
64
- raise(IOTimeoutError) if remaining_time <= 0 || wait_readable(remaining_time).nil?
64
+ raise(exclass) if remaining_time <= 0 || wait_readable(remaining_time).nil?
65
65
  else
66
66
  return ret
67
67
  end
@@ -72,15 +72,15 @@ module IOTimeoutMixin
72
72
  module WidthDeadlineIO
73
73
  private
74
74
 
75
- def with_deadline(deadline)
75
+ def with_deadline(deadline, exclass)
76
76
  loop do
77
77
  case ret = yield
78
78
  when :wait_writable
79
79
  remaining_time = deadline - Time.now
80
- raise(IOTimeoutError) if remaining_time <= 0 || ::IO.select(nil, [self], nil, remaining_time).nil?
80
+ raise(exclass) if remaining_time <= 0 || ::IO.select(nil, [self], nil, remaining_time).nil?
81
81
  when :wait_readable
82
82
  remaining_time = deadline - Time.now
83
- raise(IOTimeoutError) if remaining_time <= 0 || ::IO.select([self], nil, nil, remaining_time).nil?
83
+ raise(exclass) if remaining_time <= 0 || ::IO.select([self], nil, nil, remaining_time).nil?
84
84
  else
85
85
  return ret
86
86
  end
@@ -10,10 +10,10 @@ class TCPClient
10
10
  class SSLSocket < ::OpenSSL::SSL::SSLSocket
11
11
  include IOTimeoutMixin
12
12
 
13
- def initialize(socket, address, configuration)
13
+ def initialize(socket, address, configuration, exception)
14
14
  ssl_params = Hash[configuration.ssl_params]
15
15
  super(socket, create_context(ssl_params))
16
- connect_to(address, configuration.connect_timeout)
16
+ connect_to(address, configuration.connect_timeout, exception)
17
17
  end
18
18
 
19
19
  private
@@ -24,9 +24,9 @@ class TCPClient
24
24
  ctx
25
25
  end
26
26
 
27
- def connect_to(address, timeout)
27
+ def connect_to(address, timeout, exception)
28
28
  self.hostname = address.hostname
29
- timeout ? with_deadline(Time.now + timeout){ connect_nonblock(exception: false) } : connect
29
+ timeout ? with_deadline(Time.now + timeout, exception){ connect_nonblock(exception: false) } : connect
30
30
  post_connection_check(address.hostname)
31
31
  end
32
32
  end
@@ -5,17 +5,18 @@ class TCPClient
5
5
  class TCPSocket < ::Socket
6
6
  include IOTimeoutMixin
7
7
 
8
- def initialize(address, configuration)
8
+ def initialize(address, configuration, exception)
9
9
  super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
10
10
  configure(configuration)
11
- connect_to(address, configuration.connect_timeout)
11
+ connect_to(address, configuration.connect_timeout, exception)
12
12
  end
13
13
 
14
14
  private
15
15
 
16
- def connect_to(address, timeout)
16
+ def connect_to(address, timeout, exception)
17
17
  addr = ::Socket.pack_sockaddr_in(address.addrinfo.ip_port, address.addrinfo.ip_address)
18
- timeout ? with_deadline(Time.now + timeout){ connect_nonblock(addr, exception: false) } : connect(addr)
18
+ return connect(addr) unless timeout
19
+ with_deadline(Time.now + timeout, exception){ connect_nonblock(addr, exception: false) }
19
20
  end
20
21
 
21
22
  def configure(configuration)
@@ -1,3 +1,3 @@
1
1
  class TCPClient
2
- VERSION = '0.0.2'.freeze
2
+ VERSION = '0.0.4'.freeze
3
3
  end
@@ -3,13 +3,13 @@ require_relative '../lib/tcp-client'
3
3
  configuration = TCPClient::Configuration.create do |cfg|
4
4
  cfg.connect_timeout = 0.5 # seconds to connect the server
5
5
  cfg.write_timeout = 0.25 # seconds to write a single data junk
6
- cfg.read_timeout = 0.25 # seconds to read some bytes
6
+ cfg.read_timeout = 0.5 # seconds to read some bytes
7
7
  end
8
8
 
9
- # the following request sequence is not allowed to last longer than 0.75 seconds:
9
+ # the following request sequence is not allowed to last longer than 1.25 seconds:
10
10
  # 0.5 seconds to connect
11
11
  # + 0.25 seconds to write data
12
- # + 0.25 seconds to read
12
+ # + 0.5 seconds to read
13
13
  # a response
14
14
  TCPClient.open('www.google.com:80', configuration) do |client|
15
15
  pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
@@ -3,14 +3,14 @@ require_relative '../lib/tcp-client'
3
3
  configuration = TCPClient::Configuration.create 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
- cfg.read_timeout = 0.25 # seconds to read some bytes
7
- cfg.ssl_params = {} # use SSL, but without any specific parameters
6
+ cfg.read_timeout = 0.5 # seconds to read some bytes
7
+ cfg.ssl_params = {ssl_version: :TLSv1_2} # use TLS 1.2
8
8
  end
9
9
 
10
- # the following request sequence is not allowed to last longer than 1.5 seconds:
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
- # + 0.25 seconds to read
13
+ # + 0.5 seconds to read
14
14
  # a response
15
15
  TCPClient.open('www.google.com:443', configuration) do |client|
16
16
  pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
@@ -25,7 +25,7 @@ GemSpec = Gem::Specification.new do |spec|
25
25
 
26
26
  spec.platform = Gem::Platform::RUBY
27
27
  spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
28
- spec.required_ruby_version = '>= 2.0.0'
28
+ spec.required_ruby_version = '>= 2.5.0'
29
29
 
30
30
  spec.require_paths = %w[lib]
31
31
 
@@ -0,0 +1,55 @@
1
+ require_relative '../test_helper'
2
+
3
+ class AddressTest < Test
4
+ def test_create_from_integer
5
+ subject = TCPClient::Address.new(42)
6
+ assert_equal('localhost:42', subject.to_s)
7
+ assert_equal('localhost', subject.hostname)
8
+ assert(subject.addrinfo.ip?)
9
+ assert(subject.addrinfo.ipv6?)
10
+ assert_same(42, subject.addrinfo.ip_port)
11
+ end
12
+
13
+ def test_create_from_addrinfo
14
+ addrinfo = Addrinfo.tcp('google.com', 42)
15
+ subject = TCPClient::Address.new(addrinfo)
16
+ assert_equal(addrinfo.getnameinfo[0], subject.hostname)
17
+ assert_equal(addrinfo, subject.addrinfo)
18
+ end
19
+
20
+ def test_create_from_str
21
+ subject = TCPClient::Address.new('localhost:42')
22
+ assert_equal('localhost:42', subject.to_s)
23
+ assert_equal('localhost', subject.hostname)
24
+ assert(subject.addrinfo.ip?)
25
+ assert(subject.addrinfo.ipv6?)
26
+ assert_same(42, subject.addrinfo.ip_port)
27
+ end
28
+
29
+ def test_create_from_str_short
30
+ subject = TCPClient::Address.new(':42')
31
+ assert_equal(':42', subject.to_s)
32
+ assert_empty(subject.hostname)
33
+ assert_same(42, subject.addrinfo.ip_port)
34
+ assert(subject.addrinfo.ip?)
35
+ assert(subject.addrinfo.ipv4?)
36
+ end
37
+
38
+ def test_create_from_str_ip6
39
+ subject = TCPClient::Address.new('[::1]:42')
40
+ assert_equal('[::1]:42', subject.to_s)
41
+ assert_equal('::1', subject.hostname)
42
+ assert_same(42, subject.addrinfo.ip_port)
43
+ assert(subject.addrinfo.ip?)
44
+ assert(subject.addrinfo.ipv6?)
45
+ end
46
+
47
+ def test_create_from_empty_str
48
+ subject = TCPClient::Address.new('')
49
+ assert_equal('localhost:0', subject.to_s)
50
+ assert_equal('localhost', subject.hostname)
51
+ assert_same(0, subject.addrinfo.ip_port)
52
+ assert(subject.addrinfo.ip?)
53
+ assert(subject.addrinfo.ipv6?)
54
+ end
55
+ end
@@ -26,10 +26,10 @@ class TCPClientTest < Test
26
26
  def test_failed_state
27
27
  subject = create_nonconnected_client
28
28
  assert(subject.closed?)
29
- assert_equal(':0', subject.to_s)
29
+ assert_equal('localhost:0', subject.to_s)
30
30
  refute_nil(subject.address)
31
- assert_equal(':0', subject.address.to_s)
32
- assert_nil(subject.address.hostname)
31
+ assert_equal('localhost:0', subject.address.to_s)
32
+ assert_equal('localhost', subject.address.hostname)
33
33
  assert_instance_of(Addrinfo, subject.address.addrinfo)
34
34
  assert_same(0, subject.address.addrinfo.ip_port)
35
35
  assert_raises(TCPClient::NotConnected) do
@@ -64,13 +64,13 @@ class TCPClientTest < Test
64
64
  TCPClient.open(addr) do |subject|
65
65
  refute(subject.closed?)
66
66
  start_time = nil
67
- assert_raises(IOTimeoutError) do
67
+ assert_raises(TCPClient::Timeout) do
68
68
  start_time = Time.now
69
69
  # we need to send 1MB to avoid any TCP stack buffering
70
70
  subject.write('?' * (1024 * 1024), timeout: timeout)
71
71
  end
72
72
  assert_in_delta(timeout, Time.now - start_time, 0.02)
73
- assert_raises(IOTimeoutError) do
73
+ assert_raises(TCPClient::Timeout) do
74
74
  start_time = Time.now
75
75
  subject.read(42, timeout: timeout)
76
76
  end
@@ -89,7 +89,7 @@ class TCPClientTest < Test
89
89
 
90
90
  def check_connect_timeout(addr, config, timeout)
91
91
  start_time = nil
92
- assert_raises(IOTimeoutError) do
92
+ assert_raises(TCPClient::Timeout) do
93
93
  start_time = Time.now
94
94
  TCPClient.new.connect(addr, config)
95
95
  end
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.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-17 00:00:00.000000000 Z
11
+ date: 2018-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -79,6 +79,7 @@ files:
79
79
  - sample/google.rb
80
80
  - sample/google_ssl.rb
81
81
  - tcp-client.gemspec
82
+ - test/tcp-client/address_test.rb
82
83
  - test/tcp-client/configuration_test.rb
83
84
  - test/tcp-client/version_test.rb
84
85
  - test/tcp_client_test.rb
@@ -95,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
96
  requirements:
96
97
  - - ">="
97
98
  - !ruby/object:Gem::Version
98
- version: 2.0.0
99
+ version: 2.5.0
99
100
  required_rubygems_version: !ruby/object:Gem::Requirement
100
101
  requirements:
101
102
  - - ">="
@@ -108,6 +109,7 @@ signing_key:
108
109
  specification_version: 4
109
110
  summary: A TCP client implementation with working timeout support.
110
111
  test_files:
112
+ - test/tcp-client/address_test.rb
111
113
  - test/tcp-client/configuration_test.rb
112
114
  - test/tcp-client/version_test.rb
113
115
  - test/tcp_client_test.rb