lib/ztk/tcp_socket_check.rb
require 'socket'
require 'timeout'
module ZTK
# TCPSocketCheck Error Class
#
# @author Zachary Patten <zpatten AT jovelabs DOT io>
class TCPSocketCheckError < Error; end
# TCP Socket Checking Class
#
# This class has two basic modes of operation:
#
# * Read Test
#
# By default we will perform a read test against the host and port specified.
# In this mode we will attempt to connect to the host and port supplied and if
# we can read any amount of data, regardless of the content we view this as
# success.
#
# * Write Test
#
# If data is supplied via the configuration, this will change the mode of
# operation to a write test. Certain services, such as HTTP don't send any
# data unless you send something first. In this mode we will attempt to
# connect to the host and port supplied, once connected we will write the
# supplied data to the socket and then attempt to read from the socket. If we
# can read any amount of data, reagardless of the conent we view this as
# success.
#
# = Typical usage:
#
# Given a host and port we want to check, we can do something like this:
#
# sc = ZTK::TCPSocketCheck.new(:host => "www.github.com", :port => 22)
#
# Then if we want to check if this host is responding on the specified port:
#
# sc.ready? and puts("They are there!")
#
# This works well for protocols that spew forth some data right away for use
# to read. However, with certain protocols, such as HTTP, we need to send
# some data first before we get a response.
#
# Given we want to check a host and port that requires some giving before we
# can take:
#
# sc = ZTK::TCPSocketCheck.new(:host => "www.google.com", :port => 80, :data => "GET")
#
# Then if we want to check if this host is responding on the specified port:
#
# sc.ready? and puts("They are there!")
#
# The ready? methods timeout is bound to the configuration option *timeout*.
#
# If we are waiting for a service to come online, we can do this:
#
# sc.wait and puts("They are there!")
#
# The wait methods timeout is bound to the configuration option *wait*.
#
# @author Zachary Patten <zpatten AT jovelabs DOT io>
class TCPSocketCheck < ZTK::Base
# @param [Hash] configuration Configuration options hash.
# @option config [String] :host Host to connect to.
# @option config [Integer, String] :port Port to connect to.
# @option config [String] :data Data to send to host to provoke a response.
# @option config [Integer] :timeout (5) Set the IO select timeout.
# @option config [Integer] :wait (60) Set the amount of time before the wait
# method call will timeout.
def initialize(configuration={})
super({
:timeout => 5,
:wait => 60
}, configuration)
end
# Check to see if socket on the host and port specified is ready. This
# method will timeout and return false after the amount of seconds specified
# in *config.timeout* has passed if the socket has not become ready.
#
# @return [Boolean] Returns true or false depending on Whether the socket
# is ready or not.
def ready?
config.host.nil? and log_and_raise(TCPSocketCheckError, "You must supply a host!")
config.port.nil? and log_and_raise(TCPSocketCheckError, "You must supply a port!")
socket = TCPSocket.new(config.host, config.port)
if config.data.nil?
config.ui.logger.debug { "read(#{config.host}:#{config.port})" }
((IO.select([socket], nil, nil, config.timeout) && socket.gets) ? true : false)
else
config.ui.logger.debug { "write(#{config.host}:#{config.port}, #{config.data.size} bytes)" }
((IO.select(nil, [socket], nil, config.timeout) && socket.write(config.data)) ? true : false)
end
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::EHOSTDOWN, Errno::ENETUNREACH => e
config.ui.logger.debug { "#{config.host}:#{config.port} - #{e.message}" }
false
ensure
(socket && socket.close)
end
# Wait for the socket on the host and port specified to become ready. This
# method will timeout and return false after the amount of seconds specified
# in *config.wait* has passed if the socket has not become ready.
#
# @return [Boolean] Returns true or false depending on Whether the socket
# became ready or not.
def wait
config.ui.logger.debug { "Waiting for socket to become available; timeout after #{config.wait} seconds." }
Timeout.timeout(config.wait) do
until ready?
config.ui.logger.debug { "Sleeping 1 second." }
sleep(1)
end
end
true
rescue Timeout::Error => e
config.ui.logger.warn { "socket(#{config.host}:#{config.port}) timeout!" }
false
end
end
end