class Google::Apis::Core::HttpCommand

Command for HTTP request/response.

Constants

OPENCENSUS_AVAILABLE
RETRIABLE_ERRORS
UNSAFE_CLASS_NAMES

Attributes

body[RW]

Request body @return [#read]

connection[RW]

HTTP Client @return [HTTPClient]

header[RW]

HTTP headers @return [Hash]

method[RW]

HTTP method @return [symbol]

options[RW]

Request options @return [Google::Apis::RequestOptions]

params[RW]

Path params for URL Template @return [Hash]

query[RW]

Query params @return [Hash]

url[RW]

HTTP request URL @return [String, Addressable::URI]

Public Class Methods

new(method, url, body: nil) click to toggle source

@param [symbol] method

HTTP method

@param [String,Addressable::URI, Addressable::Template] url

HTTP URL or template

@param [String, read] body

Request body
# File lib/google/apis/core/http_command.rb, line 80
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 = Hash.new
  self.body = body
  self.query = {}
  self.params = {}
  @opencensus_span = nil
  if OPENCENSUS_AVAILABLE
    logger.warn  'OpenCensus support is now deprecated. ' +
                 'Please refer https://github.com/googleapis/google-api-ruby-client#tracing for migrating to use OpenTelemetry.' 
    
  end
end

Public Instance Methods

allow_form_encoding?() click to toggle source
# File lib/google/apis/core/http_command.rb, line 347
def allow_form_encoding?
  [:post, :put].include?(method) && body.nil?
end
apply_request_options(req_header) click to toggle source

Update the request with any specified options. @param [Hash] req_header

HTTP headers

@return [void]

# File lib/google/apis/core/http_command.rb, line 338
def apply_request_options(req_header)
  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)
end
authorization_refreshable?() click to toggle source

Check if attached credentials can be automatically refreshed @return [Boolean]

# File lib/google/apis/core/http_command.rb, line 158
def authorization_refreshable?
  options.authorization.respond_to?(:apply!)
end
check_status(status, header = nil, body = nil, message = nil) click to toggle source

Check the response and raise error if needed

@param [Fixnum] status

HTTP status code of response

@param [Hash] header

HTTP response headers

@param [String] body

HTTP response body

@param [String] message

Error message text

@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

# File lib/google/apis/core/http_command.rb, line 225
def check_status(status, header = nil, body = nil, message = nil)
  # TODO: 304 Not Modified depends on context...
  case status
  when 200...300, 308
    nil
  when 301, 302, 303, 307
    message ||= sprintf('Redirect to %s', header['Location'])
    raise Google::Apis::RedirectError.new(message, status_code: status, header: header, body: body)
  when 401
    message ||= 'Unauthorized'
    raise Google::Apis::AuthorizationError.new(message, status_code: status, header: header, body: body)
  when 429
    message ||= 'Rate limit exceeded'
    raise Google::Apis::RateLimitError.new(message, status_code: status, header: header, body: body)
  when 408
    message ||= 'Request time out'
    raise Google::Apis::RequestTimeOutError.new(message, status_code: status, header: header, body: body)
  when 304, 400, 402...500
    message ||= 'Invalid request'
    raise Google::Apis::ClientError.new(message, status_code: status, header: header, body: body)
  when 500...600
    message ||= 'Server error'
    raise Google::Apis::ServerError.new(message, status_code: status, header: header, body: body)
  else
    logger.warn(sprintf('Encountered unexpected status code %s', status))
    message ||= 'Unknown error'
    raise Google::Apis::TransmissionError.new(message, status_code: status, header: header, body: body)
  end
end
decode_response_body(_content_type, body) click to toggle source

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]

# File lib/google/apis/core/http_command.rb, line 262
def decode_response_body(_content_type, body)
  body
