class Hatt::HTTP

Attributes

config[R]
faraday_connection[R]
headers[RW]
http[R]

allow stubbing http if we are testing

last_request[R]

this is useful for testing apis, and other times you want to interrogate the http details of a response

last_response[R]

this is useful for testing apis, and other times you want to interrogate the http details of a response

name[R]

Public Class Methods

new(config) click to toggle source
# File lib/hatt/http.rb, line 16
def initialize(config)
  @config = config
  @name = @config[:name]
  @base_uri = @config[:base_uri]
  @log_headers = @config.fetch(:log_headers, true)
  @log_bodies = @config.fetch(:log_bodies, true)

  logger.debug "Configuring service:\n#{@config.to_hash.to_yaml}\n"

  @faraday_connection = Faraday.new @config[:faraday_url] do |conn_builder|
    # do our own logging
    # conn_builder.response logger: logger
    # conn_builder.adapter  Faraday.default_adapter  # make requests with Net::HTTP
    conn_builder.adapter @config.fetch(:adapter, :typhoeus).intern
    conn_builder.ssl[:verify] = false if @config[:ignore_ssl_cert]

    # defaulting this to flat adapter avoids issues when duplicating parameters
    conn_builder.options[:params_encoder] = Faraday.const_get(@config.fetch(:params_encoder, 'FlatParamsEncoder'))

    # this nonsense dont work?!  https://github.com/lostisland/faraday_middleware/issues/76
    # conn_builder.use :instrumentation
  end

  @headers = {
    'accept' => 'application/json',
    'content-type' => 'application/json'
  }
  if @config[:default_headers]
    logger.debug 'Default headers configured: ' + @config[:default_headers].inspect
    @config[:default_headers].each_pair do |k, v|
      @headers[k.to_s] = v.to_s
    end
  end
  @default_timeout = @config.fetch(:timeout, 10)
  logger.info "Initialized hatt service '#{@name}'"
end

Public Instance Methods

delete(path, options = {}) click to toggle source
# File lib/hatt/http.rb, line 148
def delete(path, options = {})
  do_request :delete, path, nil, options
end
do_request(method, path, obj = nil, options = {}) click to toggle source

do_request performs the actual request, and does associated logging options can include:

  • :timeout, which specifies num secs the request should timeout in (this turns out to be kind of annoying to implement)

# File lib/hatt/http.rb, line 79
def do_request(method, path, obj = nil, options = {})
  # hatt clients pass in path possibly including query params.
  # Faraday needs the query and path seperately.
  parsed_uri = URI.parse make_path(path)
  # faraday needs the request params as a hash.
  # this turns out to be non-trivial
  query_hash = if parsed_uri.query
                 cgi_hash = CGI.parse(parsed_uri.query)
                 # this next line accounts for one param having multiple values
                 cgi_hash.each_with_object({}) { |(k, v), h| h[k] = v[1] ? v : v.first; }
               end

  req_headers = make_headers(options)

  body = if options[:form]
           URI.encode_www_form obj
         else
           jsonify(obj)
  end


  log_request(method, parsed_uri, query_hash, req_headers, body)

  # doing it this way avoids problem with OPTIONS method: https://github.com/lostisland/faraday/issues/305
  response = nil
  metrics_obj = { method: method, service: @name, path: parsed_uri.path }
  ActiveSupport::Notifications.instrument('request.hatt', metrics_obj) do
    response = @faraday_connection.run_request(method, nil, nil, nil) do |req|
      req.path = parsed_uri.path
      req.params = metrics_obj[:params] = query_hash if query_hash

      req.headers = req_headers
      req.body = body
      req.options[:timeout] = options.fetch(:timeout, @default_timeout)
    end
    metrics_obj[:response] = response
  end

  logger.info "Request status: (#{response.status}) #{@name}: #{method.to_s.upcase} #{path}"
  @last_request = {
    method: method,
    path: parsed_uri.path,
    query: parsed_uri.query,
    headers: req_headers,
    body: body
  }
  @last_response = response

  response_obj = objectify response.body

  log_response(response, response_obj)

  raise RequestException.new(nil, response) unless response.status >= 200 && response.status < 300

  response_obj
