Base client class for all of the Amazon AWS service clients.
@private
@return [Configuration] This clients configuration.
@return [String] Returns the service endpoint (hostname) this client
makes requests against.
@private
@return [Integer] What port this client makes requests via. @private
@return [String] The snake-cased ruby name for the service
(e.g. 's3', 'iam', 'dynamo_db', etc).
@private
@return [DefaultSigner,Object] Returns the signer for this client.
This is normally a DefaultSigner, but it can be configured to an other object.
@private
Creates a new low-level client.
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.
: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
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
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
@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
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
@return (see #operations)
# File lib/aws/core/client.rb, line 106 def operations self.class.operations end
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
@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
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
@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
# 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
# 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
# File lib/aws/core/client.rb, line 420 def cacheable_request? name, options self.class::CACHEABLE_REQUESTS.include?(name) end
# 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
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
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
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
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
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
# File lib/aws/core/client.rb, line 179 def make_async_request response pauses = async_request_with_retries(response, response.http_request) response end
# 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
# File lib/aws/core/client.rb, line 171 def new_request eval(self.class.name.sub(%r::Client$/, ''))::Request.new end
# File lib/aws/core/client.rb, line 175 def new_response(*args, &block) Response.new(*args, &block) end
# File lib/aws/core/client.rb, line 313 def populate_error response response.error = extract_error(response) end
# File lib/aws/core/client.rb, line 250 def rebuild_http_request response response.rebuild_request response.retry_count += 1 end
# 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
# 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
# File lib/aws/core/client.rb, line 260 def scaling_factor response response.throttled? ? (0.5 + Kernel.rand * 0.1) : 0.3 end
# 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
# 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
# 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