tcp-client 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c78407e818a50dbd68dcee0fe5b5cebcb24c05e8067a85e58d041708d7f17d6d
4
+ data.tar.gz: 02d404e555b0aeab3536654171563a90c33d6f38a9d00a4931e44915d099667f
5
+ SHA512:
6
+ metadata.gz: 564d3dd03cf6a63997e58cc2cee355f285682af61efda74caed413bfa09b672331de4cd29f1a8434403d128f45175ffbc9ca21818c6031e3a4fb5d15e19476f3
7
+ data.tar.gz: b563240e1fed9791c4f0714e1fe9d5711441488b482e17e2a37de2e6e0828bf004d15a24db69b7b4ffc1ef2bdc79c936f3de87a4213f9ae4832c1985aa820988
@@ -0,0 +1,4 @@
1
+ _local/
2
+ tmp/
3
+ pkg/
4
+ gems.locked
@@ -0,0 +1,55 @@
1
+ # TCPClient
2
+
3
+ A TCP client implementation with working timeout support.
4
+
5
+ ## Description
6
+ This gem implements a TCP client with (optional) SSL support. The motivation of this project is the need to have a _really working_ easy to use client which can handle time limits correctly. Unlike other implementations this client respects given/configurable time limits for each method (`connect`, `read`, `write`).
7
+
8
+ ## Sample
9
+
10
+ ```ruby
11
+ configuration = TCPClient::Configuration.create do |cfg|
12
+ cfg.connect_timeout = 1 # second to connect the server
13
+ cfg.write_timeout = 0.25 # seconds to write a single data junk
14
+ cfg.read_timeout = 0.25 # seconds to read some bytes
15
+ cfg.ssl_params = {} # use SSL, but without any specific parameters
16
+ end
17
+
18
+ # the following request sequence is not allowed to last longer than 1.5 seconds:
19
+ # 1 second to connect (incl. SSL handshake etc.)
20
+ # + 0.25 seconds to write data
21
+ # + 0.25 seconds to read
22
+ # a response
23
+ TCPClient.open('www.google.com:443', configuration) do |client|
24
+ pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
25
+ pp client.read(12) # "HTTP/1.1 " + 3 byte HTTP status code
26
+ end
27
+ ```
28
+
29
+ ### Installation
30
+
31
+ Use [Bundler](http://gembundler.com/) to use TCPClient in your own project:
32
+
33
+ Add to your `Gemfile`:
34
+
35
+ ```ruby
36
+ gem 'tcp-client'
37
+ ```
38
+
39
+ and install it by running Bundler:
40
+
41
+ ```bash
42
+ $ bundle
43
+ ```
44
+
45
+ To install the gem globally use:
46
+
47
+ ```bash
48
+ $ gem install tcp-client
49
+ ```
50
+
51
+ After that you need only a single line of code in your project code to have all tools on board:
52
+
53
+ ```ruby
54
+ require 'tcp-client'
55
+ ```
data/gems.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ source('https://rubygems.org'){ gemspec }
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tcp-client/address'
4
+ require_relative 'tcp-client/tcp_socket'
5
+ require_relative 'tcp-client/ssl_socket'
6
+ require_relative 'tcp-client/configuration'
7
+ require_relative 'tcp-client/version'
8
+
9
+ class TCPClient
10
+ class NoOpenSSL < RuntimeError
11
+ def self.raise!
12
+ raise(self, 'OpenSSL is not avail', caller(1))
13
+ end
14
+ end
15
+
16
+ class NotConnected < SocketError
17
+ def self.raise!(which)
18
+ raise(self, format('client not connected - %s', which), caller(1))
19
+ end
20
+ end
21
+
22
+ def self.open(addr, configuration = Configuration.new)
23
+ addr = Address.new(addr)
24
+ client = new
25
+ client.connect(addr, configuration)
26
+ return yield(client) if block_given?
27
+ client, ret = nil, client
28
+ ret
29
+ ensure
30
+ client.close if client
31
+ end
32
+
33
+ attr_reader :address
34
+
35
+ def initialize
36
+ @socket = @address = @write_timeout = @read_timeout = nil
37
+ end
38
+
39
+ def to_s
40
+ @address ? @address.to_s : ''
41
+ end
42
+
43
+ def connect(addr, configuration)
44
+ close
45
+ NoOpenSSL.raise! if configuration.ssl? && !defined?(SSLSocket)
46
+ @address = Address.new(addr)
47
+ @socket = TCPSocket.new(@address, configuration)
48
+ @socket = SSLSocket.new(@socket, @address, configuration) if configuration.ssl?
49
+ @write_timeout = configuration.write_timeout
50
+ @read_timeout = configuration.read_timeout
51
+ self
52
+ end
53
+
54
+ def close
55
+ socket, @socket = @socket, nil
56
+ socket.close if socket
57
+ self
58
+ rescue IOError
59
+ self
60
+ end
61
+
62
+ def closed?
63
+ @socket.nil? || @socket.closed?
64
+ end
65
+
66
+ def read(nbytes, timeout: @read_timeout)
67
+ closed? ? NotConnected.raise!(self) : @socket.read(nbytes, timeout: timeout)
68
+ end
69
+
70
+ def write(*args, timeout: @write_timeout)
71
+ closed? ? NotConnected.raise!(self) : @socket.write(*args, timeout: timeout)
72
+ end
73
+ end
74
+
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ class TCPClient
6
+ class Address
7
+ attr_reader :to_s, :hostname, :addrinfo
8
+
9
+ def initialize(addr)
10
+ case addr
11
+ when self.class
12
+ init_from_selfclass(addr)
13
+ when Integer
14
+ init_from_addrinfo(Addrinfo.tcp(nil, addr))
15
+ when Addrinfo
16
+ init_from_addrinfo(add)
17
+ else
18
+ init_from_string(addr)
19
+ end
20
+ @addrinfo.freeze
21
+ end
22
+
23
+ private
24
+
25
+ def init_from_string(str)
26
+ @hostname, port = from_string(str.to_s)
27
+ @addrinfo = Addrinfo.tcp(@hostname, port)
28
+ @to_s = "#{@hostname}:#{port}"
29
+ end
30
+
31
+ def init_from_selfclass(address)
32
+ @to_s = address.to_s
33
+ @hostname = address.hostname
34
+ @addrinfo = address.addrinfo
35
+ end
36
+
37
+ def init_from_addrinfo(addrinfo)
38
+ @hostname, port = addrinfo.getnameinfo(Socket::NI_NUMERICSERV)
39
+ @to_s = "#{@hostname}:#{port}"
40
+ @addrinfo = addrinfo
41
+ end
42
+
43
+ def from_string(str)
44
+ return [nil, str.to_i] unless idx = str.rindex(':')
45
+ name = str[0, idx]
46
+ name = name[1, name.size - 2] if name[0] == '[' && name[-1] == ']'
47
+ [name, str[idx + 1, str.size - idx].to_i]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,68 @@
1
+ class TCPClient
2
+ class Configuration
3
+ def self.create
4
+ ret = new
5
+ yield(ret) if block_given?
6
+ ret
7
+ end
8
+
9
+ attr_reader :buffered, :keep_alive, :reverse_lookup
10
+ attr_accessor :ssl_params
11
+
12
+ def initialize
13
+ @buffered = @keep_alive = @reverse_lookup = true
14
+ self.timeout = @ssl_params = nil
15
+ end
16
+
17
+ def ssl?
18
+ @ssl_params ? true : false
19
+ end
20
+
21
+ def buffered=(yn)
22
+ @buffered = yn ? true : false
23
+ end
24
+
25
+ def keep_alive=(yn)
26
+ @keep_alive = yn ? true : false
27
+ end
28
+
29
+ def reverse_lookup=(yn)
30
+ @reverse_lookup = yn ? true : false
31
+ end
32
+
33
+ def timeout=(seconds)
34
+ @timeout = seconds(seconds)
35
+ @connect_timeout = @write_timeout = @read_timeout = nil
36
+ end
37
+
38
+ def connect_timeout
39
+ @connect_timeout || @timeout
40
+ end
41
+
42
+ def connect_timeout=(seconds)
43
+ @connect_timeout = seconds(seconds)
44
+ end
45
+
46
+ def write_timeout
47
+ @write_timeout || @timeout
48
+ end
49
+
50
+ def write_timeout=(seconds)
51
+ @write_timeout = seconds(seconds)
52
+ end
53
+
54
+ def read_timeout
55
+ @read_timeout || @timeout
56
+ end
57
+
58
+ def read_timeout=(seconds)
59
+ @read_timeout = seconds(seconds)
60
+ end
61
+
62
+ private
63
+
64
+ def seconds(value)
65
+ value && value > 0 ? value : nil
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,92 @@
1
+ IOTimeoutError = Class.new(IOError) unless defined?(IOTimeoutError)
2
+
3
+ module IOTimeoutMixin
4
+ def self.included(mod)
5
+ im = mod.instance_methods
6
+ mod.include(im.index(:wait_writable) && im.index(:wait_readable) ? WithDeadlineMethods : WidthDeadlineIO)
7
+ end
8
+
9
+ def read(nbytes, timeout: nil)
10
+ timeout = timeout.to_f
11
+ return read_all(nbytes){ |junk_size| super(junk_size) } if timeout <= 0
12
+ deadline = Time.now + timeout
13
+ read_all(nbytes) do |junk_size|
14
+ with_deadline(deadline){ read_nonblock(junk_size, exception: false) }
15
+ end
16
+ end
17
+
18
+ def write(*args, timeout: nil)
19
+ timeout = timeout.to_f
20
+ return write_all(args.join){ |junk| super(junk) } if timeout <= 0
21
+ deadline = Time.now + timeout
22
+ write_all(args.join) do |junk|
23
+ with_deadline(deadline){ write_nonblock(junk, exception: false) }
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def read_all(nbytes)
30
+ return '' if nbytes == 0
31
+ result = ''
32
+ loop do
33
+ unless read = yield(nbytes - result.bytesize)
34
+ close
35
+ return result
36
+ end
37
+ result += read
38
+ return result if result.bytesize >= nbytes
39
+ end
40
+ end
41
+
42
+ def write_all(data)
43
+ return 0 if 0 == (size = data.bytesize)
44
+ result = 0
45
+ loop do
46
+ written = yield(data)
47
+ result += written
48
+ return result if result >= size
49
+ data = data.byteslice(written, data.bytesize - written)
50
+ end
51
+ end
52
+
53
+ module WithDeadlineMethods
54
+ private
55
+
56
+ def with_deadline(deadline)
57
+ loop do
58
+ case ret = yield
59
+ when :wait_writable
60
+ remaining_time = deadline - Time.now
61
+ raise(IOTimeoutError) if remaining_time <= 0 || wait_writable(remaining_time).nil?
62
+ when :wait_readable
63
+ remaining_time = deadline - Time.now
64
+ raise(IOTimeoutError) if remaining_time <= 0 || wait_readable(remaining_time).nil?
65
+ else
66
+ return ret
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ module WidthDeadlineIO
73
+ private
74
+
75
+ def with_deadline(deadline)
76
+ loop do
77
+ case ret = yield
78
+ when :wait_writable
79
+ remaining_time = deadline - Time.now
80
+ raise(IOTimeoutError) if remaining_time <= 0 || ::IO.select(nil, [self], nil, remaining_time).nil?
81
+ when :wait_readable
82
+ remaining_time = deadline - Time.now
83
+ raise(IOTimeoutError) if remaining_time <= 0 || ::IO.select([self], nil, nil, remaining_time).nil?
84
+ else
85
+ return ret
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ private_constant :WithDeadlineMethods, :WidthDeadlineIO
92
+ end
@@ -0,0 +1,35 @@
1
+ begin
2
+ require 'openssl'
3
+ rescue LoadError
4
+ return
5
+ end
6
+
7
+ require_relative 'mixin/io_timeout'
8
+
9
+ class TCPClient
10
+ class SSLSocket < ::OpenSSL::SSL::SSLSocket
11
+ include IOTimeoutMixin
12
+
13
+ def initialize(socket, address, configuration)
14
+ ssl_params = Hash[configuration.ssl_params]
15
+ super(socket, create_context(ssl_params))
16
+ connect_to(address, configuration.connect_timeout)
17
+ end
18
+
19
+ private
20
+
21
+ def create_context(ssl_params)
22
+ ctx = OpenSSL::SSL::SSLContext.new
23
+ ctx.set_params(ssl_params)
24
+ ctx
25
+ end
26
+
27
+ def connect_to(address, timeout)
28
+ self.hostname = address.hostname
29
+ timeout ? with_deadline(Time.now + timeout){ connect_nonblock(exception: false) } : connect
30
+ post_connection_check(address.hostname)
31
+ end
32
+ end
33
+
34
+ private_constant :SSLSocket
35
+ end
@@ -0,0 +1,32 @@
1
+ require 'socket'
2
+ require_relative 'mixin/io_timeout'
3
+
4
+ class TCPClient
5
+ class TCPSocket < ::Socket
6
+ include IOTimeoutMixin
7
+
8
+ def initialize(address, configuration)
9
+ super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM)
10
+ configure(configuration)
11
+ connect_to(address, configuration.connect_timeout)
12
+ end
13
+
14
+ private
15
+
16
+ def connect_to(address, timeout)
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)
19
+ end
20
+
21
+ def configure(configuration)
22
+ unless configuration.buffered
23
+ self.sync = true
24
+ setsockopt(:TCP, :NODELAY, 1)
25
+ end
26
+ setsockopt(:SOCKET, :KEEPALIVE, configuration.keep_alive ? 1 : 0)
27
+ self.do_not_reverse_lookup = configuration.reverse_lookup
28
+ end
29
+ end
30
+
31
+ private_constant :TCPSocket
32
+ end
@@ -0,0 +1,3 @@
1
+ class TCPClient
2
+ VERSION = '0.0.2'.freeze
3
+ end
@@ -0,0 +1 @@
1
+ require_relative 'tcp-client'
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.ruby_opts = %w[-w]
6
+ t.verbose = true
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default do
11
+ exec('rake -T')
12
+ end
@@ -0,0 +1,17 @@
1
+ require_relative '../lib/tcp-client'
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.25 # seconds to read some bytes
7
+ end
8
+
9
+ # the following request sequence is not allowed to last longer than 0.75 seconds:
10
+ # 0.5 seconds to connect
11
+ # + 0.25 seconds to write data
12
+ # + 0.25 seconds to read
13
+ # a response
14
+ TCPClient.open('www.google.com:80', 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
17
+ end
@@ -0,0 +1,18 @@
1
+ require_relative '../lib/tcp-client'
2
+
3
+ configuration = TCPClient::Configuration.create do |cfg|
4
+ cfg.connect_timeout = 1 # second to connect the server
5
+ cfg.write_timeout = 0.25 # seconds to write a single data junk
6
+ cfg.read_timeout = 0.25 # seconds to read some bytes
7
+ cfg.ssl_params = {} # use SSL, but without any specific parameters
8
+ end
9
+
10
+ # the following request sequence is not allowed to last longer than 1.5 seconds:
11
+ # 1 second to connect (incl. SSL handshake etc.)
12
+ # + 0.25 seconds to write data
13
+ # + 0.25 seconds to read
14
+ # a response
15
+ TCPClient.open('www.google.com:443', configuration) do |client|
16
+ pp client.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n") # simple HTTP get request
17
+ pp client.read(12) # "HTTP/1.1 " + 3 byte HTTP status code
18
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../lib/tcp-client/version', __FILE__)
4
+
5
+ GemSpec = Gem::Specification.new do |spec|
6
+ spec.name = 'tcp-client'
7
+ spec.version = TCPClient::VERSION
8
+ spec.summary = 'A TCP client implementation with working timeout support.'
9
+ spec.description = <<~EOS
10
+ This gem implements a TCP client with (optional) SSL support. The
11
+ motivation of this project is the need to have a _really working_
12
+ easy to use client which can handle time limits correctly. Unlike
13
+ other implementations this client respects given/configurable time
14
+ limits for each method (`connect`, `read`, `write`).
15
+ EOS
16
+ spec.author = 'Mike Blumtritt'
17
+ spec.email = 'mike.blumtritt@invision.de'
18
+ spec.homepage = 'https://github.com/mblumtritt/tcp-client'
19
+ spec.metadata = {'issue_tracker' => 'https://github.com/mblumtritt/tcp-client/issues'}
20
+ spec.rubyforge_project = spec.name
21
+
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'minitest'
25
+
26
+ spec.platform = Gem::Platform::RUBY
27
+ spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
28
+ spec.required_ruby_version = '>= 2.0.0'
29
+
30
+ spec.require_paths = %w[lib]
31
+
32
+ all_files = %x(git ls-files -z).split(0.chr)
33
+ spec.test_files = all_files.grep(%r{^(spec|test)/})
34
+ spec.files = all_files - spec.test_files
35
+
36
+ spec.has_rdoc = false
37
+ spec.extra_rdoc_files = %w[README.md]
38
+ end
@@ -0,0 +1,45 @@
1
+ require_relative '../test_helper'
2
+
3
+ class ConfigurationTest < Test
4
+ def test_defaults
5
+ subject = TCPClient::Configuration.new
6
+ assert(subject.buffered)
7
+ assert(subject.keep_alive)
8
+ assert(subject.reverse_lookup)
9
+ refute(subject.ssl?)
10
+ assert_nil(subject.connect_timeout)
11
+ assert_nil(subject.read_timeout)
12
+ assert_nil(subject.write_timeout)
13
+ end
14
+
15
+ 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_params = {}
20
+ end
21
+ refute(subject.buffered)
22
+ refute(subject.keep_alive)
23
+ refute(subject.reverse_lookup)
24
+ assert_same(42, subject.connect_timeout)
25
+ assert_same(42, subject.read_timeout)
26
+ assert_same(42, subject.write_timeout)
27
+ assert(subject.ssl?)
28
+ end
29
+
30
+ def test_timeout_overwrite
31
+ subject = TCPClient::Configuration.create do |cfg|
32
+ cfg.connect_timeout = 1
33
+ cfg.read_timeout = 2
34
+ cfg.write_timeout = 3
35
+ end
36
+ assert_same(1, subject.connect_timeout)
37
+ assert_same(2, subject.read_timeout)
38
+ assert_same(3, subject.write_timeout)
39
+
40
+ subject.timeout = 42
41
+ assert_same(42, subject.connect_timeout)
42
+ assert_same(42, subject.read_timeout)
43
+ assert_same(42, subject.write_timeout)
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../test_helper'
2
+
3
+ class VersionTest < Test
4
+ def test_format
5
+ assert_match(/\d+\.\d+\.\d+/, TCPClient::VERSION)
6
+ end
7
+ end
@@ -0,0 +1,111 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TCPClientTest < Test
4
+ def test_defaults
5
+ subject = TCPClient.new
6
+ assert(subject.closed?)
7
+ assert_equal('', subject.to_s)
8
+ assert_nil(subject.address)
9
+ 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
16
+ end
17
+
18
+ def create_nonconnected_client
19
+ client = TCPClient.new
20
+ client.connect('', TCPClient::Configuration.new)
21
+ rescue Errno::EADDRNOTAVAIL
22
+ ensure
23
+ return client
24
+ end
25
+
26
+ def test_failed_state
27
+ subject = create_nonconnected_client
28
+ assert(subject.closed?)
29
+ assert_equal(':0', subject.to_s)
30
+ refute_nil(subject.address)
31
+ assert_equal(':0', subject.address.to_s)
32
+ assert_nil(subject.address.hostname)
33
+ assert_instance_of(Addrinfo, subject.address.addrinfo)
34
+ assert_same(0, subject.address.addrinfo.ip_port)
35
+ assert_raises(TCPClient::NotConnected) do
36
+ subject.write('hello world!')
37
+ end
38
+ assert_raises(TCPClient::NotConnected) do
39
+ subject.read(42)
40
+ end
41
+ end
42
+
43
+ def test_connected_state
44
+ server = TCPServer.new(1234)
45
+ TCPClient.open('localhost:1234', TCPClient::Configuration.new) do |subject|
46
+ refute(subject.closed?)
47
+ assert_equal('localhost:1234', subject.to_s)
48
+ refute_nil(subject.address)
49
+ address_when_opened = subject.address
50
+ assert_equal('localhost:1234', subject.address.to_s)
51
+ assert_equal('localhost', subject.address.hostname)
52
+ assert_instance_of(Addrinfo, subject.address.addrinfo)
53
+ assert_same(1234, subject.address.addrinfo.ip_port)
54
+
55
+ subject.close
56
+ assert(subject.closed?)
57
+ assert_same(address_when_opened, subject.address)
58
+ end
59
+ ensure
60
+ server.close if server
61
+ end
62
+
63
+ def check_read_write_timeout(addr, timeout)
64
+ TCPClient.open(addr) do |subject|
65
+ refute(subject.closed?)
66
+ start_time = nil
67
+ assert_raises(IOTimeoutError) do
68
+ start_time = Time.now
69
+ # we need to send 1MB to avoid any TCP stack buffering
70
+ subject.write('?' * (1024 * 1024), timeout: timeout)
71
+ end
72
+ assert_in_delta(timeout, Time.now - start_time, 0.02)
73
+ assert_raises(IOTimeoutError) do
74
+ start_time = Time.now
75
+ subject.read(42, timeout: timeout)
76
+ end
77
+ assert_in_delta(timeout, Time.now - start_time, 0.02)
78
+ end
79
+ end
80
+
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
88
+ end
89
+
90
+ def check_connect_timeout(addr, config, timeout)
91
+ start_time = nil
92
+ assert_raises(IOTimeoutError) do
93
+ start_time = Time.now
94
+ TCPClient.new.connect(addr, config)
95
+ end
96
+ assert_in_delta(timeout, Time.now - start_time, 0.02)
97
+ end
98
+
99
+ def test_connect_ssl_timeout
100
+ server = TCPServer.new(1236)
101
+ config = TCPClient::Configuration.create do |cfg|
102
+ cfg.ssl_params = {}
103
+ end
104
+ [0.5, 1, 1.5].each do |timeout|
105
+ config.timeout = timeout
106
+ check_connect_timeout('localhost:1236', config, timeout)
107
+ end
108
+ ensure
109
+ server.close if server
110
+ end
111
+ end
@@ -0,0 +1,9 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/parallel'
3
+ require_relative '../lib/tcp-client'
4
+
5
+ $stdout.sync = $stderr.sync = true
6
+
7
+ class Test < Minitest::Test
8
+ parallelize_me!
9
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tcp-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Mike Blumtritt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: |
56
+ This gem implements a TCP client with (optional) SSL support. The
57
+ motivation of this project is the need to have a _really working_
58
+ easy to use client which can handle time limits correctly. Unlike
59
+ other implementations this client respects given/configurable time
60
+ limits for each method (`connect`, `read`, `write`).
61
+ email: mike.blumtritt@invision.de
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files:
65
+ - README.md
66
+ files:
67
+ - ".gitignore"
68
+ - README.md
69
+ - gems.rb
70
+ - lib/tcp-client.rb
71
+ - lib/tcp-client/address.rb
72
+ - lib/tcp-client/configuration.rb
73
+ - lib/tcp-client/mixin/io_timeout.rb
74
+ - lib/tcp-client/ssl_socket.rb
75
+ - lib/tcp-client/tcp_socket.rb
76
+ - lib/tcp-client/version.rb
77
+ - lib/tcp_client.rb
78
+ - rakefile.rb
79
+ - sample/google.rb
80
+ - sample/google_ssl.rb
81
+ - tcp-client.gemspec
82
+ - test/tcp-client/configuration_test.rb
83
+ - test/tcp-client/version_test.rb
84
+ - test/tcp_client_test.rb
85
+ - test/test_helper.rb
86
+ homepage: https://github.com/mblumtritt/tcp-client
87
+ licenses: []
88
+ metadata:
89
+ issue_tracker: https://github.com/mblumtritt/tcp-client/issues
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 2.0.0
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 1.3.6
104
+ requirements: []
105
+ rubyforge_project: tcp-client
106
+ rubygems_version: 2.7.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: A TCP client implementation with working timeout support.
110
+ test_files:
111
+ - test/tcp-client/configuration_test.rb
112
+ - test/tcp-client/version_test.rb
113
+ - test/tcp_client_test.rb
114
+ - test/test_helper.rb