class TCPTimeout::TCPSocket

Public Class Methods

new(host, port, opts = {}) click to toggle source
# File lib/handset_detection/vendor/tcp_timeout.rb, line 27
def initialize(host, port, opts = {})
  @connect_timeout = opts[:connect_timeout]
  @write_timeout = opts[:write_timeout]
  @read_timeout = opts[:read_timeout]

  family = opts[:family] || Socket::AF_INET
  address = Socket.getaddrinfo(host, nil, family).first[3]
  @sockaddr = Socket.pack_sockaddr_in(port, address)

  @socket = Socket.new(family, Socket::SOCK_STREAM, 0)
  @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

  local_host = opts[:local_host]
  local_port = opts[:local_port]
  if local_host || local_port
    local_host ||= ''
    local_address = Socket.getaddrinfo(local_host, nil, family).first[3]
    local_sockaddr = Socket.pack_sockaddr_in(local_port, local_address)
    @socket.bind(local_sockaddr)
  end

  connect
end

Public Instance Methods

connect() click to toggle source
# File lib/handset_detection/vendor/tcp_timeout.rb, line 51
def connect
  return @socket.connect(@sockaddr) unless @connect_timeout

  begin
    @socket.connect_nonblock(@sockaddr)
  rescue Errno::EINPROGRESS
    select_timeout(:connect, @connect_timeout)
    # If there was a failure this will raise an Error
    begin
      @socket.connect_nonblock(@sockaddr)
    rescue Errno::EISCONN
      # Successfully connected
    end
  end
end
read(length = nil, *args) click to toggle source
# File lib/handset_detection/vendor/tcp_timeout.rb, line 88
def read(length = nil, *args)
  raise ArgumentError, 'too many arguments' if args.length > 2

  timeout = (args.length > 1) ? args.pop : @read_timeout
  return @socket.read(length, *args) unless length > 0 && timeout

  buffer = args.first || ''.force_encoding(Encoding::ASCII_8BIT)

  begin
    # Drain internal buffers
    @socket.read_nonblock(length, buffer)
    return buffer if buffer.bytesize >= length
  rescue Errno::EWOULDBLOCK, Errno::EAGAIN
    # Internal buffers were empty
    buffer.clear
  rescue EOFError
    return nil
  end

  @chunk ||= ''.force_encoding(Encoding::ASCII_8BIT)

  loop do
    timeout = select_timeout(:read, timeout)

    begin
      @socket.read_nonblock(length, @chunk)
    rescue Errno::EWOULDBLOCK, Errno::EAGAIN
      retry
    rescue EOFError
      return buffer.empty? ? nil : buffer
    end
    buffer << @chunk

    if length
      length -= @chunk.bytesize
      return buffer if length <= 0
    end
  end
end
readbyte() click to toggle source
# File lib/handset_detection/vendor/tcp_timeout.rb, line 142
def readbyte
  readpartial(1).ord
end
readpartial(length, *args) click to toggle source
# File lib/handset_detection/vendor/tcp_timeout.rb, line 128
def readpartial(length, *args)
  raise ArgumentError, 'too many arguments' if args.length > 2

  timeout = (args.length > 1) ? args.pop : @read_timeout
  return @socket.readpartial(length, *args) unless length > 0 && timeout

  begin
    @socket.read_nonblock(length, *args)
  rescue Errno::EWOULDBLOCK, Errno::EAGAIN
    timeout = select_timeout(:read, timeout)
    retry
  end
end
write(data, timeout = nil) click to toggle source
# File lib/handset_detection/vendor/tcp_timeout.rb, line 67
def write(data, timeout = nil)
  timeout ||= @write_timeout
  return @socket.write(data) unless timeout

  length = data.bytesize

  total_count = 0
  loop do
    begin
      count = @socket.write_nonblock(data)
    rescue Errno::EWOULDBLOCK, Errno::EAGAIN
      timeout = select_timeout(:write, timeout)
      retry
    end

    total_count += count
    return total_count if total_count >= length
    data = data.byteslice(count..-1)
  end
end

Private Instance Methods

select_timeout(type, timeout) click to toggle source
# File lib/handset_detection/vendor/tcp_timeout.rb, line 148
def select_timeout(type, timeout)
  if timeout >= 0
    if type == :read
      read_array = [@socket]
    else
      write_array = [@socket]
    end

    start = Time.now
    if IO.select(read_array, write_array, [@socket], timeout)
      waited = Time.now - start
      return timeout - waited
    end
  end
  raise SocketTimeout, "#{type} timeout"
end