google-apis-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Google
16
+ module Apis
17
+ module Core
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,224 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'addressable/uri'
16
+ require 'addressable/template'
17
+ require 'google/apis/core/http_command'
18
+ require 'google/apis/errors'
19
+ require 'json'
20
+ require 'retriable'
21
+
22
+ module Google
23
+ module Apis
24
+ module Core
25
+ # Command for executing most basic API request with JSON requests/responses
26
+ class ApiCommand < HttpCommand
27
+ JSON_CONTENT_TYPE = 'application/json'
28
+ FIELDS_PARAM = 'fields'
29
+ ERROR_REASON_MAPPING = {
30
+ 'rateLimitExceeded' => Google::Apis::RateLimitError,
31
+ 'userRateLimitExceeded' => Google::Apis::RateLimitError,
32
+ 'projectNotLinked' => Google::Apis::ProjectNotLinkedError
33
+ }
34
+
35
+ # JSON serializer for request objects
36
+ # @return [Google::Apis::Core::JsonRepresentation]
37
+ attr_accessor :request_representation
38
+
39
+ # Request body to serialize
40
+ # @return [Object]
41
+ attr_accessor :request_object
42
+
43
+ # JSON serializer for response objects
44
+ # @return [Google::Apis::Core::JsonRepresentation]
45
+ attr_accessor :response_representation
46
+
47
+ # Class to instantiate when de-serializing responses
48
+ # @return [Object]
49
+ attr_accessor :response_class
50
+
51
+ # Client library version.
52
+ # @return [String]
53
+ attr_accessor :client_version
54
+
55
+ # @param [symbol] method
56
+ # HTTP method
57
+ # @param [String,Addressable::URI, Addressable::Template] url
58
+ # HTTP URL or template
59
+ # @param [String, #read] body
60
+ # Request body
61
+ def initialize(method, url, body: nil, client_version: nil)
62
+ super(method, url, body: body)
63
+ self.client_version = client_version || '0.0'
64
+ end
65
+
66
+ # Serialize the request body
67
+ #
68
+ # @return [void]
69
+ def prepare!
70
+ set_api_client_header
71
+ set_user_project_header
72
+ if options&.api_format_version
73
+ header['X-Goog-Api-Format-Version'] = options.api_format_version.to_s
74
+ end
75
+ query[FIELDS_PARAM] = normalize_fields_param(query[FIELDS_PARAM]) if query.key?(FIELDS_PARAM)
76
+ if request_representation && request_object
77
+ header['Content-Type'] ||= JSON_CONTENT_TYPE
78
+ if options && options.skip_serialization
79
+ self.body = request_object
80
+ else
81
+ self.body = request_representation.new(request_object).to_json(user_options: { skip_undefined: true })
82
+ end
83
+ end
84
+ super
85
+ end
86
+
87
+ # Deserialize the response body if present
88
+ #
89
+ # @param [String] content_type
90
+ # Content type of body
91
+ # @param [String, #read] body
92
+ # Response body
93
+ # @return [Object]
94
+ # Response object
95
+ # noinspection RubyUnusedLocalVariable
96
+ def decode_response_body(content_type, body)
97
+ return super unless response_representation
98
+ return super if options && options.skip_deserialization
99
+ return super if content_type.nil?
100
+ return nil unless content_type.start_with?(JSON_CONTENT_TYPE)
101
+ body = "{}" if body.empty?
102
+ instance = response_class.new
103
+ response_representation.new(instance).from_json(body, unwrap: response_class)
104
+ instance
105
+ end
106
+
107
+ # Check the response and raise error if needed
108
+ #
109
+ # @param [Fixnum] status
110
+ # HTTP status code of response
111
+ # @param [Hash] header
112
+ # HTTP response headers
113
+ # @param [String] body
114
+ # HTTP response body
115
+ # @param [String] message
116
+ # Error message text
117
+ # @return [void]
118
+ # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
119
+ # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
120
+ # @raise [Google::Apis::AuthorizationError] Authorization is required
121
+ def check_status(status, header = nil, body = nil, message = nil)
122
+ case status
123
+ when 400, 402...500
124
+ reason, message = parse_error(body)
125
+ if reason
126
+ message = sprintf('%s: %s', reason, message)
127
+ raise ERROR_REASON_MAPPING[reason].new(
128
+ message,
129
+ status_code: status,
130
+ header: header,
131
+ body: body
132
+ ) if ERROR_REASON_MAPPING.key?(reason)
133
+ end
134
+ super(status, header, body, message)
135
+ else
136
+ super(status, header, body, message)
137
+ end
138
+ end
139
+
140
+ def allow_form_encoding?
141
+ request_representation.nil? && super
142
+ end
143
+
144
+ private
145
+
146
+ def set_api_client_header
147
+ old_xgac = header
148
+ .find_all { |k, v| k.downcase == 'x-goog-api-client' }
149
+ .map { |(a, b)| b }
150
+ .join(' ')
151
+ .split
152
+ .find_all { |s| s !~ %r{^gl-ruby/|^gdcl/} }
153
+ .join(' ')
154
+ xgac = "gl-ruby/#{RUBY_VERSION} gdcl/#{client_version}"
155
+ xgac = old_xgac.empty? ? xgac : "#{old_xgac} #{xgac}"
156
+ header.delete_if { |k, v| k.downcase == 'x-goog-api-client' }
157
+ header['X-Goog-Api-Client'] = xgac
158
+ end
159
+
160
+ def set_user_project_header
161
+ quota_project_id = options.quota_project
162
+ if !quota_project_id && options&.authorization.respond_to?(:quota_project_id)
163
+ quota_project_id = options.authorization.quota_project_id
164
+ end
165
+ header['X-Goog-User-Project'] = quota_project_id if quota_project_id
166
+ end
167
+
168
+ # Attempt to parse a JSON error message
169
+ # @param [String] body
170
+ # HTTP response body
171
+ # @return [Array<(String, String)>]
172
+ # Error reason and message
173
+ def parse_error(body)
174
+ obj = JSON.load(body)
175
+ error = obj['error']
176
+ if error['details']
177
+ return extract_v2_error_details(error)
178
+ elsif error['errors']
179
+ return extract_v1_error_details(error)
180
+ else
181
+ fail 'Can not parse error message. No "details" or "errors" detected'
182
+ end
183
+ rescue
184
+ return [nil, nil]
185
+ end
186
+
187
+ # Extracts details from a v1 error message
188
+ # @param [Hash] error
189
+ # Parsed JSON
190
+ # @return [Array<(String, String)>]
191
+ # Error reason and message
192
+ def extract_v1_error_details(error)
193
+ reason = error['errors'].first['reason']
194
+ message = error['message']
195
+ return [reason, message]
196
+ end
197
+
198
+ # Extracts details from a v2error message
199
+ # @param [Hash] error
200
+ # Parsed JSON
201
+ # @return [Array<(String, String)>]
202
+ # Error reason and message
203
+ def extract_v2_error_details(error)
204
+ reason = error['status']
205
+ message = error['message']
206
+ return [reason, message]
207
+ end
208
+
209
+ # Convert field names from ruby conventions to original names in JSON
210
+ #
211
+ # @param [String] fields
212
+ # Value of 'fields' param
213
+ # @return [String]
214
+ # Updated header value
215
+ def normalize_fields_param(fields)
216
+ # TODO: Generate map of parameter names during code gen. Small possibility that camelization fails
217
+ fields.gsub(/:/, '').gsub(/\w+/) do |str|
218
+ str.gsub(/(?:^|_)([a-z])/){ Regexp.last_match.begin(0) == 0 ? $1 : $1.upcase }
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,459 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'addressable/uri'
16
+ require 'addressable/template'
17
+ require 'google/apis'
18
+ require 'google/apis/core/version'
19
+ require 'google/apis/core/api_command'
20
+ require 'google/apis/core/batch'
21
+ require 'google/apis/core/upload'
22
+ require 'google/apis/core/download'
23
+ require 'google/apis/options'
24
+ require 'googleauth'
25
+ require 'httpclient'
26
+
27
+ module Google
28
+ module Apis
29
+ module Core
30
+ # Helper class for enumerating over a result set requiring multiple fetches
31
+ class PagedResults
32
+ include Enumerable
33
+
34
+ attr_reader :last_result
35
+
36
+ # @param [BaseService] service
37
+ # Current service instance
38
+ # @param [Fixnum] max
39
+ # Maximum number of items to iterate over. Nil if no limit
40
+ # @param [Boolean] cache
41
+ # True (default) if results should be cached so multiple iterations can be used.
42
+ # @param [Symbol] items
43
+ # Name of the field in the result containing the items. Defaults to :items
44
+ def initialize(service, max: nil, items: :items, cache: true, response_page_token: :next_page_token, &block)
45
+ @service = service
46
+ @block = block
47
+ @max = max
48
+ @items_field = items
49
+ @response_page_token_field = response_page_token
50
+ if cache
51
+ @result_cache = Hash.new do |h, k|
52
+ h[k] = @block.call(k, @service)
53
+ end
54
+ @fetch_proc = Proc.new { |token| @result_cache[token] }
55
+ else
56
+ @fetch_proc = Proc.new { |token| @block.call(token, @service) }
57
+ end
58
+ end
59
+
60
+ # Iterates over result set, fetching additional pages as needed
61
+ def each
62
+ page_token = nil
63
+ item_count = 0
64
+ loop do
65
+ @last_result = @fetch_proc.call(page_token)
66
+ items = @last_result.send(@items_field)
67
+ if items.kind_of?(Array)
68
+ for item in items
69
+ item_count = item_count + 1
70
+ break if @max && item_count > @max
71
+ yield item
72
+ end
73
+ elsif items.kind_of?(Hash)
74
+ items.each do |key, val|
75
+ item_count = item_count + 1
76
+ break if @max && item_count > @max
77
+ yield key, val
78
+ end
79
+ elsif items
80
+ # yield singular non-nil items (for genomics API)
81
+ yield items
82
+ end
83
+ break if @max && item_count >= @max
84
+ next_page_token = @last_result.send(@response_page_token_field)
85
+ break if next_page_token.nil? || next_page_token == page_token
86
+ page_token = next_page_token
87
+ end
88
+ end
89
+ end
90
+
91
+ # Base service for all APIs. Not to be used directly.
92
+ #
93
+ class BaseService
94
+ include Logging
95
+
96
+ # Root URL (host/port) for the API
97
+ # @return [Addressable::URI]
98
+ attr_accessor :root_url
99
+
100
+ # Additional path prefix for all API methods
101
+ # @return [Addressable::URI]
102
+ attr_accessor :base_path
103
+
104
+ # Alternate path prefix for media uploads
105
+ # @return [Addressable::URI]
106
+ attr_accessor :upload_path
107
+
108
+ # Alternate path prefix for all batch methods
109
+ # @return [Addressable::URI]
110
+ attr_accessor :batch_path
111
+
112
+ # HTTP client
113
+ # @return [HTTPClient]
114
+ attr_accessor :client
115
+
116
+ # General settings
117
+ # @return [Google::Apis::ClientOptions]
118
+ attr_accessor :client_options
119
+
120
+ # Default options for all requests
121
+ # @return [Google::Apis::RequestOptions]
122
+ attr_accessor :request_options
123
+
124
+ # Client library name.
125
+ # @return [String]
126
+ attr_accessor :client_name
127
+
128
+ # Client library version.
129
+ # @return [String]
130
+ attr_accessor :client_version
131
+
132
+ # @param [String,Addressable::URI] root_url
133
+ # Root URL for the API
134
+ # @param [String,Addressable::URI] base_path
135
+ # Additional path prefix for all API methods
136
+ # @api private
137
+ def initialize(root_url, base_path, client_name: nil, client_version: nil)
138
+ self.root_url = root_url
139
+ self.base_path = base_path
140
+ self.client_name = client_name || 'google-api-ruby-client'
141
+ self.client_version = client_version || Google::Apis::Core::VERSION
142
+ self.upload_path = "upload/#{base_path}"
143
+ self.batch_path = 'batch'
144
+ self.client_options = Google::Apis::ClientOptions.default.dup
145
+ self.request_options = Google::Apis::RequestOptions.default.dup
146
+ end
147
+
148
+ # @!attribute [rw] authorization
149
+ # @return [Signet::OAuth2::Client]
150
+ # OAuth2 credentials
151
+ def authorization=(authorization)
152
+ request_options.authorization = authorization
153
+ end
154
+
155
+ def authorization
156
+ request_options.authorization
157
+ end
158
+
159
+ # TODO: with(options) method
160
+
161
+ # Perform a batch request. Calls made within the block are sent in a single network
162
+ # request to the server.
163
+ #
164
+ # @example
165
+ # service.batch do |s|
166
+ # s.get_item(id1) do |res, err|
167
+ # # process response for 1st call
168
+ # end
169
+ # # ...
170
+ # s.get_item(idN) do |res, err|
171
+ # # process response for Nth call
172
+ # end
173
+ # end
174
+ #
175
+ # @param [Hash, Google::Apis::RequestOptions] options
176
+ # Request-specific options
177
+ # @yield [self]
178
+ # @return [void]
179
+ def batch(options = nil)
180
+ batch_command = BatchCommand.new(:post, Addressable::URI.parse(root_url + batch_path))
181
+ batch_command.options = request_options.merge(options)
182
+ apply_command_defaults(batch_command)
183
+ begin
184
+ start_batch(batch_command)
185
+ yield self
186
+ ensure
187
+ end_batch
188
+ end
189
+ batch_command.execute(client)
190
+ end
191
+
192
+ # Perform a batch upload request. Calls made within the block are sent in a single network
193
+ # request to the server. Batch uploads are useful for uploading multiple small files. For larger
194
+ # files, use single requests which use a resumable upload protocol.
195
+ #
196
+ # @example
197
+ # service.batch do |s|
198
+ # s.insert_item(upload_source: 'file1.txt') do |res, err|
199
+ # # process response for 1st call
200
+ # end
201
+ # # ...
202
+ # s.insert_item(upload_source: 'fileN.txt') do |res, err|
203
+ # # process response for Nth call
204
+ # end
205
+ # end
206
+ #
207
+ # @param [Hash, Google::Apis::RequestOptions] options
208
+ # Request-specific options
209
+ # @yield [self]
210
+ # @return [void]
211
+ def batch_upload(options = nil)
212
+ batch_command = BatchUploadCommand.new(:put, Addressable::URI.parse(root_url + upload_path))
213
+ batch_command.options = request_options.merge(options)
214
+ apply_command_defaults(batch_command)
215
+ begin
216
+ start_batch(batch_command)
217
+ yield self
218
+ ensure
219
+ end_batch
220
+ end
221
+ batch_command.execute(client)
222
+ end
223
+
224
+ # Get the current HTTP client
225
+ # @return [HTTPClient]
226
+ def client
227
+ @client ||= new_client
228
+ end
229
+
230
+
231
+ # Simple escape hatch for making API requests directly to a given
232
+ # URL. This is not intended to be used as a generic HTTP client
233
+ # and should be used only in cases where no service method exists
234
+ # (e.g. fetching an export link for a Google Drive file.)
235
+ #
236
+ # @param [Symbol] method
237
+ # HTTP method as symbol (e.g. :get, :post, :put, ...)
238
+ # @param [String] url
239
+ # URL to call
240
+ # @param [Hash<String,String>] params
241
+ # Optional hash of query parameters
242
+ # @param [#read] body
243
+ # Optional body for POST/PUT
244
+ # @param [IO, String] download_dest
245
+ # IO stream or filename to receive content download
246
+ # @param [Google::Apis::RequestOptions] options
247
+ # Request-specific options
248
+ #
249
+ # @yield [result, err] Result & error if block supplied
250
+ # @yieldparam result [String] HTTP response body
251
+ # @yieldparam err [StandardError] error object if request failed
252
+ #
253
+ # @return [String] HTTP response body
254
+ def http(method, url, params: nil, body: nil, download_dest: nil, options: nil, &block)
255
+ if download_dest
256
+ command = DownloadCommand.new(method, url, body: body, client_version: client_version)
257
+ else
258
+ command = HttpCommand.new(method, url, body: body)
259
+ end
260
+ command.options = request_options.merge(options)
261
+ apply_command_defaults(command)
262
+ command.query.merge(Hash(params))
263
+ execute_or_queue_command(command, &block)
264
+ end
265
+
266
+ # Executes a given query with paging, automatically retrieving
267
+ # additional pages as necessary. Requires a block that returns the
268
+ # result set of a page. The current page token is supplied as an argument
269
+ # to the block.
270
+ #
271
+ # Note: The returned enumerable also contains a `last_result` field
272
+ # containing the full result of the last query executed.
273
+ #
274
+ # @param [Fixnum] max
275
+ # Maximum number of items to iterate over. Defaults to nil -- no upper bound.
276
+ # @param [Symbol] items
277
+ # Name of the field in the result containing the items. Defaults to :items
278
+ # @param [Boolean] cache
279
+ # True (default) if results should be cached so multiple iterations can be used.
280
+ # @return [Enumerble]
281
+ # @yield [token, service]
282
+ # Current page token & service instance
283
+ # @yieldparam [String] token
284
+ # Current page token to be used in the query
285
+ # @yieldparam [service]
286
+ # Current service instance
287
+ # @since 0.9.4
288
+ #
289
+ # @example Retrieve all files,
290
+ # file_list = service.fetch_all { |token, s| s.list_files(page_token: token) }
291
+ # file_list.each { |f| ... }
292
+ def fetch_all(max: nil, items: :items, cache: true, response_page_token: :next_page_token, &block)
293
+ fail "fetch_all may not be used inside a batch" if batch?
294
+ return PagedResults.new(self, max: max, items: items, cache: cache, response_page_token: response_page_token, &block)
295
+ end
296
+
297
+ protected
298
+
299
+ # Create a new upload command.
300
+ #
301
+ # @param [symbol] method
302
+ # HTTP method for uploading (typically :put or :post)
303
+ # @param [String] path
304
+ # Additional path to upload endpoint, appended to API base path
305
+ # @param [Hash, Google::Apis::RequestOptions] options
306
+ # Request-specific options
307
+ # @return [Google::Apis::Core::UploadCommand]
308
+ def make_upload_command(method, path, options)
309
+ template = Addressable::Template.new(root_url + upload_path + path)
310
+ if batch?
311
+ command = MultipartUploadCommand.new(method, template, client_version: client_version)
312
+ else
313
+ command = ResumableUploadCommand.new(method, template, client_version: client_version)
314
+ end
315
+ command.options = request_options.merge(options)
316
+ apply_command_defaults(command)
317
+ command
318
+ end
319
+
320
+ # Create a new download command.
321
+ #
322
+ # @param [symbol] method
323
+ # HTTP method for uploading (typically :get)
324
+ # @param [String] path
325
+ # Additional path to download endpoint, appended to API base path
326
+ # @param [Hash, Google::Apis::RequestOptions] options
327
+ # Request-specific options
328
+ # @return [Google::Apis::Core::DownloadCommand]
329
+ def make_download_command(method, path, options)
330
+ template = Addressable::Template.new(root_url + base_path + path)
331
+ command = DownloadCommand.new(method, template, client_version: client_version)
332
+ command.options = request_options.merge(options)
333
+ command.query['alt'] = 'media'
334
+ apply_command_defaults(command)
335
+ command
336
+ end
337
+
338
+ # Create a new command.
339
+ #
340
+ # @param [symbol] method
341
+ # HTTP method (:get, :post, :delete, etc...)
342
+ # @param [String] path
343
+ # Additional path, appended to API base path
344
+ # @param [Hash, Google::Apis::RequestOptions] options
345
+ # Request-specific options
346
+ # @return [Google::Apis::Core::DownloadCommand]
347
+ def make_simple_command(method, path, options)
348
+ full_path =
349
+ if path.start_with? "/"
350
+ path[1..-1]
351
+ else
352
+ base_path + path
353
+ end
354
+ template = Addressable::Template.new(root_url + full_path)
355
+ command = ApiCommand.new(method, template, client_version: client_version)
356
+ command.options = request_options.merge(options)
357
+ apply_command_defaults(command)
358
+ command
359
+ end
360
+
361
+ # Execute the request. If a batch is in progress, the request is added to the batch instead.
362
+ #
363
+ # @param [Google::Apis::Core::HttpCommand] command
364
+ # Command to execute
365
+ # @return [Object] response object if command executed and no callback supplied
366
+ # @yield [result, err] Result & error if block supplied
367
+ # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
368
+ # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification
369
+ # @raise [Google::Apis::AuthorizationError] Authorization is required
370
+ def execute_or_queue_command(command, &callback)
371
+ batch_command = current_batch
372
+ if batch_command
373
+ fail "Can not combine services in a batch" if Thread.current[:google_api_batch_service] != self
374
+ batch_command.add(command, &callback)
375
+ nil
376
+ else
377
+ command.execute(client, &callback)
378
+ end
379
+ end
380
+
381
+ # Update commands with service-specific options. To be implemented by subclasses
382
+ # @param [Google::Apis::Core::HttpCommand] _command
383
+ def apply_command_defaults(_command)
384
+ end
385
+
386
+ private
387
+
388
+ # Get the current batch context
389
+ #
390
+ # @return [Google:Apis::Core::BatchRequest]
391
+ def current_batch
392
+ Thread.current[:google_api_batch]
393
+ end
394
+
395
+ # Check if a batch is in progress
396
+ # @return [Boolean]
397
+ def batch?
398
+ !current_batch.nil?
399
+ end
400
+
401
+ # Start a new thread-local batch context
402
+ # @param [Google::Apis::Core::BatchCommand] cmd
403
+ def start_batch(cmd)
404
+ fail "Batch already in progress" if batch?
405
+ Thread.current[:google_api_batch] = cmd
406
+ Thread.current[:google_api_batch_service] = self
407
+ end
408
+
409
+ # Clear thread-local batch context
410
+ def end_batch
411
+ Thread.current[:google_api_batch] = nil
412
+ Thread.current[:google_api_batch_service] = nil
413
+ end
414
+
415
+ # Create a new HTTP client
416
+ # @return [HTTPClient]
417
+ def new_client
418
+ client = ::HTTPClient.new
419
+
420
+ if client_options.transparent_gzip_decompression
421
+ client.transparent_gzip_decompression = client_options.transparent_gzip_decompression
422
+ end
423
+
424
+ client.proxy = client_options.proxy_url if client_options.proxy_url
425
+
426
+ if client_options.open_timeout_sec
427
+ client.connect_timeout = client_options.open_timeout_sec
428
+ end
429
+
430
+ if client_options.read_timeout_sec
431
+ client.receive_timeout = client_options.read_timeout_sec
432
+ end
433
+
434
+ if client_options.send_timeout_sec
435
+ client.send_timeout = client_options.send_timeout_sec
436
+ end
437
+
438
+ client.follow_redirect_count = 5
439
+ client.default_header = { 'User-Agent' => user_agent }
440
+
441
+ client.debug_dev = logger if client_options.log_http_requests
442
+ client
443
+ end
444
+
445
+
446
+ # Build the user agent header
447
+ # @return [String]
448
+ def user_agent
449
+ sprintf('%s/%s %s/%s %s (gzip)',
450
+ client_options.application_name,
451
+ client_options.application_version,
452
+ client_name,
453
+ client_version,
454
+ Google::Apis::OS_VERSION)
455
+ end
456
+ end
457
+ end
458
+ end
459
+ end