class Rester::Client

Attributes

adapter[R]
error_threshold[R]
logger[R]
retry_period[R]
version[R]

Public Class Methods

new(adapter, params={}) click to toggle source
# File lib/rester/client.rb, line 18
def initialize(adapter, params={})
  self.adapter = adapter
  self.version = params[:version]
  @error_threshold = (params[:error_threshold] || 3).to_i
  @retry_period = (params[:retry_period] || 1).to_f
  self.logger = params[:logger]
  @_breaker_enabled = params.fetch(:circuit_breaker_enabled,
    ENV['RACK_ENV'] != 'test' && ENV['RAILS_ENV'] != 'test'
  )

  @_resource = Resource.new(self)
  _init_requester

  # Send a test ping request to the service so we can store the producer's
  # name for future request logs
  fail Errors::ConnectionError unless connected?
end

Public Instance Methods

circuit_breaker_enabled?() click to toggle source
# File lib/rester/client.rb, line 43
def circuit_breaker_enabled?
  !!@_breaker_enabled
end
connected?() click to toggle source
# File lib/rester/client.rb, line 36
def connected?
  adapter.connected? && @_requester.call(:get, '/ping', {}).successful?
rescue Exception => e
  logger.error("Connection Error: #{e.inspect}")
  false
end
logger=(logger) click to toggle source
# File lib/rester/client.rb, line 47
def logger=(logger)
  logger = Utils::LoggerWrapper.new(logger) if logger
  @logger = logger
end
name() click to toggle source
# File lib/rester/client.rb, line 56
def name
  @_producer_name
end
request(verb, path, params={}) click to toggle source
# File lib/rester/client.rb, line 60
def request(verb, path, params={})
  path = _path_with_version(path)
  @_requester.call(verb, path, params)
rescue Utils::CircuitBreaker::CircuitOpenError
  # Translate this error so it's easier handle for clients.
  # Also, at some point we may want to extract CircuitBreaker into its own
  # gem, and this will make that easier.
  raise Errors::CircuitOpenError
end
with_context(*args, &block) click to toggle source

This is only implemented by the StubAdapter.

# File lib/rester/client.rb, line 72
def with_context(*args, &block)
  adapter.with_context(*args, &block)
end

Protected Instance Methods

adapter=(adapter) click to toggle source
# File lib/rester/client.rb, line 78
def adapter=(adapter)
  @adapter = adapter
end
version=(version) click to toggle source
# File lib/rester/client.rb, line 82
def version=(version)
  unless (@version = (version || 1).to_i) > 0
    fail ArgumentError, 'version must be > 0'
  end
end

Private Instance Methods

_init_requester() click to toggle source

Sets up the circuit breaker for making requests to the service.

Any exception raised by the `_request` method will count as a failure for the circuit breaker. Once the threshold for errors has been reached, the circuit opens and all subsequent requests will raise a CircuitOpenError.

When the circuit is opened or closed, a message is sent to the logger for the client.

# File lib/rester/client.rb, line 105
def _init_requester
  if circuit_breaker_enabled?
    @_requester = Utils::CircuitBreaker.new(
      threshold: error_threshold, retry_period: retry_period
    ) { |*args| _request(*args) }

    @_requester.on_open do
      logger.error("circuit opened for #{name}")
    end

    @_requester.on_close do
      logger.info("circuit closed for #{name}")
    end
  else
    @_requester = proc { |*args| _request(*args) }
  end
end
_parse_json(data) click to toggle source
# File lib/rester/client.rb, line 179
def _parse_json(data)
  if data.is_a?(String) && !data.empty?
    JSON.parse(data, symbolize_names: true)
  else
    {}
  end
end
_path_with_version(path) click to toggle source
# File lib/rester/client.rb, line 153
def _path_with_version(path)
  Utils.join_paths("/v#{version}", path)
end
_process_response(start_time, verb, path, status, headers, body) click to toggle source
# File lib/rester/client.rb, line 157
def _process_response(start_time, verb, path, status, headers, body)
  elapsed_ms = (Time.now.to_f - start_time) * 1000
  response = Response.new(status, _parse_json(body))
  @_producer_name = headers['X-Rester-Producer-Name']
  logger.info("received status #{status} after %0.3fms" % elapsed_ms)

  unless [200, 201, 400].include?(status)
    case status
    when 401
      fail Errors::AuthenticationError
    when 403
      fail Errors::ForbiddenError
    when 404
      fail Errors::NotFoundError, path
    else
      fail Errors::ServerError, response[:message]
    end
  end

  response
end
_request(verb, path, params) click to toggle source

Add a correlation ID to the header and send the request to the adapter

# File lib/rester/client.rb, line 125
def _request(verb, path, params)
  Rester.wrap_request do
    Rester.request_info[:producer_name] = name
    Rester.request_info[:path] = path
    Rester.request_info[:verb] = verb
    logger.info('sending request')

    _set_default_headers
    start_time = Time.now.to_f

    begin
      response = adapter.request(verb, path, params)
      _process_response(start_time, verb, path, *response)
    rescue Errors::TimeoutError
      logger.error('timed out')
      raise
    end
  end
end
_set_default_headers() click to toggle source
# File lib/rester/client.rb, line 145
def _set_default_headers
  adapter.headers(
    'X-Rester-Correlation-ID' => Rester.correlation_id,
    'X-Rester-Consumer-Name' => Rester.service_name,
    'X-Rester-Producer-Name' => name
  )
end
method_missing(meth, *args, &block) click to toggle source

Submits the method to the adapter.

# File lib/rester/client.rb, line 92
def method_missing(meth, *args, &block)
  @_resource.send(:method_missing, meth, *args, &block)
end