class AWS::Core::Client

Base client class for all of the Amazon AWS service clients.

Constants

CACHEABLE_REQUESTS

@private

Attributes

config[R]

@return [Configuration] This clients configuration.

credential_provider[R]

@return [CredentialProviders::Provider] Returns the credentail

provider for this client.

@private

endpoint[R]

@return [String] Returns the service endpoint (hostname) this client

makes requests against.

@private

http_read_timeout[R]

@return [Integer] The number of secords before requests made by

this client should timeout if they have not received a response.
port[R]

@return [Integer] What port this client makes requests via. @private

service_ruby_name[R]

@return [String] The snake-cased ruby name for the service

(e.g. 's3', 'iam', 'dynamo_db', etc).

@private

Public Class Methods

new(options = {}) click to toggle source

Creates a new low-level client. @param [Hash] options @option options [Core::Configuration] :config (AWS.config)

The base configuration object to use.  All other options
are merged with this.  Defaults to the AWS.config.

@option (see AWS.config)

# File lib/aws/core/client.rb, line 40
def initialize options = {}

  options = options.dup # so we don't modify the options passed in

  @service_ruby_name = self.class.service_ruby_name

  # translate these into service specific configuration options,
  # e.g. :endpoint into :s3_endpoint
  [:endpoint, :region, :port].each do |opt|
    if options[opt]
      options[:"#{service_ruby_name}_#{opt}"] = options.delete(opt)
    end
  end

  @config = (options.delete(:config) || AWS.config)
  @config = @config.with(options)

  @credential_provider = @config.credential_provider
  @http_handler = @config.http_handler
  @endpoint = config.send(:"#{service_ruby_name}_endpoint")
  @port = config.send(:"#{service_ruby_name}_port")
  @http_read_timeout = @config.http_read_timeout

end

Protected Class Methods

add_client_request_method(method_name, options = {}) click to toggle source

Adds a single method to the current client class. This method yields a request method builder that allows you to specify how:

  • the request is built

  • the response is processed

  • the response is stubbed for testing

