X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/f63888ab61f0ca4d1b5c8e5a5e25030725682355..9c7e953d1b88d1a338f2a290f10f17ee4721710c:/lib/google/api_client.rb diff --git a/lib/google/api_client.rb b/lib/google/api_client.rb index 8904da03f4..dc9c853790 100644 --- a/lib/google/api_client.rb +++ b/lib/google/api_client.rb @@ -14,10 +14,10 @@ require 'faraday' -require 'faraday/utils' require 'multi_json' require 'compat/multi_json' require 'stringio' +require 'retriable' require 'google/api_client/version' require 'google/api_client/logging' @@ -30,7 +30,9 @@ require 'google/api_client/result' require 'google/api_client/media' require 'google/api_client/service_account' require 'google/api_client/batch' -require 'google/api_client/railtie' if defined?(Rails::Railtie) +require 'google/api_client/gzip' +require 'google/api_client/client_secrets' +require 'google/api_client/railtie' if defined?(Rails) module Google @@ -97,8 +99,7 @@ module Google end self.user_agent = options[:user_agent] || ( "#{application_string} " + - "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} " + - ENV::OS_VERSION + "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION} (gzip)" ).strip # The writer method understands a few Symbols and will generate useful # default authentication mechanisms. @@ -107,16 +108,18 @@ module Google self.auto_refresh_token = options.fetch(:auto_refresh_token) { true } self.key = options[:key] self.user_ip = options[:user_ip] + self.retries = options.fetch(:retries) { 0 } @discovery_uris = {} @discovery_documents = {} @discovered_apis = {} ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__) self.connection = Faraday.new do |faraday| + faraday.response :gzip faraday.options.params_encoder = Faraday::FlatParamsEncoder faraday.ssl.ca_file = ca_file faraday.ssl.verify = true faraday.adapter Faraday.default_adapter - end + end return self end @@ -176,7 +179,6 @@ module Google return @authorization end - ## # Default Faraday/HTTP connection. # @@ -230,6 +232,13 @@ module Google # The base path. Should almost always be '/discovery/v1'. attr_accessor :discovery_path + ## + # Number of times to retry on recoverable errors + # + # @return [FixNum] + # Number of retries + attr_accessor :retries + ## # Returns the URI for the directory document. # @@ -424,6 +433,8 @@ module Google # Verifies an ID token against a server certificate. Used to ensure that # an ID token supplied by an untrusted client-side mechanism is valid. # Raises an error if the token is invalid or missing. + # + # @deprecated Use the google-id-token gem for verifying JWTs def verify_id_token! require 'jwt' require 'openssl' @@ -531,6 +542,10 @@ module Google # - (TrueClass, FalseClass) :authenticated (default: true) - # `true` if the request must be signed or somehow # authenticated, `false` otherwise. + # - (TrueClass, FalseClass) :gzip (default: true) - + # `true` if gzip enabled, `false` otherwise. + # - (FixNum) :retries - + # # of times to retry on recoverable errors # # @return [Google::APIClient::Result] The result from the API, nil if batch. # @@ -545,7 +560,7 @@ module Google # ) # # @see Google::APIClient#generate_request - def execute(*params) + def execute!(*params) if params.first.kind_of?(Google::APIClient::Request) request = params.shift options = params.shift || {} @@ -569,53 +584,56 @@ module Google end request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil? + request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false + request.headers['Content-Type'] ||= '' request.parameters['key'] ||= self.key unless self.key.nil? request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil? connection = options[:connection] || self.connection request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false - - result = request.send(connection) - if result.status == 401 && request.authorization.respond_to?(:refresh_token) && auto_refresh_token - begin - logger.debug("Attempting refresh of access token & retry of request") - request.authorization.fetch_access_token! - result = request.send(connection) - rescue Signet::AuthorizationError - # Ignore since we want the original error + + tries = 1 + (options[:retries] || self.retries) + + Retriable.retriable :tries => tries, + :on => [TransmissionError], + :on_retry => client_error_handler(request.authorization), + :interval => lambda {|attempts| (2 ** attempts) + rand} do + result = request.send(connection, true) + + case result.status + when 200...300 + result + when 301, 302, 303, 307 + request = generate_request(request.to_hash.merge({ + :uri => result.headers['location'], + :api_method => nil + })) + raise RedirectError.new(result.headers['location'], result) + when 400...500 + raise ClientError.new(result.error_message || "A client error has occurred", result) + when 500...600 + raise ServerError.new(result.error_message || "A server error has occurred", result) + else + raise TransmissionError.new(result.error_message || "A transmission error has occurred", result) end end - - return result end ## - # Same as Google::APIClient#execute, but raises an exception if there was - # an error. + # Same as Google::APIClient#execute!, but does not raise an exception for + # normal API errros. # # @see Google::APIClient#execute - def execute!(*params) - result = self.execute(*params) - if result.error? - error_message = result.error_message - case result.response.status - when 400...500 - exception_type = ClientError - error_message ||= "A client error has occurred." - when 500...600 - exception_type = ServerError - error_message ||= "A server error has occurred." - else - exception_type = TransmissionError - error_message ||= "A transmission error has occurred." - end - raise exception_type, error_message + def execute(*params) + begin + return self.execute!(*params) + rescue TransmissionError => e + return e.result end - return result end - + protected - + ## # Resolves a URI template against the client's configured base. # @@ -642,7 +660,33 @@ module Google return Addressable::Template.new(@base_uri + template).expand(mapping) end + + ## + # Returns on proc for special processing of retries as not all client errors + # are recoverable. Only 401s should be retried and only if the credentials + # are refreshable + # + # @param [#fetch_access_token!] authorization + # OAuth 2 credentials + # @return [Proc] + def client_error_handler(authorization) + can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token + Proc.new do |exception, tries| + next unless exception.kind_of?(ClientError) + if exception.result.status == 401 && can_refresh && tries == 1 + begin + logger.debug("Attempting refresh of access token & retry of request") + authorization.fetch_access_token! + next + rescue Signet::AuthorizationError + end + end + raise exception + end + end + end + end require 'google/api_client/version'