end
do_retry(func, client) { |result, nil| ... } click to toggle source
# File lib/google/apis/core/http_command.rb, line 116
def do_retry func, client
  begin
    Retriable.retriable tries: options.retries + 1,
                        max_elapsed_time: options.max_elapsed_time,
                        base_interval: options.base_interval,
                        max_interval: options.max_interval,
                        multiplier: options.multiplier,
                        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, Signet::AuthorizationError, Signet::RemoteServerError, Signet::UnexpectedStatusError],
                          on_retry: proc { |*| refresh_authorization } do
        send(func, client).tap do |result|
          if block_given?
            yield result, nil
          end
        end
      end
    end
  rescue => e
    if block_given?
      yield nil, e
    else
      raise e
    end
  end
end
error(err, rethrow: false, &block) click to toggle source

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

# File lib/google/apis/core/http_command.rb, line 285
def error(err, rethrow: false, &block)
  logger.debug { sprintf('Error - %s', PP.pp(err, '')) }
  if err.is_a?(HTTPClient::BadResponseError)
    begin
      res = err.res
      raise Google::Apis::TransmissionError.new(err) if res.nil?
      check_status(res.status.to_i, res.header, res.body)
    rescue Google::Apis::Error => e
      err = e
    end
  elsif err.is_a?(HTTPClient::TimeoutError) || err.is_a?(SocketError) || err.is_a?(HTTPClient::KeepAliveDisconnected) || err.is_a?(Errno::ECONNREFUSED) || err.is_a?(Errno::ETIMEDOUT)
    err = Google::Apis::TransmissionError.new(err)
  end
  block.call(nil, err) if block_given?
  fail err if rethrow || block.nil?
end
execute(client, &block) click to toggle source

Execute the command, retrying as necessary

@param [HTTPClient] 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

# File lib/google/apis/core/http_command.rb, line 106
def execute(client, &block)
  prepare!
  opencensus_begin_span
  do_retry :execute_once, client, &block
ensure
  opencensus_end_span
  @http_res = nil
  release!
end
execute_once(client) click to toggle source

Execute the command once.

@private @param [HTTPClient] 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

# File lib/google/apis/core/http_command.rb, line 311
def execute_once(client)
  body.rewind if body.respond_to?(:rewind)
  begin
    logger.debug { sprintf('Sending HTTP %s %s', method, url) }
    request_header = header.dup
    apply_request_options(request_header)

    @http_res = client.request(method.to_s.upcase,
                               url.to_s,
                               query: nil,
                               body: body,
                               header: request_header,
                               follow_redirect: true)
    logger.debug { @http_res.status }
    logger.debug { safe_single_line_representation @http_res }
    response = process_response(@http_res.status.to_i, @http_res.header, @http_res.body)
    success(response)
  rescue => e
    logger.debug { sprintf('Caught error %s', e) }
    error(e, rethrow: true)
  end
end
prepare!() click to toggle source

Prepare the request (e.g. calculate headers, add query params, serialize data, etc) before sending

@private @return [void]

# File lib/google/apis/core/http_command.rb, line 166
def prepare!
  normalize_unicode = true
  if options
    header.update(options.header) if options.header
    query.update(options.query) if options.query
    normalize_unicode = options.normalize_unicode
  end
  self.url = url.expand(params, nil, normalize_unicode) if url.is_a?(Addressable::Template)
  url.query_values = normalize_query_values(query).merge(url.query_values || {})

  if allow_form_encoding?
    @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'
  else
    @form_encoded = false
  end

  self.body = '' unless self.body
end
process_response(status, header, body) click to toggle source

Check the response and either decode body or raise error

@param [Fixnum] status

HTTP status code of response

@param [Hash] 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

# File lib/google/apis/core/http_command.rb, line 206
def process_response(status, header, body)
  check_status(status, header, body)
  decode_response_body(header['Content-Type'].first, body)
end
refresh_authorization() click to toggle source

Refresh the authorization authorization after a 401 error

@private @return [void]

# File lib/google/apis/core/http_command.rb, line 151
def refresh_authorization
  # Handled implicitly by auth lib, here in case need to override
  logger.debug('Retrying after authentication failure')
end
release!() click to toggle source

Release any resources used by this command @private @return [void]

# File lib/google/apis/core/http_command.rb, line 190
def release!
end
success(result, &block) click to toggle source

Process a success response @param [Object] result

Result object

@return [Object] result if no block given @yield [result, nil] if block given

