X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/a49e63847551b2a97603b0ba19fe7ce60f062f59..9c7e953d1b88d1a338f2a290f10f17ee4721710c:/lib/google/api_client.rb diff --git a/lib/google/api_client.rb b/lib/google/api_client.rb index f3d1f521ac..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' @@ -31,7 +31,8 @@ require 'google/api_client/media' require 'google/api_client/service_account' require 'google/api_client/batch' require 'google/api_client/gzip' -require 'google/api_client/railtie' if defined?(Rails::Railtie) +require 'google/api_client/client_secrets' +require 'google/api_client/railtie' if defined?(Rails) module Google @@ -107,6 +108,7 @@ 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 = {} @@ -117,7 +119,7 @@ module Google faraday.ssl.ca_file = ca_file faraday.ssl.verify = true faraday.adapter Faraday.default_adapter - end + end return self end @@ -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' @@ -533,6 +544,8 @@ module Google # 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. # @@ -547,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 || {} @@ -572,53 +585,55 @@ module Google 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, true) - 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. # @@ -645,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'