end
get(path, options = {}) click to toggle source
# File lib/hatt/http.rb, line 136
def get(path, options = {})
  do_request :get, path, nil, options
end
head(path, options = {}) click to toggle source
# File lib/hatt/http.rb, line 140
def head(path, options = {})
  do_request :head, path, nil, options
end
in_parallel(&blk) click to toggle source
# File lib/hatt/http.rb, line 70
def in_parallel(&blk)
  @faraday_connection.headers = @headers
  @faraday_connection.in_parallel(&blk)
end
last_response_status() click to toggle source

convenience method for accessing the last response status code

# File lib/hatt/http.rb, line 169
def last_response_status
  @last_response.status
end
options(path, options = {}) click to toggle source
# File lib/hatt/http.rb, line 144
def options(path, options = {})
  do_request :options, path, nil, options
end
patch(path, obj, options = {}) click to toggle source
# File lib/hatt/http.rb, line 160
def patch(path, obj, options = {})
  do_request :patch, path, obj, options
end
post(path, obj, options = {}) click to toggle source
# File lib/hatt/http.rb, line 152
def post(path, obj, options = {})
  do_request :post, path, obj, options
end
post_form(path, params, options = {}) click to toggle source
# File lib/hatt/http.rb, line 164
def post_form(path, params, options = {})
  do_request :post, path, params, options.merge(form: true)
end
put(path, obj, options = {}) click to toggle source
# File lib/hatt/http.rb, line 156
def put(path, obj, options = {})
  do_request :put, path, obj, options
end
stubs() click to toggle source
# File lib/hatt/http.rb, line 53
def stubs
  stubs = Faraday::Adapter::Test::Stubs.new
  @faraday_connection = Faraday.new @config[:faraday_url] do |conn_builder|
    conn_builder.adapter :test, stubs
  end
  stubs
end

Private Instance Methods

log_request(method, parsed_uri, query_hash={}, headers, body) click to toggle source
# File lib/hatt/http.rb, line 196
def log_request(method, parsed_uri, query_hash={}, headers, body)
  # log the request
  logger.debug [
    "Doing request: #{@name}: #{method.to_s.upcase} #{parsed_uri.path}?#{query_hash.is_a?(Hash) ? URI.encode_www_form(query_hash) : ''}",
    @log_headers ? ['Request Headers:',
                    headers.map { |k, v| "#{k}: #{v.inspect}" }] : nil,
    @log_bodies ? ['Request Body:', body] : nil
  ].flatten.compact.join("\n")
end
log_response(response, response_obj) click to toggle source
# File lib/hatt/http.rb, line 206
def log_response(response, response_obj)
  log_messages = [
    'Response Details:',
    @log_headers ? ['Response Headers:',
                    response.headers.map { |k, v| "#{k}: #{v.inspect}" }] : nil
  ]
  if response.headers['content-type'] =~ /json/
    log_messages += [
      @log_bodies ? ['Response Body:', jsonify(response_obj)] : nil,
      ''
    ]
  end
  logger.debug log_messages.flatten.compact.join("\n")
end
make_headers(options) click to toggle source
# File lib/hatt/http.rb, line 184
def make_headers(options)
  headers = if options[:additional_headers]
              @headers.merge options[:additional_headers]
            elsif options[:headers]
              options[:headers]
            else
              @headers.clone
  end
  headers['content-type'] = 'application/x-www-form-urlencoded' if options[:form]
  headers
end
make_path(path_suffix) click to toggle source

add base uri to request

# File lib/hatt/http.rb, line 176
def make_path(path_suffix)
  if @base_uri
    @base_uri + path_suffix
  else
    path_suffix
  end
end