tcp-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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