module Projector::Transport::HTTP

Persistent HTTP API transport adapter.

Constants

ERROR_MAP

Status code to exception map.

MAX_REDIRECTS

Maximum URI redrects to follow before failing.

Private Instance Methods

boolean_from_response(*args) click to toggle source

Returns true if the request is not an error or redirect @return [Boolean]

# File lib/projector/transport/http.rb, line 183
def boolean_from_response(*args)
  response = request(*args)
  (200..299).include? response.code.to_i
end
build_request(method, uri) click to toggle source

Constructs a request object based off the method @param [Symbol] method the HTTP method @param [String] uri the HTTP URI to request

# File lib/projector/transport/http.rb, line 115
def build_request(method, uri)
  case method
    when :get
      Net::HTTP::Get.new(uri.request_uri)
    when :post
      Net::HTTP::Post.new(uri.request_uri)
    when :put
      Net::HTTP::Put.new(uri.request_uri)
    when :delete
      Net::HTTP::Delete.new(uri.request_uri)
    when :patch
      Net::HTTP::Patch.new(uri.request_uri)
  end
end
can_post_data?(method) click to toggle source

Returns true if the HTTP method can post data @return [Boolean]

# File lib/projector/transport/http.rb, line 190
def can_post_data?(method)
  [:post, :put, :patch].include?(method)
end
handle_error(request, response) click to toggle source

Raises the appropriate error based on the {ERROR_MAP} raise [Projector::Error]

# File lib/projector/transport/http.rb, line 157
def handle_error(request, response)
  return unless error = ERROR_MAP[response.code.to_i]
  message = nil
  begin
    message = JSON.parse(response.body) || request.path
  rescue JSON::ParserError
  end
  # Raise error
  err = error.new(message)
  err.retry_after = response['retry-after'].to_i if response.code.to_i >= 500
  err.request = request
  err.response = response
  err.response_body = message
  err.request_id = request['request-id']
  raise err
end
http() click to toggle source

Returns an instance of the HTTP client @return [Net::HTTP::Persistent]

# File lib/projector/transport/http.rb, line 103
def http
  @http ||= begin
              http = Net::HTTP::Persistent.new('projector')
              http.open_timeout = 5
              http.read_timeout = 2
              http
            end
end
json_request(*args) click to toggle source

Makes a request and parses the result as a JSON object @return [Response]

# File lib/projector/transport/http.rb, line 176
def json_request(*args)
  # Perform request, pass result format
  Response.new(request(*args))
end
log_debug_data(*args) click to toggle source

Logs all HTTP request data to STDOUT if the PROJECTOR_DEBUG_HTTP env variable is set

# File lib/projector/transport/http.rb, line 195
def log_debug_data(*args)
  return unless ENV['PROJECTOR_DEBUG_HTTP']
  @logger ||= begin
    require 'logger'
    Logger.new(STDOUT)
  end
  @logger.info(args.reject{|s| s = s.to_s; s.nil? || s.empty?}.join(' - '))
end
param_for(key, value, parent = nil) click to toggle source

Escapes params to a URI-safe form

# File lib/projector/transport/http.rb, line 142
def param_for(key, value, parent = nil)
  if value.is_a?(Hash)
    params = []
    value.each_pair do |value_key, value_value|
      value_parent = parent ? parent + "[#{key}]" : key.to_s
      params << param_for(value_key, value_value, value_parent)
    end
    params
  else
    ["#{parent ? parent + "[#{key}]" : key.to_s}=#{CGI::escape(value.to_s)}"]
  end
end
request(method, host, path, params = nil, redirect_count = 0) click to toggle source

Makes an HTTP Request

@param [String] method The HTTP method to use @param [String] host the host to connect to @param [String] path the path to request @param [Hash] params (nil) the parameters to send @param [Integer] redirect_count (0) the number of redirects that have been followed so far

# File lib/projector/transport/http.rb, line 44
def request(method, host, path, params = nil, redirect_count = 0)
  uri = URI.parse("#{host}#{path}")

  # if the request requires parameters in the query string, merge them in
  if params && !can_post_data?(method)
    query_values = uri.query ? URI.decode_www_form(uri.query).inject({}) {|h,(k,v)| h[k]=v; h} : {}
    uri.query = to_url_params((query_values || {}).merge(params))
  end

  # Build request
  request = build_request(method, uri)

  # Add headers
  request['Authorization'] = "Token #{self.token}"
  request['Content-Type'] = 'application/json'
  request['Accept'] = "application/vnd.projector+json; version=#{Projector::API_VERSION}"
  request['User-Agent'] = "ProjectorRubySdk/#{Projector::VERSION}"
  request['Request-Id'] = SecureRandom.uuid

  # Add params as JSON if they exist
  request.body = JSON.generate(params) if can_post_data?(method) && params

  log_debug_data(uri, request.method, request.body)

  # Request
  response = http.request(uri, request)

  # Log debug data if needed
  log_debug_data(uri, response.code, response.body)

  # If we've been given a redirect
  if [301, 302].include?(response.code.to_i)

    # Make sure we aren't in a giant redirect loop
    if redirect_count >= MAX_REDIRECTS
      err = Projector::Error::TooManyRedirects.new
      err.request = request
      err.response = response
      err.redirect_count = redirect_count
      raise err
    end

    # Parse the new location
    uri = URI.parse(response['Location'])
    # Make the call to the redirected location, incrementing the redirect count by 1
    return request(method, uri.select(:scheme, :host).join("://"), uri.path, params, redirect_count + 1)
    # Return the result of the redirected request
  end

  # Check for errors
  handle_error(request, response)


  # Return the raw response object
  response
end
to_url_params(hash) click to toggle source

Converts a hash to HTTP URI format @param [Hash] hash the hash to be converted @return [String]

# File lib/projector/transport/http.rb, line 133
def to_url_params(hash)
  params = []
  hash.each_pair do |key, value|
    params << param_for(key, value).flatten
  end
  params.sort.join('&')
end