# File lib/google/apis/core/http_command.rb, line 271
def success(result, &block)
  logger.debug { sprintf('Success - %s', safe_pretty_representation(result)) }
  block.call(result, nil) if block_given?
  result
end

Private Instance Methods

form_encoded?() click to toggle source
# File lib/google/apis/core/http_command.rb, line 442
def form_encoded?
  @form_encoded
end
map_http_status(http_status) click to toggle source
# File lib/google/apis/core/http_command.rb, line 446
def map_http_status http_status
  case http_status
  when 200..399 then 0 # OK
  when 400 then 3 # INVALID_ARGUMENT
  when 401 then 16 # UNAUTHENTICATED
  when 403 then 7 # PERMISSION_DENIED
  when 404 then 5 # NOT_FOUND
  when 429 then 8 # RESOURCE_EXHAUSTED
  when 501 then 12 # UNIMPLEMENTED
  when 503 then 14 # UNAVAILABLE
  when 504 then 4 # DEADLINE_EXCEEDED
  else 2 # UNKNOWN
  end
end
normalize_query_value(v) click to toggle source
# File lib/google/apis/core/http_command.rb, line 468
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
normalize_query_values(input) click to toggle source
# File lib/google/apis/core/http_command.rb, line 461
def normalize_query_values(input)
  input.inject({}) do |h, (k, v)|
    h[k] = normalize_query_value(v)
    h
  end
end
opencensus_begin_span() click to toggle source
# File lib/google/apis/core/http_command.rb, line 392
def opencensus_begin_span
  return unless OPENCENSUS_AVAILABLE && options.use_opencensus
  return if @opencensus_span
  return unless OpenCensus::Trace.span_context

  @opencensus_span = OpenCensus::Trace.start_span url.path.to_s
  @opencensus_span.kind = OpenCensus::Trace::SpanBuilder::CLIENT
  @opencensus_span.put_attribute "http.host", url.host.to_s
  @opencensus_span.put_attribute "http.method", method.to_s.upcase
  @opencensus_span.put_attribute "http.path", url.path.to_s
  if body.respond_to? :bytesize
    @opencensus_span.put_message_event \
      OpenCensus::Trace::SpanBuilder::SENT, 1, body.bytesize
  end

  formatter = OpenCensus::Trace.config.http_formatter
  if formatter.respond_to? :header_name
    header[formatter.header_name] = formatter.serialize @opencensus_span.context.trace_context
  end
rescue StandardError => e
  # Log exceptions and continue, so opencensus failures don't cause
  # the entire request to fail.
  logger.debug { sprintf('Error opening OpenCensus span: %s', e) }
end
opencensus_end_span() click to toggle source
# File lib/google/apis/core/http_command.rb, line 417
def opencensus_end_span
  return unless OPENCENSUS_AVAILABLE
  return unless @opencensus_span
  return unless OpenCensus::Trace.span_context

  if @http_res
    if @http_res.body.respond_to? :bytesize
      @opencensus_span.put_message_event \
        OpenCensus::Trace::SpanBuilder::RECEIVED, 1, @http_res.body.bytesize
    end
    status = @http_res.status.to_i
    if status > 0
      @opencensus_span.set_status map_http_status status
      @opencensus_span.put_attribute "http.status_code", status
    end
  end

  OpenCensus::Trace.end_span @opencensus_span
  @opencensus_span = nil
rescue StandardError => e
  # Log exceptions and continue, so failures don't cause leaks by
  # aborting cleanup.
  logger.debug { sprintf('Error finishing OpenCensus span: %s', e) }
end
safe_pretty_representation(obj) click to toggle source
# File lib/google/apis/core/http_command.rb, line 376
def safe_pretty_representation obj
  out = ""
  printer = RedactingPP.new out, 79
  printer.guard_inspect_key { printer.pp obj }
  printer.flush
  out << "\n"
end
safe_single_line_representation(obj) click to toggle source
# File lib/google/apis/core/http_command.rb, line 384
def safe_single_line_representation obj
  out = ""
  printer = RedactingSingleLine.new out
  printer.guard_inspect_key { printer.pp obj }
  printer.flush
  out
end