class AWS::Core::Client
Base client class for all of the Amazon AWS
service clients.
Constants
- CACHEABLE_REQUESTS
@private
Attributes
@return [Configuration] This clients configuration.
@return [CredentialProviders::Provider] Returns the credential
provider for this client.
@private
@return [String] Returns the service endpoint (hostname) this client
makes requests against.
@private
@return [Integer] The number of seconds before requests made by
this client should timeout if they have not received a response.
@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 [String] Returns the service path this client
makes requests against.
@private
Public Class Methods
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 41 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") if config.send(:"#{service_ruby_name}_service_path") <=> "/" @uri = config.send(:"#{service_ruby_name}_service_path") end @http_read_timeout = @config.http_read_timeout end
Protected Class Methods
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 590 def add_client_request_method method_name, options = {}, &block operations << method_name ClientRequestMethodBuilder.new(self, method_name, &block) method_def = <<-METHOD def #{method_name}(*args, &block) options = args.first ? args.first : {} client_request(#{method_name.inspect}, options, &block) end METHOD module_eval(method_def) end
# File lib/aws/core/client.rb, line 636 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
Defines one method for each service operation described in the API configuration. @param [String] api_version
# File lib/aws/core/client.rb, line 620 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
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 611 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
@return [Array<Symbol>] Returns a list of service operations as
method names supported by this client.
# File lib/aws/core/client.rb, line 557 def operations @operations ||= [] end
Define this in sub-classes (e.g. QueryClient
, RESTClient
, etc)
# File lib/aws/core/client.rb, line 574 def request_builder_for api_config, operation raise NotImplementedError end
@private
# File lib/aws/core/client.rb, line 562 def request_builders @request_builders ||= {} end
Define this in sub-classes (e.g. QueryClient
, RESTClient
, etc)
# File lib/aws/core/client.rb, line 579 def response_parser_for api_config, operation raise NotImplementedError end
@private
# File lib/aws/core/client.rb, line 567 def response_parsers @response_parsers ||= {} end
Public Instance Methods
Logs the warning to the configured logger, otherwise to stderr. @param [String] warning @return [nil]
# File lib/aws/core/client.rb, line 175 def log_warning warning message = '[aws-sdk-gem-warning] ' + warning if config.logger config.logger.warn(message) else $stderr.puts(message) end nil end
Primarily used for testing, this method returns an empty pseudo service response without making a request. Its used primarily for testing the lighter level service interfaces. @private
# File lib/aws/core/client.rb, line 163 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 Client.operations
)
# File lib/aws/core/client.rb, line 101 def operations self.class.operations end
The stub returned is memoized. @see new_stub_for
@private
# File lib/aws/core/client.rb, line 154 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.
@private
# File lib/aws/core/client.rb, line 147 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(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 126 def with_http_handler(handler = nil, &blk) handler ||= Http::Handler.new(@http_handler, &blk) with_options(:http_handler => handler) end
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 139 def with_options options with_config(config.with(options)) end
Protected Instance Methods
# File lib/aws/core/client.rb, line 203 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 511 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.uri = uri 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
# File lib/aws/core/client.rb, line 507 def cacheable_request? name, options self.class::CACHEABLE_REQUESTS.include?(name) end
# File lib/aws/core/client.rb, line 447 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 # close files we opened response.on_complete do if response.http_request.body_stream.is_a?(ManagedFile) response.http_request.body_stream.close 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 432 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 443 def errors_module AWS.const_get(self.class.to_s[/(\w+)::Client/, 1])::Errors end
@return [Boolean] Returns true
if the response contains an
error message that indicates credentials have expired.
# File lib/aws/core/client.rb, line 328 def expired_credentials? response response.error and response.error.respond_to?(:code) and (response.error.code == 'ExpiredTokenException' || response.error.code == 'ExpiredToken') end
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 417 def extract_error_details response raise NotImplementedError 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 359 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 [Response] response @return [nil]
# File lib/aws/core/client.rb, line 379 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 195 def make_async_request response pauses = async_request_with_retries(response, response.http_request) response end
# File lib/aws/core/client.rb, line 234 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
# File lib/aws/core/client.rb, line 187 def new_request eval(self.class.name.sub(/::Client$/, ''))::Request.new end
# File lib/aws/core/client.rb, line 191 def new_response(*args, &block) Response.new(*args, &block) end
# File lib/aws/core/client.rb, line 387 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 response.http_response.network_error 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
# File lib/aws/core/client.rb, line 282 def rebuild_http_request response credential_provider.refresh if expired_credentials?(response) response.rebuild_request if redirected?(response) loc = URI.parse(response.http_response.headers['location'].first) AWS::Core::MetaUtils.extend_method(response.http_request, :host) do loc.host end response.http_request.host = loc.host response.http_request.port = loc.port response.http_request.uri = loc.path end response.retry_count += 1 end
# File lib/aws/core/client.rb, line 340 def redirected? response response.http_response.status == 307 end
# File lib/aws/core/client.rb, line 266 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
# File lib/aws/core/client.rb, line 318 def retryable_error? response expired_credentials?(response) or response.network_error? or throttled?(response) or redirected?(response) or response.error.kind_of?(Errors::ServerError) end
# File lib/aws/core/client.rb, line 344 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 306 def scaling_factor response throttled?(response) ? (0.5 + Kernel.rand * 0.1) : 0.3 end
# File lib/aws/core/client.rb, line 310 def should_retry? response if retryable_error?(response) response.safe_to_retry? else false end end
# File lib/aws/core/client.rb, line 297 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
# File lib/aws/core/client.rb, line 334 def throttled? response response.error and response.error.respond_to?(:code) and response.error.code.to_s.match(/Throttling/i) end
# File lib/aws/core/client.rb, line 542 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