Skip to content

Instantly share code, notes, and snippets.

@Raven24
Created July 23, 2015 12:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Raven24/6f048d0f25814f2b9066 to your computer and use it in GitHub Desktop.
Save Raven24/6f048d0f25814f2b9066 to your computer and use it in GitHub Desktop.
Connection test script
require 'resolv'
require 'net/http'
class ConnectionTester
REQUEST_OPTS = {
open_timeout: 3,
ssl_timeout: 3,
read_timeout: 5
}
class << self
# Test the reachability of a server by the given HTTP/S URL.
# In the first step, a DNS query is performed to check whether the
# given name even resolves correctly.
# The second step is to send a HTTP request and look at the returned
# status code.
# This function isn't intended to check for the availability of a
# specific page, instead a HEAD request is sent to the root directory
# of the server.
#
# @param [String] server URL
# @return [Result] result object containing information about the
# server and to what point the connection was successful
def check(url)
url = "http://#{url}" unless url.include?('://')
req_opts = request_opts_for_url(url)
host = req_opts.delete(:host)
ct = ConnectionTester.new(host)
res = Result.new(host)
begin
# test DNS resolving
res.ip = ct.resolve
# test HTTP request, measure time
start = Time.new
res.status_code = ct.request(req_opts)
stop = Time.new
# at this point we were successful
res.reachable = true
res.ssl_status = true if ct.uses_ssl?
res.rt = ((stop-start) * 1000.0).to_i # milliseconds
rescue Failure => e
case e
when NetFailure
res.reachable = false
when SSLFailure
res.reachable = true
res.ssl_status = false
when HTTPFailure
res.reachable = true
res.ssl_status = true if ct.uses_ssl?
end
res.error = e
end
res.freeze
end
# put together a hash of options for a Net::HTTP request.
# This also includes a :host key containing the hostname of the server
#
# @param [String] target URL
def request_opts_for_url(url)
uri = URI.parse(url)
raise ArgumentError, "invalid protocol: '#{uri.scheme.upcase}'" unless uri.is_a?(URI::HTTP)
host = uri.host
ssl = (uri.scheme == 'https') ? true : false
REQUEST_OPTS.merge({host: host, use_ssl: ssl})
end
end
def initialize(hostname)
@hostname = hostname
end
# Carry out the DNS query
#
# @return [Resolv::IPv4, Resolv::IPv6] the resolved IP address
def resolve
res = Resolv::DNS.new
addr = res.getaddress(@hostname)
res.close
addr
rescue Resolv::ResolvError, Resolv::ResolvTimeout
raise DNSFailure, "unable to resolve '#{@hostname}'"
end
# Perform a HTTP HEAD request to determine the following information
# * is the host reachable
# * is port 80/443 open
# * is the SSL certificate valid (only on HTTPS)
# * does the server return a successful HTTP status code
#
# @param
# @return [Integer] HTTP status code
def request(opts, host=nil, max_redirects=3)
host ||= @hostname
raise HTTPFailure, "too many redirects on '#{host}'" if max_redirects == 0
port = opts[:use_ssl] ? 443 : 80
@uses_ssl = opts[:use_ssl]
Net::HTTP.start(host, port, nil, nil, nil, opts) do |http|
resp = http.head('/')
case resp
when Net::HTTPSuccess
Integer(resp.code)
when Net::HTTPRedirection
req_opts = ConnectionTester.request_opts_for_url(resp['location'])
new_host = req_opts.delete(:host)
request(req_opts, new_host, max_redirects-1)
else
raise HTTPFailure, "unsuccessful response code: #{resp.code}"
end
end
rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED => e
raise NetFailure, e.message
rescue OpenSSL::SSL::SSLError => e
raise SSLFailure, e.message
rescue Net::ProtocolError, ArgumentError
raise HTTPFailure, e.message
end
def uses_ssl?
@uses_ssl
end
class Failure < StandardError
end
class DNSFailure < Failure
end
class NetFailure < Failure
end
class SSLFailure < Failure
end
class HTTPFailure < Failure
end
class Result
# @return [String] hostname derived from the URL
attr_accessor :hostname
# @return [String] IP address from DNS query
attr_accessor :ip
# @return [Boolean] whether the host was reachable or not
attr_accessor :reachable
# @return [Boolean] indicating how the SSL verification went
attr_accessor :ssl_status
# @return [Integer] HTTP status code that was returned for the HEAD request
attr_accessor :status_code
# @return [Integer] response time for the HTTP request
attr_accessor :rt
# @return [Exception] if the test is unsuccessful, this will contain
# an exception of type {ConnectionTester::Failure}
attr_accessor :error
def initialize(hostname)
@hostname = hostname
@rt = -1
end
def success?
@error.nil?
end
def error?
!@error.nil?
end
end
end
require 'pp'
['https://www.google.com/', 'https://this.is-not-a.domain/', #
'joindiaspora.com', 'pod.geraspora.de',
'orf.at', 'ftp://kernel.org', 'wikipedia.org'].each do |name|
puts "testing '#{name}'"
begin
res = ConnectionTester.check(name)
pp res
puts "OK" if res.success?
puts "ERROR: #{res.error.message}" if res.error?
rescue => e
puts e.message
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment