Class: Google::APIClient

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/google/api_client.rb,
lib/google/api_client/gzip.rb,
lib/google/api_client/batch.rb,
lib/google/api_client/media.rb,
lib/google/api_client/errors.rb,
lib/google/api_client/result.rb,
lib/google/api_client/charset.rb,
lib/google/api_client/logging.rb,
lib/google/api_client/railtie.rb,
lib/google/api_client/request.rb,
lib/google/api_client/service.rb,
lib/google/api_client/version.rb,
lib/google/api_client/reference.rb,
lib/google/api_client/auth/pkcs12.rb,
lib/google/api_client/environment.rb,
lib/google/api_client/auth/storage.rb,
lib/google/api_client/discovery/api.rb,
lib/google/api_client/service/batch.rb,
lib/google/api_client/auth/key_utils.rb,
lib/google/api_client/client_secrets.rb,
lib/google/api_client/service/result.rb,
lib/google/api_client/discovery/media.rb,
lib/google/api_client/service/request.rb,
lib/google/api_client/discovery/method.rb,
lib/google/api_client/discovery/schema.rb,
lib/google/api_client/service/resource.rb,
lib/google/api_client/auth/file_storage.rb,
lib/google/api_client/auth/jwt_asserter.rb,
lib/google/api_client/auth/installed_app.rb,
lib/google/api_client/discovery/resource.rb,
lib/google/api_client/service/stub_generator.rb,
lib/google/api_client/auth/storages/file_store.rb,
lib/google/api_client/auth/storages/redis_store.rb,
lib/google/api_client/service/simple_file_store.rb,
lib/google/api_client/auth/compute_service_account.rb

Overview

This class manages APIs communication.

Defined Under Namespace

Modules: ENV, KeyUtils, Logging, PKCS12, Schema, VERSION Classes: API, AuthorizationError, BatchError, BatchRequest, BatchedCallResponse, Charset, ClientError, ClientSecrets, ComputeServiceAccount, FileStorage, FileStore, Gzip, InstalledAppFlow, InvalidIDTokenError, JWTAsserter, MediaUpload, Method, Railtie, RangedIO, RedirectError, RedisStore, Reference, Request, Resource, Result, ResumableUpload, ServerError, Service, Storage, TransmissionError, UploadIO, ValidationError

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#logger

Constructor Details

#initialize(options = {}) ⇒ APIClient

Creates a new Google API client.

Parameters:

  • options (Hash) (defaults to: {})

    The configuration parameters for the client.

Options Hash (options):

  • :authorization (Symbol, #generate_authenticated_request) — default: :oauth_1

    The authorization mechanism used by the client. The following mechanisms are supported out-of-the-box:

    • :two_legged_oauth_1
    • :oauth_1
    • :oauth_2
  • :auto_refresh_token (Boolean) — default: true

    The setting that controls whether or not the api client attempts to refresh authorization when a 401 is hit in #execute. If the token does not support it, this option is ignored.

  • :application_name (String)

    The name of the application using the client.

  • :application_version (String)

    The version number of the application using the client.

  • :user_agent (String) — default: "{app_name} google-api-ruby-client/{version} {os_name}/{os_version}"

    The user agent used by the client. Most developers will want to leave this value alone and use the :application_name option instead.

  • :host (String) — default: "www.googleapis.com"

    The API hostname used by the client. This rarely needs to be changed.

  • :port (String) — default: 443

    The port number used by the client. This rarely needs to be changed.

  • :discovery_path (String) — default: "/discovery/v1"

    The discovery base path. This rarely needs to be changed.

  • :ca_file (String)

    Optional set of root certificates to use when validating SSL connections. By default, a bundled set of trusted roots will be used.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/google/api_client.rb', line 84

def initialize(options={})
  logger.debug { "#{self.class} - Initializing client with options #{options}" }
  
  # Normalize key to String to allow indifferent access.
  options = options.inject({}) do |accu, (key, value)|
    accu[key.to_sym] = value
    accu
  end
  # Almost all API usage will have a host of 'www.googleapis.com'.
  self.host = options[:host] || 'www.googleapis.com'
  self.port = options[:port] || 443
  self.discovery_path = options[:discovery_path] || '/discovery/v1'

  # Most developers will want to leave this value alone and use the
  # application_name option.
  if options[:application_name]
    app_name = options[:application_name]
    app_version = options[:application_version]
    application_string = "#{app_name}/#{app_version || '0.0.0'}"
  else
    logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" }
  end

  proxy = options[:proxy] || Object::ENV["http_proxy"]

  self.user_agent = options[:user_agent] || (
    "#{application_string} " +
    "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.
  self.authorization =
    options.key?(:authorization) ? options[:authorization] : :oauth_2
  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 }
  self.expired_auth_retry = options.fetch(:expired_auth_retry) { true }
  @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 :charset if options[:force_encoding]
    faraday.response :gzip
    faraday.options.params_encoder = Faraday::FlatParamsEncoder
    faraday.ssl.ca_file = ca_file
    faraday.ssl.verify = true
    faraday.proxy proxy
    faraday.adapter Faraday.default_adapter
    if options[:faraday_option].is_a?(Hash)
      options[:faraday_option].each_pair do |option, value|
        faraday.options.send("#{option}=", value)
      end
    end
  end
  return self
end

Class Attribute Details

.loggerLogger

Logger for the API client

Returns:

  • (Logger)

    logger instance.



11
12
13
# File 'lib/google/api_client/logging.rb', line 11

def logger
  @logger
end

Instance Attribute Details

#authorization#generate_authenticated_request

Returns the authorization mechanism used by the client.

Returns:

  • (#generate_authenticated_request)

    The authorization mechanism.



147
148
149
# File 'lib/google/api_client.rb', line 147

def authorization
  @authorization
end

#auto_refresh_tokenBoolean

The setting that controls whether or not the api client attempts to refresh authorization when a 401 is hit in #execute.

Returns:

  • (Boolean)


210
211
212
# File 'lib/google/api_client.rb', line 210

def auto_refresh_token
  @auto_refresh_token
end

#connectionFaraday::Connection

Default Faraday/HTTP connection.

Returns:

  • (Faraday::Connection)


203
204
205
# File 'lib/google/api_client.rb', line 203

def connection
  @connection
end

#discovery_pathString

The base path used by the client for discovery.

Returns:

  • (String)

    The base path. Should almost always be '/discovery/v1'.



250
251
252
# File 'lib/google/api_client.rb', line 250

def discovery_path
  @discovery_path
end

#expired_auth_retryBoolean

Whether or not an expired auth token should be re-acquired (and the operation retried) regardless of retries setting

Returns:

  • (Boolean)

    Auto retry on auth expiry



264
265
266
# File 'lib/google/api_client.rb', line 264

def expired_auth_retry
  @expired_auth_retry
end

#hostString

The API hostname used by the client.

Returns:



236
237
238
# File 'lib/google/api_client.rb', line 236

def host
  @host
end

#keyString

The application's API key issued by the API console.

Returns:

  • (String)

    The API key.



216
217
218
# File 'lib/google/api_client.rb', line 216

def key
  @key
end

#portString

The port number used by the client.

Returns:

  • (String)

    The port number. Should almost always be 443.



243
244
245
# File 'lib/google/api_client.rb', line 243

def port
  @port
end

#retriesFixNum

Number of times to retry on recoverable errors

Returns:

  • (FixNum)

    Number of retries



257
258
259
# File 'lib/google/api_client.rb', line 257

def retries
  @retries
end

#user_agentString

The user agent used by the client.

Returns:

  • (String)

    The user agent string used in the User-Agent header.



229
230
231
# File 'lib/google/api_client.rb', line 229

def user_agent
  @user_agent
end

#user_ipString

The IP address of the user this request is being performed on behalf of.

Returns:

  • (String)

    The user's IP address.



222
223
224
# File 'lib/google/api_client.rb', line 222

def user_ip
  @user_ip
end

Instance Method Details

#directory_documentHash

Returns the parsed directory document.

Returns:

  • (Hash)

    The parsed JSON from the directory document.



337
338
339
340
341
342
343
344
345
346
# File 'lib/google/api_client.rb', line 337

def directory_document
  return @directory_document ||= (begin
    response = self.execute!(
      :http_method => :get,
      :uri => self.directory_uri,
      :authenticated => false
    )
    response.data
  end)
end

#directory_uriAddressable::URI

Returns the URI for the directory document.

Returns:

  • (Addressable::URI)

    The URI of the directory document.



270
271
272
# File 'lib/google/api_client.rb', line 270

def directory_uri
  return resolve_uri(self.discovery_path + '/apis')
end

#discovered_api(api, version = nil) ⇒ Google::APIClient::API

Returns the service object for a given service name and service version.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/google/api_client.rb', line 394

def discovered_api(api, version=nil)
  if !api.kind_of?(String) && !api.kind_of?(Symbol)
    raise TypeError,
      "Expected String or Symbol, got #{api.class}."
  end
  api = api.to_s
  version = version || 'v1'
  return @discovered_apis["#{api}:#{version}"] ||= begin
    document_base = self.discovery_uri(api, version)
    discovery_document = self.discovery_document(api, version)
    if document_base && discovery_document
      Google::APIClient::API.new(
        document_base,
        discovery_document
      )
    else
      nil
    end
  end
end

#discovered_apisArray

Returns all APIs published in the directory document.

Returns:

  • (Array)

    The list of available APIs.



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/google/api_client.rb', line 371

def discovered_apis
  @directory_apis ||= (begin
    document_base = self.directory_uri
    if self.directory_document && self.directory_document['items']
      self.directory_document['items'].map do |discovery_document|
        Google::APIClient::API.new(
          document_base,
          discovery_document
        )
      end
    else
      []
    end
  end)
end

#discovered_method(rpc_name, api, version = nil) ⇒ Google::APIClient::Method

Returns the method object for a given RPC name and service version.

Parameters:

  • rpc_name (String, Symbol)

    The RPC name of the desired method.

  • api (String, Symbol)

    The API the method is within.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/google/api_client.rb', line 423

def discovered_method(rpc_name, api, version=nil)
  if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
    raise TypeError,
      "Expected String or Symbol, got #{rpc_name.class}."
  end
  rpc_name = rpc_name.to_s
  api = api.to_s
  version = version || 'v1'
  service = self.discovered_api(api, version)
  if service.to_h[rpc_name]
    return service.to_h[rpc_name]
  else
    return nil
  end
end

#discovery_document(api, version = nil) ⇒ Hash

Returns the parsed discovery document.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:

  • (Hash)

    The parsed JSON from the discovery document.



354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/google/api_client.rb', line 354

def discovery_document(api, version=nil)
  api = api.to_s
  version = version || 'v1'
  return @discovery_documents["#{api}:#{version}"] ||= (begin
    response = self.execute!(
      :http_method => :get,
      :uri => self.discovery_uri(api, version),
      :authenticated => false
    )
    response.data
  end)
end

#discovery_uri(api, version = nil) ⇒ Addressable::URI

Returns the URI for the discovery document.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String) (defaults to: nil)

    The desired version of the API.

Returns:

  • (Addressable::URI)

    The URI of the discovery document.



295
296
297
298
299
300
301
302
303
304
305
# File 'lib/google/api_client.rb', line 295

def discovery_uri(api, version=nil)
  api = api.to_s
  version = version || 'v1'
  return @discovery_uris["#{api}:#{version}"] ||= (
    resolve_uri(
      self.discovery_path + '/apis/{api}/{version}/rest',
      'api' => api,
      'version' => version
    )
  )
end

#execute(*params) ⇒ Object

Same as Google::APIClient#execute!, but does not raise an exception for normal API errros.

See Also:



667
668
669
670
671
672
673
# File 'lib/google/api_client.rb', line 667

def execute(*params)
  begin
    return self.execute!(*params)
  rescue TransmissionError => e
    return e.result
  end
end

#execute!(*params) ⇒ Google::APIClient::Result

Executes a request, wrapping it in a Result object.

Examples:

result = client.execute(batch_request)
plus = client.discovered_api('plus')
result = client.execute(
  :api_method => plus.activities.list,
  :parameters => {'collection' => 'public', 'userId' => 'me'}
)

Parameters:

  • params (Google::APIClient::Request, Hash, Array)

    Either a Google::APIClient::Request, a Hash, or an Array.

    If a Google::APIClient::Request, no other parameters are expected.

    If a Hash, the below parameters are handled. If an Array, the parameters are assumed to be in the below order:

    • (Google::APIClient::Method) api_method: The method object or the RPC name of the method being executed.
    • (Hash, Array) parameters: The parameters to send to the method.
    • (String) body: The body of the request.
    • (Hash, Array) headers: The HTTP headers for the request.
    • (Hash) options: A set of options for the request, of which:
      • (#generate_authenticated_request) :authorization (default: true) - The authorization mechanism for the response. Used only if :authenticated is true.
      • (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

Returns:

See Also:



591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
# File 'lib/google/api_client.rb', line 591

def execute!(*params)
  if params.first.kind_of?(Google::APIClient::Request)
    request = params.shift
    options = params.shift || {}
  else
    # This block of code allows us to accept multiple parameter passing
    # styles, and maintaining some backwards compatibility.
    #
    # Note: I'm extremely tempted to deprecate this style of execute call.
    if params.last.respond_to?(:to_hash) && params.size == 1
      options = params.pop
    else
      options = {}
    end

    options[:api_method] = params.shift if params.size > 0
    options[:parameters] = params.shift if params.size > 0
    options[:body] = params.shift if params.size > 0
    options[:headers] = params.shift if params.size > 0
    options.update(params.shift) if params.size > 0
    request = self.generate_request(options)
  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
  
  tries = 1 + (options[:retries] || self.retries)
  attempt = 0

  Retriable.retriable :tries => tries, 
                      :on => [TransmissionError],
                      :on_retry => client_error_handler,
                      :interval => lambda {|attempts| (2 ** attempts) + rand} do
    attempt += 1

    # This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
    # auth to be re-attempted without having to retry all sorts of other failures like
    # NotFound, etc
    Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1, 
                        :on => [AuthorizationError],
                        :on_retry => authorization_error_handler(request.authorization) 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 401
          raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result)
        when 400, 402...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
  end
end

#generate_request(options = {}) ⇒ Google::APIClient::Reference

Generates a request.

Examples:

request = client.generate_request(
  :api_method => 'plus.activities.list',
  :parameters =>
    {'collection' => 'public', 'userId' => 'me'}
)

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :api_method (Google::APIClient::Method)

    The method object or the RPC name of the method being executed.

  • :parameters (Hash, Array)

    The parameters to send to the method.

  • :headers (Hash, Array)

    The HTTP headers for the request.

  • :body (String)

    The body of the request.

  • :version (String) — default: "v1"

    The service version. Only used if api_method is a String.

  • :authorization (#generate_authenticated_request)

    The authorization mechanism for the response. Used only if :authenticated is true.

  • :authenticated (TrueClass, FalseClass) — default: true

    true if the request must be signed or somehow authenticated, false otherwise.

Returns:



542
543
544
545
546
547
# File 'lib/google/api_client.rb', line 542

def generate_request(options={})
  options = {
    :api_client => self
  }.merge(options)
  return Google::APIClient::Request.new(options)
end

#preferred_version(api) ⇒ Google::APIClient::API

Note:

Warning: This method should be used with great care.

Returns the service object with the highest version number.

As APIs are updated, minor differences between versions may cause incompatibilities. Requesting a specific version will avoid this issue.

Parameters:

  • api (String, Symbol)

    The name of the service.

Returns:



449
450
451
452
453
454
455
456
457
458
# File 'lib/google/api_client.rb', line 449

def preferred_version(api)
  if !api.kind_of?(String) && !api.kind_of?(Symbol)
    raise TypeError,
      "Expected String or Symbol, got #{api.class}."
  end
  api = api.to_s
  return self.discovered_apis.detect do |a|
    a.name == api && a.preferred == true
  end
end

#register_discovery_document(api, version, discovery_document) ⇒ Google::APIClient::API

Manually registers a pre-loaded discovery document for a specific version of an API.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String)

    The desired version of the API.

  • discovery_document (String, StringIO)

    The contents of the discovery document.

Returns:



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/google/api_client.rb', line 316

def register_discovery_document(api, version, discovery_document)
  api = api.to_s
  version = version || 'v1'
  if discovery_document.kind_of?(StringIO)
    discovery_document.rewind
    discovery_document = discovery_document.string
  elsif discovery_document.respond_to?(:to_str)
    discovery_document = discovery_document.to_str
  else
    raise TypeError,
      "Expected String or StringIO, got #{discovery_document.class}."
  end
  @discovery_documents["#{api}:#{version}"] =
    MultiJson.load(discovery_document)
  discovered_api(api, version)
end

#register_discovery_uri(api, version, uri) ⇒ Google::APIClient::API

Manually registers a URI as a discovery document for a specific version of an API.

Parameters:

  • api (String, Symbol)

    The API name.

  • version (String)

    The desired version of the API.

  • uri (Addressable::URI)

    The URI of the discovery document.

Returns:



282
283
284
285
286
287
# File 'lib/google/api_client.rb', line 282

def register_discovery_uri(api, version, uri)
  api = api.to_s
  version = version || 'v1'
  @discovery_uris["#{api}:#{version}"] = uri
  discovered_api(api, version)
end

#verify_id_token!Object

Deprecated.

Use the google-id-token gem for verifying JWTs

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.



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/google/api_client.rb', line 466

def verify_id_token!
  require 'jwt'
  require 'openssl'
  @certificates ||= {}
  if !self.authorization.respond_to?(:id_token)
    raise ArgumentError, (
      "Current authorization mechanism does not support ID tokens: " +
      "#{self.authorization.class.to_s}"
    )
  elsif !self.authorization.id_token
    raise ArgumentError, (
      "Could not verify ID token, ID token missing. " +
      "Scopes were: #{self.authorization.scope.inspect}"
    )
  else
    check_cached_certs = lambda do
      valid = false
      for key, cert in @certificates
        begin
          self.authorization.decoded_id_token(cert.public_key)
          valid = true
        rescue JWT::DecodeError, Signet::UnsafeOperationError
          # Expected exception. Ignore, ID token has not been validated.
        end
      end
      valid
    end
    if check_cached_certs.call()
      return true
    end
    response = self.execute!(
      :http_method => :get,
      :uri => 'https://www.googleapis.com/oauth2/v1/certs',
      :authenticated => false
    )
    @certificates.merge!(
      Hash[MultiJson.load(response.body).map do |key, cert|
        [key, OpenSSL::X509::Certificate.new(cert)]
      end]
    )
    if check_cached_certs.call()
      return true
    else
      raise InvalidIDTokenError,
        "Could not verify ID token against any available certificate."
    end
  end
  return nil
end