Created
July 23, 2015 12:40
-
-
Save Raven24/6f048d0f25814f2b9066 to your computer and use it in GitHub Desktop.
Connection test script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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? | |
[email protected]? | |
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