lib/vaulted_billing/http.rb
require 'uri'
require 'net/http'
module VaultedBilling
class HTTP
HTTP_ERRORS = [
Timeout::Error,
Errno::ETIMEDOUT,
Errno::EINVAL,
Errno::ECONNRESET,
Errno::ECONNREFUSED,
Errno::EHOSTUNREACH,
EOFError,
Net::HTTPBadResponse,
Net::HTTPHeaderSyntaxError,
Net::ProtocolError
] unless defined?(HTTP_ERRORS)
class Response
attr_accessor :code
attr_accessor :message
attr_accessor :body
attr_accessor :success
attr_accessor :raw_response
attr_accessor :connection_error
alias :connection_error? :connection_error
def initialize(http_response)
if http_response
self.raw_response = http_response
self.code = http_response.code
self.message = http_response.message
self.body = http_response.body
self.success = ((http_response.code =~ /^2\d{2}/) == 0)
self.connection_error = false
end
end
alias :success? :success
alias :status_code :code
end
attr_reader :uri
def initialize(caller, uri, options = {})
@uri = [uri].flatten.compact.collect { |u| URI.parse(u.to_s).normalize }
@headers = options[:headers] || {}
@basic_auth = options[:basic_auth]
@content_type = options[:content_type]
@caller = caller
@before_request = options[:before_request]
@on_success = options[:on_success]
@on_error = options[:on_error]
@on_complete = options[:on_complete]
end
def post(body, options = {})
request(:post, uri.dup, body, options)
end
def get(options = {})
request(:get, uri.dup, nil, options)
end
def put(body, options = {})
request(:put, uri.dup, body, options)
end
private
def log(level, string)
if VaultedBilling.logger?
VaultedBilling.logger.send(level) { string }
end
end
def request(method, uris, body = nil, options = {})
uri = uris.shift || raise(ArgumentError, "URI is empty")
request = case method
when :get
Net::HTTP::Get
when :put
Net::HTTP::Put
when :post
Net::HTTP::Post
else
raise ArugmentError
end.new(uri.path)
request.initialize_http_header(@headers.merge(options[:headers] || {}).reverse_merge({
'User-Agent' => user_agent_string
}))
request.body = body if body
set_basic_auth request, options[:basic_auth] || @basic_auth
set_content_type request, options[:content_type] || @content_type
response = Net::HTTP.new(uri.host, uri.port).tap do |https|
https.read_timeout = options[:read_timeout] || 60
https.open_timeout = options[:open_timeout] || 60
https.use_ssl = true
https.ca_file = VaultedBilling.config.ca_file
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
run_callback(:before_request, options[:before_request] || @before_request, request)
http_response = run_request(request, response, options)
if http_response.connection_error && uris.present?
request(method, uris, body, options)
else
run_callback(:on_complete, options[:on_complete] || @on_complete, http_response)
http_response
end
end
def run_callback(type, callback, *payload)
case callback
when Proc
callback.call(*payload)
when String, Symbol
@caller.send(callback, *payload)
end
end
def run_request(request, response, options)
log :debug, "%s %s to %s" % [request.class.name.split('::').last, request.body.inspect, uri.to_s]
http_response = Response.new(response.request(request))
log :info, "Response code %s (HTTP %s), %s" % [http_response.message, http_response.code.presence || '0', http_response.body.inspect]
run_callback(:on_success, options[:on_success] || @on_success, http_response)
http_response
rescue *HTTP_ERRORS
log :info, "HTTP Error: %s - %s" % [$!.class.name, $!.message]
Response.new(nil).tap do |request_response|
request_response.success = false
request_response.message = "%s - %s" % [$!.class.name, $!.message]
request_response.connection_error = true
run_callback(:on_error, options[:on_error] || @on_error, request_response, $!)
end
end
def set_content_type(request, content)
request.set_content_type(content) if content
end
def set_basic_auth(request, auth)
request.basic_auth(auth.first, auth.last) if auth
end
def user_agent_string
"vaulted_billing/%s (Rubygems; Ruby %s %s)" % [VaultedBilling::Version, RUBY_VERSION, RUBY_PLATFORM]
end
end
end