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.

endpoint[R]

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

makes requests against.

@private

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

signer[R]

@return [DefaultSigner,Object] Returns the signer for this client.

This is normally a DefaultSigner, but it can be configured to
an other object.

@private

Public Class Methods

new(options = {}) click to toggle source

Creates a new low-level client.

Required Options

To create a client you must provide access to AWS credentials. There are two options:

  • :signer -- An object that responds to access_key_id (to return the AWS Access Key ID) and to sign(string_to_sign) (to return a signature for a given string). An example implementation is AWS::Core::DefaultSigner. This option is useful if you want to more tightly control access to your secret access key (for example by moving the signature computation into a different process).

  • :access_key_id and :secret_access_key -- You can use these options to provide the AWS Access Key ID and AWS Secret Access Key directly to the client.

Optional

  • :http_handler -- Any object that implements a handle(request, response) method; an example is BuiltinHttpHandler. This method is used to perform the HTTP requests that this client constructs.

# File lib/aws/core/client.rb, line 57
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)
  @config ||= AWS.config
  @config = @config.with(options)

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

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 470
      def self.add_client_request_method method_name, options = {}, &block

        self.operations << method_name
  
        ClientRequestMethodBuilder.new(self, method_name, &block)
  
        module_eval "          def #{method_name}(*args, &block)
            options = args.first ? args.first : {}
            client_request(#{method_name.inspect}, options, &block)
          end
"
  
      end
api_config() click to toggle source

Parses the service's API configuration yaml file. This file has configuration that drives the request and response DSLs. @return [Hash]

# File lib/aws/core/client.rb, line 488
def self.api_config
  config_file = 
    File.dirname(File.dirname(__FILE__)) + 
    "/api_config/#{service_name}-#{self::API_VERSION}.yml"
  YAML.load(File.read(config_file))
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 497
def self.operations
  @operations ||= []
end

Public Instance Methods

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 160
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 106
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 151
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.
# File lib/aws/core/client.rb, line 144
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
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 130
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

@param [Hash] options @see AWS.config detailed list of accepted options.

# File lib/aws/core/client.rb, line 137
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 187
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, &block) click to toggle source
# File lib/aws/core/client.rb, line 424
def build_request(name, options, &block)

  # we dont want to pass the async option to the configure block
  opts = options.dup
  opts.delete(:async)
  
  http_request = new_request
  
  # configure the http request
  http_request.service_ruby_name = service_ruby_name
  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, &block)

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

  http_request

end
cacheable_request?(name, options) click to toggle source
# File lib/aws/core/client.rb, line 420
def cacheable_request? name, options
  self.class::CACHEABLE_REQUESTS.include?(name)
end
client_request(name, options, &block) click to toggle source
# File lib/aws/core/client.rb, line 371
def client_request name, options, &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, &block)
        response.request_options = options
        response
  
      else
  
        client = self
        response = new_response { client.send(:build_request, name, options, &block) }
        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) :
            make_sync_request(response)
  
          # 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 356
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 367
def errors_module
  AWS.const_get(self.class.to_s[%r(\w+)::Client/, 1])::Errors
end
extract_error(response) click to toggle source

If the response contains error, this method will construct and return an error object. If no error is contained in the response, then nil is returned. @param [Response] response @return [Errors::Base,nil]

# File lib/aws/core/client.rb, line 322
def extract_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
  ]

  case
  when response.timeout? then TimeoutError.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
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 285
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 [Resposne] response @return [nil]

# File lib/aws/core/client.rb, line 305
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 179
def make_async_request response
  
  pauses = async_request_with_retries(response, response.http_request)
  
  response
  
end
make_sync_request(response) click to toggle source
# File lib/aws/core/client.rb, line 218
def make_sync_request response
  retry_server_errors do
  
    response.http_response = http_response =
      Http::Response.new
  
    @http_handler.handle(response.http_request, http_response)

    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 171
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 175
def new_response(*args, &block)
  Response.new(*args, &block)
end
populate_error(response) click to toggle source
# File lib/aws/core/client.rb, line 313
def populate_error response
  response.error = extract_error(response)
end
rebuild_http_request(response) click to toggle source
# File lib/aws/core/client.rb, line 250
def rebuild_http_request response
  response.rebuild_request
  response.retry_count += 1
end
retry_server_errors() { || ... } click to toggle source
# File lib/aws/core/client.rb, line 233
def retry_server_errors &block
  
  response = yield
  
  sleeps = sleep_durations(response)
  while should_retry?(response)
    break if sleeps.empty?
    Kernel.sleep(sleeps.shift)
    # rebuild the request to get a fresh signature
    rebuild_http_request(response)
    response = yield
  end
  
  response
  
end
return_or_raise(options) { || ... } click to toggle source
# File lib/aws/core/client.rb, line 270
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 260
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 264
def should_retry? response
  response.timeout? or
    response.throttled? or
    response.error.kind_of?(Errors::ServerError)
end
sleep_durations(response) click to toggle source
# File lib/aws/core/client.rb, line 255
def sleep_durations response
  factor = scaling_factor(response)
  Array.new(config.max_retries) {|n| (2 ** n) * factor }
end
user_agent_string() click to toggle source
# File lib/aws/core/client.rb, line 452
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