tcp-client 0.0.2 → 0.0.4

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