envylabs/vaulted_billing

View on GitHub
lib/vaulted_billing/http.rb

Summary

Maintainability
A
1 hr
Test Coverage
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