# File lib/aws/core/client.rb, line 553
        def add_client_request_method method_name, options = {}, &block

          operations << method_name

          ClientRequestMethodBuilder.new(self, method_name, &block)

          method_def = "            def #{method_name}(*args, &block)
              options = args.first ? args.first : {}
              client_request(#{method_name.inspect}, options, &block)
            end
"

          module_eval(method_def)

        end
define_client_method(method_name, builder, parser) click to toggle source
# File lib/aws/core/client.rb, line 599
def define_client_method method_name, builder, parser

  request_builders[method_name] = builder
  response_parsers[method_name] = parser

  add_client_request_method(method_name) do

    configure_request do |request, request_options|
      builder.populate_request(request, request_options)
    end

    process_response do |response|
      response.data = parser.extract_data(response)
    end

    simulate_response do |response|
      response.data = parser.simulate
    end

  end
end
define_client_methods(api_version) click to toggle source

Defines one method for each service operation described in the API configuration. @param [String] api_version

# File lib/aws/core/client.rb, line 583
def define_client_methods api_version

  const_set(:API_VERSION, api_version)

  api_config = load_api_config(api_version)

  api_config[:operations].each do |operation|

    builder = request_builder_for(api_config, operation)
    parser = response_parser_for(api_config, operation)

    define_client_method(operation[:method], builder, parser)

  end
end
load_api_config(api_version) click to toggle source

Loads the API configuration for the given API version. @param [String] api_version The API version date string

(e.g. '2012-01-05').

@return [Hash]

# File lib/aws/core/client.rb, line 574
def load_api_config api_version
  lib = File.dirname(File.dirname(__FILE__))
  path = "#{lib}/api_config/#{service_name}-#{api_version}.yml"
  YAML.load(File.read(path))
end
operations() click to toggle source

@return [Array<Symbol>] Returns a list of service operations as

method names supported by this client.
# File lib/aws/core/client.rb, line 520
def operations
  @operations ||= []
end
request_builder_for(api_config, operation) click to toggle source

Define this in sub-classes (e.g. QueryClient, RESTClient, etc)

# File lib/aws/core/client.rb, line 537
def request_builder_for api_config, operation
  raise NotImplementedError
end
request_builders() click to toggle source

@private

# File lib/aws/core/client.rb, line 525
def request_builders
  @request_builders ||= {}
end
response_parser_for(api_config, operation) click to toggle source

Define this in sub-classes (e.g. QueryClient, RESTClient, etc)

# File lib/aws/core/client.rb, line 542
def response_parser_for api_config, operation
  raise NotImplementedError
end
response_parsers() click to toggle source

@private

# File lib/aws/core/client.rb, line 530
def response_parsers
  @response_parsers ||= {}
end

Public Instance Methods

log_warning(warning) click to toggle source

Logs the warning to the configured logger, otherwise to stderr. @param [String] warning @return [nil]

# File lib/aws/core/client.rb, line 166
def log_warning warning
  message = '[aws-sdk-gem-warning] ' + warning
  if config.logger
    config.logger.warn(message)
  else
    $stderr.puts(message)
  end
  nil
end
new_stub_for(method_name) click to toggle source

Primarily used for testing, this method returns an empty psuedo service response without making a request. Its used primarily for testing the ligher level service interfaces. @private

# File lib/aws/core/client.rb, line 154
def new_stub_for method_name
  response = Response.new(Http::Request.new, Http::Response.new)
  response.request_type = method_name
  response.request_options = {}
  send("simulate_#{method_name}_response", response)
  response.signal_success
  response
end
operations() click to toggle source

@return (see #operations)

# File lib/aws/core/client.rb, line 92
def operations
  self.class.operations
end
stub_for(method_name) click to toggle source

The stub returned is memoized. @see #new_stub_for @private

# File lib/aws/core/client.rb, line 145
def stub_for method_name
  @stubs ||= {}
  @stubs[method_name] ||= new_stub_for(method_name)
end
with_config(config) click to toggle source

@param [Configuration] config The configuration object to use. @return [Core::Client] Returns a new client object with the given

configuration.

@private

# File lib/aws/core/client.rb, line 138
def with_config config
  self.class.new(:config => config)
end
with_http_handler(handler = nil, &blk) click to toggle source

Returns a copy of the client with a different HTTP handler. You can pass an object like BuiltinHttpHandler or you can use a block; for example:

s3_with_logging = s3.with_http_handler do |request, response|
  $stderr.puts request.inspect
  super(request, response)
  $stderr.puts response.inspect
end

The block executes in the context of an HttpHandler instance, and super delegates to the HTTP handler used by this client. This provides an easy way to spy on requests and responses. See HttpHandler, HttpRequest, and HttpResponse for more details on how to implement a fully functional HTTP handler using a different HTTP library than the one that ships with Ruby. @param handler (nil) A new http handler. Leave blank and pass a

block to wrap the current handler with the block.

@return [Core::Client] Returns a new instance of the client class with

the modified or wrapped http handler.
# File lib/aws/core/client.rb, line 117
def with_http_handler(handler = nil, &blk)
  handler ||= Http::Handler.new(@http_handler, &blk)
  with_options(:http_handler => handler)
end
with_options(options) click to toggle source

Returns a new client with the passed configuration options merged with the current configuration options.

no_retry_client = client.with_options(:max_retries => 0)

@param [Hash] options @option (see AWS.config) @return [Client]

# File lib/aws/core/client.rb, line 130
def with_options options
  with_config(config.with(options))
end

Protected Instance Methods

async_request_with_retries(response, http_request, retry_delays = nil) click to toggle source
# File lib/aws/core/client.rb, line 194
def async_request_with_retries response, http_request, retry_delays = nil

  response.http_response = Http::Response.new

  handle = Object.new
  handle.extend AsyncHandle
  handle.on_complete do |status|
    case status
    when :failure
      response.error = StandardError.new("failed to contact the service")
      response.signal_failure
    when :success
      populate_error(response)
      retry_delays ||= sleep_durations(response)
      if should_retry?(response) and !retry_delays.empty?
        rebuild_http_request(response)
        @http_handler.sleep_with_callback(retry_delays.shift) do
          async_request_with_retries(response, response.http_request, retry_delays)
        end
      else
        response.error ?
          response.signal_failure :
          response.signal_success
      end
    end
  end

  @http_handler.handle_async(http_request, response.http_response, handle)

end
build_request(name, options) click to toggle source
# File lib/aws/core/client.rb, line 475
def build_request name, options

  # we dont want to pass the async option to the configure block
  opts = options.dup
  opts.delete(:async)

  http_request = new_request
  http_request.access_key_id = credential_provider.access_key_id

  # configure the http request
  http_request.service_ruby_name = service_ruby_name
  http_request.default_read_timeout = http_read_timeout
  http_request.host = endpoint
  http_request.port = port
  http_request.region = config.send(:"#{service_ruby_name}_region")
  http_request.proxy_uri = config.proxy_uri
  http_request.use_ssl = config.use_ssl?
  http_request.ssl_verify_peer = config.ssl_verify_peer?
  http_request.ssl_ca_file = config.ssl_ca_file if config.ssl_ca_file
  http_request.ssl_ca_path = config.ssl_ca_path if config.ssl_ca_path

  send("configure_#{name}_request", http_request, opts)

  http_request.headers["user-agent"] = user_agent_string
  http_request.add_authorization!(credential_provider)

  http_request

end
cacheable_request?(name, options) click to toggle source
# File lib/aws/core/client.rb, line 471
def cacheable_request? name, options
  self.class::CACHEABLE_REQUESTS.include?(name)
end
client_request(name, options, &read_block) click to toggle source
# File lib/aws/core/client.rb, line 418
def client_request name, options, &read_block
  return_or_raise(options) do
    log_client_request(options) do

      if config.stub_requests?

        response = stub_for(name)
        response.http_request = build_request(name, options)
        response.request_options = options
        response

      else

        client = self

        response = new_response do
          client.send(:build_request, name, options)
        end

        response.request_type = name
        response.request_options = options

        if
          cacheable_request?(name, options) and
          cache = AWS.response_cache and
          cached_response = cache.cached(response)
        then
          cached_response.cached = true
          cached_response
        else
          # process the http request
          options[:async] ?
          make_async_request(response, &read_block) :
            make_sync_request(response, &read_block)

          # process the http response
          response.on_success do
            send("process_#{name}_response", response)
            if cache = AWS.response_cache
              cache.add(response)
            end
          end

          response

        end

      end

    end
  end
end
error_class(error_code) click to toggle source

Given an error code string, this method will return an error class.

AWS::EC2::Client.new.send(:error_code, 'InvalidInstanceId')
#=> AWS::EC2::Errors::InvalidInstanceId

@param [String] error_code The error code string as returned by

the service.  If this class contains periods, they will be
converted into namespaces (e.g. 'Foo.Bar' becomes Errors::Foo::Bar).

@return [Class]

# File lib/aws/core/client.rb, line 403
def error_class error_code
  errors_module.error_class(error_code)
end
errors_module() click to toggle source

Returns the ::Errors module for the current client.

AWS::S3::Client.new.errors_module
#=> AWS::S3::Errors

@return [Module]

# File lib/aws/core/client.rb, line 414
def errors_module
  AWS.const_get(self.class.to_s[%r(\w+)::Client/, 1])::Errors
end
expired_credentials?(response) click to toggle source

@return [Boolean] Returns true if the response contains an

error message that indicates credentials have expired.
# File lib/aws/core/client.rb, line 309
def expired_credentials? response
  response.error and
  response.error.respond_to?(:code) and
  response.error.code == 'ExpiredTokenException'
end
extract_error_details(response) click to toggle source

Extracts the error code and error message from a response if it contains an error. Returns nil otherwise. Should be defined in sub-classes (e.g. QueryClient, RESTClient, etc). @param [Response] response @return [Array<Code,Message>,nil] Should return an array with an

error code and message, or +nil+.
# File lib/aws/core/client.rb, line 388
def extract_error_details response
  raise NotImplementedError
end
log_client_request(options) { || ... } click to toggle source

Yields to the given block (which should be making a request and returning a {Response} object). The results of the request/response are logged.

@param [Hash] options @option options [Boolean] :async @return [Response]

# File lib/aws/core/client.rb, line 330
def log_client_request options, &block

  # time the request, retries and all
  start = Time.now
  response = yield
  response.duration = Time.now - start

  if options[:async]
    response.on_complete { log_response(response) }
  else
    log_response(response)
  end

  response

end
log_response(response) click to toggle source

Logs the response to the configured logger. @param [Response] response @return [nil]

# File lib/aws/core/client.rb, line 350
def log_response response
  if config.logger
    message = config.log_formatter.format(response)
    config.logger.send(config.log_level, message)
  end
  nil
end
make_async_request(response) click to toggle source
# File lib/aws/core/client.rb, line 186
def make_async_request response

  pauses = async_request_with_retries(response, response.http_request)

  response

end
make_sync_request(response) { |http_response.body| ... } click to toggle source
# File lib/aws/core/client.rb, line 225
def make_sync_request response, &read_block
  retry_server_errors do

    response.http_response = Http::Response.new

    @http_handler.handle(
      response.http_request,
      response.http_response,
      &read_block)

    if
      block_given? and
      response.http_response.status < 300 and
      response.http_response.body
    then

      msg = ":http_handler read the entire http response body into "
      msg << "memory, it should have instead yielded chunks"
      log_warning(msg)

      # go ahead and yield the body on behalf of the handler
      yield(response.http_response.body)

    end

    populate_error(response)
    response.signal_success unless response.error
    response

  end
end
new_request() click to toggle source
# File lib/aws/core/client.rb, line 178
def new_request
  eval(self.class.name.sub(%r::Client$/, ''))::Request.new
end
new_response(*args, &block) click to toggle source
# File lib/aws/core/client.rb, line 182
def new_response(*args, &block)
  Response.new(*args, &block)
end
populate_error(response) click to toggle source
# File lib/aws/core/client.rb, line 358
def populate_error response

  status = response.http_response.status

  error_code, error_message = extract_error_details(response)

  error_args = [
    response.http_request,
    response.http_response,
    error_code,
    error_message
  ]

  response.error =
    case
    when response.network_error? then NetworkError.new
    when error_code then error_class(error_code).new(*error_args)
    when status >= 500 then Errors::ServerError.new(*error_args)
    when status >= 300 then Errors::ClientError.new(*error_args)
    else nil # no error
    end

end
rebuild_http_request(response) click to toggle source
# File lib/aws/core/client.rb, line 273
def rebuild_http_request response
  credential_provider.refresh if expired_credentials?(response)
  response.rebuild_request
  response.retry_count += 1
end
retry_server_errors() { || ... } click to toggle source
# File lib/aws/core/client.rb, line 257
def retry_server_errors &block

  response = yield

  sleeps = sleep_durations(response)
  while should_retry?(response)
    break if sleeps.empty?
    Kernel.sleep(sleeps.shift)
    rebuild_http_request(response)
    response = yield
  end

  response

end
retryable_error?(response) click to toggle source
# File lib/aws/core/client.rb, line 300
def retryable_error? response
  expired_credentials?(response) or
  response.network_error? or
  response.throttled? or
  response.error.kind_of?(Errors::ServerError)
end
return_or_raise(options) { || ... } click to toggle source
# File lib/aws/core/client.rb, line 315
def return_or_raise options, &block
  response = yield
  unless options[:async]
    raise response.error if response.error
  end
  response
end
scaling_factor(response) click to toggle source
# File lib/aws/core/client.rb, line 288
def scaling_factor response
  response.throttled? ? (0.5 + Kernel.rand * 0.1) : 0.3
end
should_retry?(response) click to toggle source
# File lib/aws/core/client.rb, line 292
def should_retry? response
  if retryable_error?(response)
    response.safe_to_retry?
  else
    false
  end
end
sleep_durations(response) click to toggle source
# File lib/aws/core/client.rb, line 279
def sleep_durations response
  if expired_credentials?(response)
    [0]
  else
    factor = scaling_factor(response)
    Array.new(config.max_retries) {|n| (2 ** n) * factor }
  end
end
user_agent_string() click to toggle source
# File lib/aws/core/client.rb, line 505
def user_agent_string
  engine = (RUBY_ENGINE rescue nil or "ruby")
  user_agent = "%s aws-sdk-ruby/#{VERSION} %s/%s %s" %
    [config.user_agent_prefix, engine, RUBY_VERSION, RUBY_PLATFORM]
  user_agent.strip!
  if AWS.memoizing?
    user_agent << " memoizing"
  end
  user_agent
end