| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  | # Copyright 2015 Google Inc. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  | # you may not use this file except in compliance with the License. | 
					
						
							|  |  |  | # You may obtain a copy of the License at | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | #      http://www.apache.org/licenses/LICENSE-2.0 | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | # distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | # See the License for the specific language governing permissions and | 
					
						
							|  |  |  | # limitations under the License. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require 'addressable/uri' | 
					
						
							|  |  |  | require 'addressable/template' | 
					
						
							|  |  |  | require 'google/apis/options' | 
					
						
							|  |  |  | require 'google/apis/errors' | 
					
						
							|  |  |  | require 'retriable' | 
					
						
							|  |  |  | require 'hurley' | 
					
						
							|  |  |  | require 'hurley/addressable' | 
					
						
							| 
									
										
										
										
											2015-12-02 23:49:51 +00:00
										 |  |  | require 'hurley_patches' | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  | require 'google/apis/core/logging' | 
					
						
							|  |  |  | require 'pp' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Google | 
					
						
							|  |  |  |   module Apis | 
					
						
							|  |  |  |     module Core | 
					
						
							|  |  |  |       # Command for HTTP request/response. | 
					
						
							|  |  |  |       class HttpCommand | 
					
						
							|  |  |  |         include Logging | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         RETRIABLE_ERRORS = [Google::Apis::ServerError, Google::Apis::RateLimitError, Google::Apis::TransmissionError] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Request options | 
					
						
							|  |  |  |         # @return [Google::Apis::RequestOptions] | 
					
						
							|  |  |  |         attr_accessor :options | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # HTTP request URL | 
					
						
							|  |  |  |         # @return [String, Addressable::URI] | 
					
						
							|  |  |  |         attr_accessor :url | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # HTTP headers | 
					
						
							|  |  |  |         # @return [Hurley::Header] | 
					
						
							|  |  |  |         attr_accessor :header | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Request body | 
					
						
							|  |  |  |         # @return [#read] | 
					
						
							|  |  |  |         attr_accessor :body | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # HTTP method | 
					
						
							|  |  |  |         # @return [symbol] | 
					
						
							|  |  |  |         attr_accessor :method | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # HTTP Client | 
					
						
							|  |  |  |         # @return [Hurley::Client] | 
					
						
							|  |  |  |         attr_accessor :connection | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Query params | 
					
						
							|  |  |  |         # @return [Hash] | 
					
						
							|  |  |  |         attr_accessor :query | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Path params for URL Template | 
					
						
							|  |  |  |         # @return [Hash] | 
					
						
							|  |  |  |         attr_accessor :params | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # @param [symbol] method | 
					
						
							|  |  |  |         #   HTTP method | 
					
						
							|  |  |  |         # @param [String,Addressable::URI, Addressable::Template] url | 
					
						
							|  |  |  |         #   HTTP URL or template | 
					
						
							|  |  |  |         # @param [String, #read] body | 
					
						
							|  |  |  |         #   Request body | 
					
						
							|  |  |  |         def initialize(method, url, body: nil) | 
					
						
							|  |  |  |           self.options = Google::Apis::RequestOptions.default.dup | 
					
						
							|  |  |  |           self.url = url | 
					
						
							|  |  |  |           self.url = Addressable::Template.new(url) if url.is_a?(String) | 
					
						
							|  |  |  |           self.method = method | 
					
						
							|  |  |  |           self.header = Hurley::Header.new | 
					
						
							|  |  |  |           self.body = body | 
					
						
							|  |  |  |           self.query = {} | 
					
						
							|  |  |  |           self.params = {} | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Execute the command, retrying as necessary | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @param [Hurley::Client] client | 
					
						
							|  |  |  |         #   HTTP client | 
					
						
							|  |  |  |         # @yield [result, err] Result or error if block supplied | 
					
						
							|  |  |  |         # @return [Object] | 
					
						
							|  |  |  |         # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried | 
					
						
							|  |  |  |         # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification | 
					
						
							|  |  |  |         # @raise [Google::Apis::AuthorizationError] Authorization is required | 
					
						
							|  |  |  |         def execute(client) | 
					
						
							|  |  |  |           prepare! | 
					
						
							|  |  |  |           begin | 
					
						
							|  |  |  |             Retriable.retriable tries: options.retries + 1, | 
					
						
							|  |  |  |                                 base_interval: 1, | 
					
						
							|  |  |  |                                 multiplier: 2, | 
					
						
							|  |  |  |                                 on: RETRIABLE_ERRORS do |try| | 
					
						
							|  |  |  |               # 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 | 
					
						
							|  |  |  |               auth_tries = (try == 1 && authorization_refreshable? ? 2 : 1) | 
					
						
							|  |  |  |               Retriable.retriable tries: auth_tries, | 
					
						
							|  |  |  |                                   on: [Google::Apis::AuthorizationError], | 
					
						
							|  |  |  |                                   on_retry: proc { |*| refresh_authorization } do | 
					
						
							| 
									
										
										
										
											2015-11-20 14:32:43 +00:00
										 |  |  |                 execute_once(client).tap do |result| | 
					
						
							|  |  |  |                   if block_given? | 
					
						
							|  |  |  |                     yield result, nil | 
					
						
							|  |  |  |                   end | 
					
						
							|  |  |  |                 end | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |               end | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           rescue => e | 
					
						
							| 
									
										
										
										
											2015-11-20 14:32:43 +00:00
										 |  |  |             if block_given? | 
					
						
							|  |  |  |               yield nil, e | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               raise e | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           end | 
					
						
							|  |  |  |         ensure | 
					
						
							|  |  |  |           release! | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Refresh the authorization authorization after a 401 error | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @private | 
					
						
							|  |  |  |         # @return [void] | 
					
						
							|  |  |  |         def refresh_authorization | 
					
						
							|  |  |  |           # Handled implicitly by auth lib, here in case need to override | 
					
						
							|  |  |  |           logger.debug('Retrying after authentication failure') | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Check if attached credentials can be automatically refreshed | 
					
						
							|  |  |  |         # @return [Boolean] | 
					
						
							|  |  |  |         def authorization_refreshable? | 
					
						
							|  |  |  |           options.authorization.respond_to?(:apply!) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Prepare the request (e.g. calculate headers, serialize data, etc) before sending | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @private | 
					
						
							|  |  |  |         # @return [void] | 
					
						
							|  |  |  |         def prepare! | 
					
						
							|  |  |  |           header.update(options.header) if options && options.header | 
					
						
							|  |  |  |           self.url = url.expand(params) if url.is_a?(Addressable::Template) | 
					
						
							| 
									
										
										
										
											2015-12-14 02:16:31 +00:00
										 |  |  |           url.query_values = query.merge(url.query_values || {}) | 
					
						
							| 
									
										
										
										
											2016-02-25 17:35:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if [:post, :put].include?(method)  && body.nil? | 
					
						
							|  |  |  |             @form_encoded = true | 
					
						
							|  |  |  |             self.body = Addressable::URI.form_encode(url.query_values(Array)) | 
					
						
							|  |  |  |             self.header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' | 
					
						
							|  |  |  |             self.url.query_values = {} | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             @form_encoded = false | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Release any resources used by this command | 
					
						
							|  |  |  |         # @private | 
					
						
							|  |  |  |         # @return [void] | 
					
						
							|  |  |  |         def release! | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Check the response and either decode body or raise error | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @param [Fixnum] status | 
					
						
							|  |  |  |         #   HTTP status code of response | 
					
						
							|  |  |  |         # @param [Hurley::Header] header | 
					
						
							|  |  |  |         #   Response headers | 
					
						
							|  |  |  |         # @param [String, #read] body | 
					
						
							|  |  |  |         #  Response body | 
					
						
							|  |  |  |         # @return [Object] | 
					
						
							|  |  |  |         #   Response object | 
					
						
							|  |  |  |         # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried | 
					
						
							|  |  |  |         # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification | 
					
						
							|  |  |  |         # @raise [Google::Apis::AuthorizationError] Authorization is required | 
					
						
							|  |  |  |         def process_response(status, header, body) | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |           check_status(status, header, body) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           decode_response_body(header[:content_type], body) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Check the response and raise error if needed | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @param [Fixnum] status | 
					
						
							|  |  |  |         #   HTTP status code of response | 
					
						
							| 
									
										
										
										
											2015-10-02 20:31:19 +00:00
										 |  |  |         # @param | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |         # @param [Hurley::Header] header | 
					
						
							|  |  |  |         #   HTTP response headers | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |         # @param [String] body | 
					
						
							|  |  |  |         #   HTTP response body | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |         # @param [String] message | 
					
						
							|  |  |  |         #   Error message text | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |         # @return [void] | 
					
						
							|  |  |  |         # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried | 
					
						
							|  |  |  |         # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification | 
					
						
							|  |  |  |         # @raise [Google::Apis::AuthorizationError] Authorization is required | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |         def check_status(status, header = nil, body = nil, message = nil) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           # TODO: 304 Not Modified depends on context... | 
					
						
							|  |  |  |           case status | 
					
						
							|  |  |  |           when 200...300
 | 
					
						
							|  |  |  |             nil | 
					
						
							|  |  |  |           when 301, 302, 303, 307
 | 
					
						
							| 
									
										
										
										
											2015-10-02 20:31:19 +00:00
										 |  |  |             message ||= sprintf('Redirect to %s', header[:location]) | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |             raise Google::Apis::RedirectError.new(message, status_code: status, header: header, body: body) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           when 401
 | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |             message ||= 'Unauthorized' | 
					
						
							|  |  |  |             raise Google::Apis::AuthorizationError.new(message, status_code: status, header: header, body: body) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           when 304, 400, 402...500
 | 
					
						
							| 
									
										
										
										
											2015-10-02 20:31:19 +00:00
										 |  |  |             message ||= 'Invalid request' | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |             raise Google::Apis::ClientError.new(message, status_code: status, header: header, body: body) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           when 500...600
 | 
					
						
							| 
									
										
										
										
											2015-10-02 20:31:19 +00:00
										 |  |  |             message ||= 'Server error' | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |             raise Google::Apis::ServerError.new(message, status_code: status, header: header, body: body) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           else | 
					
						
							|  |  |  |             logger.warn(sprintf('Encountered unexpected status code %s', status)) | 
					
						
							| 
									
										
										
										
											2015-07-20 19:36:13 +00:00
										 |  |  |             message ||= 'Unknown error' | 
					
						
							|  |  |  |             raise Google::Apis::TransmissionError.new(message, status_code: status, header: header, body: body) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Process the actual response body. Intended to be overridden by subclasses | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @param [String] _content_type | 
					
						
							|  |  |  |         #  Content type of body | 
					
						
							|  |  |  |         # @param [String, #read] body | 
					
						
							|  |  |  |         #  Response body | 
					
						
							|  |  |  |         # @return [Object] | 
					
						
							|  |  |  |         def decode_response_body(_content_type, body) | 
					
						
							|  |  |  |           body | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Process a success response | 
					
						
							|  |  |  |         # @param [Object] result | 
					
						
							|  |  |  |         #  Result object | 
					
						
							|  |  |  |         # @return [Object] result if no block given | 
					
						
							|  |  |  |         # @yield [result, nil] if block given | 
					
						
							|  |  |  |         def success(result, &block) | 
					
						
							|  |  |  |           logger.debug { sprintf('Success - %s', PP.pp(result, '')) } | 
					
						
							|  |  |  |           block.call(result, nil) if block_given? | 
					
						
							|  |  |  |           result | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Process an error response | 
					
						
							|  |  |  |         # @param [StandardError] err | 
					
						
							|  |  |  |         #  Error object | 
					
						
							|  |  |  |         # @param [Boolean] rethrow | 
					
						
							|  |  |  |         #  True if error should be raised again after handling | 
					
						
							|  |  |  |         # @return [void] | 
					
						
							|  |  |  |         # @yield [nil, err] if block given | 
					
						
							|  |  |  |         # @raise [StandardError] if no block | 
					
						
							|  |  |  |         def error(err, rethrow: false, &block) | 
					
						
							|  |  |  |           logger.debug { sprintf('Error - %s', PP.pp(err, '')) } | 
					
						
							| 
									
										
										
										
											2016-02-25 21:30:27 +00:00
										 |  |  |           err = Google::Apis::TransmissionError.new(err) if err.is_a?(Hurley::ClientError) || err.is_a?(SocketError) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           block.call(nil, err) if block_given? | 
					
						
							|  |  |  |           fail err if rethrow || block.nil? | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Execute the command once. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @private | 
					
						
							|  |  |  |         # @param [Hurley::Client] client | 
					
						
							|  |  |  |         #   HTTP client | 
					
						
							|  |  |  |         # @return [Object] | 
					
						
							|  |  |  |         # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried | 
					
						
							|  |  |  |         # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification | 
					
						
							|  |  |  |         # @raise [Google::Apis::AuthorizationError] Authorization is required | 
					
						
							| 
									
										
										
										
											2015-11-20 14:32:43 +00:00
										 |  |  |         def execute_once(client) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           body.rewind if body.respond_to?(:rewind) | 
					
						
							|  |  |  |           begin | 
					
						
							|  |  |  |             logger.debug { sprintf('Sending HTTP %s %s', method, url) } | 
					
						
							|  |  |  |             response = client.send(method, url, body) do |req| | 
					
						
							| 
									
										
										
										
											2015-10-02 20:31:19 +00:00
										 |  |  |               # Temporary workaround for Hurley bug where the connection preference | 
					
						
							|  |  |  |               # is ignored and it uses nested anyway | 
					
						
							| 
									
										
										
										
											2016-02-25 17:35:38 +00:00
										 |  |  |               unless form_encoded? | 
					
						
							|  |  |  |                 req.url.query_class = Hurley::Query::Flat | 
					
						
							|  |  |  |                 query.each do | k, v| | 
					
						
							|  |  |  |                  req.url.query[k] = normalize_query_value(v) | 
					
						
							|  |  |  |                 end | 
					
						
							| 
									
										
										
										
											2015-12-20 23:22:13 +00:00
										 |  |  |               end | 
					
						
							| 
									
										
										
										
											2015-10-02 20:31:19 +00:00
										 |  |  |               # End workaround | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |               apply_request_options(req) | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |             logger.debug { response.status_code } | 
					
						
							|  |  |  |             logger.debug { response.inspect } | 
					
						
							|  |  |  |             response = process_response(response.status_code, response.header, response.body) | 
					
						
							| 
									
										
										
										
											2015-11-20 14:32:43 +00:00
										 |  |  |             success(response) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           rescue => e | 
					
						
							|  |  |  |             logger.debug { sprintf('Caught error %s', e) } | 
					
						
							| 
									
										
										
										
											2015-11-20 14:32:43 +00:00
										 |  |  |             error(e, rethrow: true) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Update the request with any specified options. | 
					
						
							|  |  |  |         # @param [Hurley::Request] req | 
					
						
							|  |  |  |         #  HTTP request | 
					
						
							|  |  |  |         # @return [void] | 
					
						
							|  |  |  |         def apply_request_options(req) | 
					
						
							|  |  |  |           if options.authorization.respond_to?(:apply!) | 
					
						
							|  |  |  |             options.authorization.apply!(req.header) | 
					
						
							|  |  |  |           elsif options.authorization.is_a?(String) | 
					
						
							|  |  |  |             req.header[:authorization] = sprintf('Bearer %s', options.authorization) | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           req.header.update(header) | 
					
						
							|  |  |  |           req.options.timeout = options.timeout_sec | 
					
						
							| 
									
										
										
										
											2016-02-23 17:55:55 +00:00
										 |  |  |           req.options.open_timeout = options.open_timeout_sec | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-12-20 23:22:13 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         private | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-25 17:35:38 +00:00
										 |  |  |         def form_encoded? | 
					
						
							|  |  |  |           @form_encoded | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 23:22:13 +00:00
										 |  |  |         def normalize_query_value(v) | 
					
						
							|  |  |  |           case v | 
					
						
							|  |  |  |           when Array | 
					
						
							|  |  |  |             v.map { |v2| normalize_query_value(v2) } | 
					
						
							|  |  |  |           when nil | 
					
						
							|  |  |  |             nil | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             v.to_s | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |