tcp-client 0.0.6 → 0.1.0
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 +4 -4
- data/README.md +12 -6
- data/gems.rb +1 -1
- data/lib/tcp-client.rb +22 -13
- data/lib/tcp-client/address.rb +14 -12
- data/lib/tcp-client/configuration.rb +11 -4
- data/lib/tcp-client/default_configuration.rb +21 -0
- data/lib/tcp-client/mixin/io_timeout.rb +27 -19
- data/lib/tcp-client/ssl_socket.rb +8 -2
- data/lib/tcp-client/tcp_socket.rb +9 -3
- data/lib/tcp-client/version.rb +1 -1
- data/rakefile.rb +9 -2
- data/sample/google.rb +15 -10
- data/sample/google_ssl.rb +9 -5
- data/tcp-client.gemspec +10 -8
- data/test/tcp-client/address_test.rb +3 -1
- data/test/tcp-client/configuration_test.rb +44 -11
- data/test/tcp-client/default_configuration_test.rb +59 -0
- data/test/tcp-client/version_test.rb +3 -1
- data/test/tcp_client_test.rb +54 -45
- data/test/test_helper.rb +3 -3
- metadata +14 -11
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: bb5546419b47d6680b576f39d8c4b382c39d74f07ca43f6e782160122b6ba524
         | 
| 4 | 
            +
              data.tar.gz: 3e563cd2db3efc765e491ece2fc37a784cb55ef05f6390eb23d1322a926be49e
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
               | 
| 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  | 
| 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
    
    
    
        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!( | 
| 18 | 
            -
                  raise(self,  | 
| 18 | 
            +
                def self.raise!(reason)
         | 
| 19 | 
            +
                  raise(self, "client not connected - #{reason}", caller(1))
         | 
| 19 20 | 
             
                end
         | 
| 20 21 | 
             
              end
         | 
| 21 22 |  | 
| 22 | 
            -
               | 
| 23 | 
            +
              TimeoutError = Class.new(IOError)
         | 
| 24 | 
            +
              ConnectTimeoutError = Class.new(TimeoutError)
         | 
| 25 | 
            +
              ReadTimeoutError = Class.new(TimeoutError)
         | 
| 26 | 
            +
              WriteTimeoutError = Class.new(TimeoutError)
         | 
| 23 27 |  | 
| 24 | 
            -
               | 
| 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 | 
            -
                 | 
| 29 | 
            -
                client, ret = nil, client
         | 
| 30 | 
            -
                ret
         | 
| 35 | 
            +
                block_given? ? yield(client) : client
         | 
| 31 36 | 
             
              ensure
         | 
| 32 | 
            -
                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,  | 
| 50 | 
            -
                 | 
| 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 | 
| 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 | 
            -
                 | 
| 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 | 
            -
                 | 
| 81 | 
            +
                NotConnected.raise!(self) if closed?
         | 
| 82 | 
            +
                @socket.write(*msg, timeout: timeout, exception: WriteTimeoutError)
         | 
| 74 83 | 
             
              end
         | 
| 75 84 |  | 
| 76 85 | 
             
              def flush
         | 
    
        data/lib/tcp-client/address.rb
    CHANGED
    
    | @@ -4,7 +4,7 @@ require 'socket' | |
| 4 4 |  | 
| 5 5 | 
             
            class TCPClient
         | 
| 6 6 | 
             
              class Address
         | 
| 7 | 
            -
                attr_reader : | 
| 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 | 
            -
                 | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 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,  | 
| 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  | 
| 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  | 
| 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 | 
            -
                 | 
| 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) | 
| 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) | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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 | 
| 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 | 
            -
                   | 
| 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 | 
| 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 = | 
| 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) | 
| 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 | 
| 38 | 
            +
              private_constant(:TCPSocket)
         | 
| 33 39 | 
             
            end
         | 
    
        data/lib/tcp-client/version.rb
    CHANGED
    
    
    
        data/rakefile.rb
    CHANGED
    
    | @@ -1,5 +1,12 @@ | |
| 1 | 
            -
             | 
| 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( | 
| 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 | 
            -
             | 
| 4 | 
            -
               | 
| 5 | 
            -
               | 
| 6 | 
            -
               | 
| 7 | 
            -
             | 
| 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 | 
| 10 | 
            -
            #  | 
| 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 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
               | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
               | 
| 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 | 
            -
             | 
| 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 = <<~ | 
| 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 | 
            -
               | 
| 15 | 
            +
              DESCRIPTION
         | 
| 16 16 | 
             
              spec.author = 'Mike Blumtritt'
         | 
| 17 | 
            -
              spec.email = 'mike.blumtritt@ | 
| 17 | 
            +
              spec.email = 'mike.blumtritt@pm.me'
         | 
| 18 18 | 
             
              spec.homepage = 'https://github.com/mblumtritt/tcp-client'
         | 
| 19 | 
            -
              spec.metadata = { | 
| 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 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 = | 
| 17 | 
            -
                   | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 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 = | 
| 44 | 
            -
                   | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 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
         | 
    
        data/test/tcp_client_test.rb
    CHANGED
    
    | @@ -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)  | 
| 11 | 
            -
             | 
| 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('',  | 
| 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)  | 
| 36 | 
            -
             | 
| 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 | 
            -
                 | 
| 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  | 
| 64 | 
            -
                TCPClient.open( | 
| 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:: | 
| 64 | 
            +
                  assert_raises(TCPClient::ReadTimeoutError) do
         | 
| 68 65 | 
             
                    start_time = Time.now
         | 
| 69 | 
            -
                     | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 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  | 
| 82 | 
            -
                 | 
| 83 | 
            -
                 | 
| 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( | 
| 98 | 
            +
              def check_connect_timeout(ssl_config)
         | 
| 91 99 | 
             
                start_time = nil
         | 
| 92 | 
            -
                assert_raises(TCPClient:: | 
| 100 | 
            +
                assert_raises(TCPClient::ConnectTimeoutError) do
         | 
| 93 101 | 
             
                  start_time = Time.now
         | 
| 94 | 
            -
                  TCPClient.new.connect( | 
| 102 | 
            +
                  TCPClient.new.connect('localhost:1234', ssl_config)
         | 
| 95 103 | 
             
                end
         | 
| 96 | 
            -
                assert_in_delta( | 
| 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 | 
            -
                 | 
| 101 | 
            -
             | 
| 102 | 
            -
                 | 
| 103 | 
            -
                 | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
                 | 
| 107 | 
            -
             | 
| 108 | 
            -
                 | 
| 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 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 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 | 
| 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:  | 
| 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:  | 
| 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:  | 
| 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@ | 
| 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 | 
            -
               | 
| 91 | 
            -
             | 
| 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 | 
            -
             | 
| 107 | 
            -
             | 
| 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
         |