Expose HTTP status code & body in errors
This commit is contained in:
parent
810cfa4b5d
commit
2a9fd28176
|
@ -0,0 +1,93 @@
|
|||
# Copyright 2010 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.
|
||||
|
||||
module Google
|
||||
class APIClient
|
||||
##
|
||||
# Helper for loading keys from the PKCS12 files downloaded when
|
||||
# setting up service accounts at the APIs Console.
|
||||
#
|
||||
module KeyUtils
|
||||
##
|
||||
# Loads a key from PKCS12 file, assuming a single private key
|
||||
# is present.
|
||||
#
|
||||
# @param [String] keyfile
|
||||
# Path of the PKCS12 file to load. If not a path to an actual file,
|
||||
# assumes the string is the content of the file itself.
|
||||
# @param [String] passphrase
|
||||
# Passphrase for unlocking the private key
|
||||
#
|
||||
# @return [OpenSSL::PKey] The private key for signing assertions.
|
||||
def self.load_from_pkcs12(keyfile, passphrase)
|
||||
load_key(keyfile, passphrase) do |content, pass_phrase|
|
||||
OpenSSL::PKCS12.new(content, pass_phrase).key
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
# Loads a key from a PEM file.
|
||||
#
|
||||
# @param [String] keyfile
|
||||
# Path of the PEM file to load. If not a path to an actual file,
|
||||
# assumes the string is the content of the file itself.
|
||||
# @param [String] passphrase
|
||||
# Passphrase for unlocking the private key
|
||||
#
|
||||
# @return [OpenSSL::PKey] The private key for signing assertions.
|
||||
#
|
||||
def self.load_from_pem(keyfile, passphrase)
|
||||
load_key(keyfile, passphrase) do | content, pass_phrase|
|
||||
OpenSSL::PKey::RSA.new(content, pass_phrase)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Helper for loading keys from file or memory. Accepts a block
|
||||
# to handle the specific file format.
|
||||
#
|
||||
# @param [String] keyfile
|
||||
# Path of thefile to load. If not a path to an actual file,
|
||||
# assumes the string is the content of the file itself.
|
||||
# @param [String] passphrase
|
||||
# Passphrase for unlocking the private key
|
||||
#
|
||||
# @yield [String, String]
|
||||
# Key file & passphrase to extract key from
|
||||
# @yieldparam [String] keyfile
|
||||
# Contents of the file
|
||||
# @yieldparam [String] passphrase
|
||||
# Passphrase to unlock key
|
||||
# @yieldreturn [OpenSSL::PKey]
|
||||
# Private key
|
||||
#
|
||||
# @return [OpenSSL::PKey] The private key for signing assertions.
|
||||
def self.load_key(keyfile, passphrase, &block)
|
||||
begin
|
||||
begin
|
||||
content = File.open(keyfile, 'rb') { |io| io.read }
|
||||
rescue
|
||||
content = keyfile
|
||||
end
|
||||
block.call(content, passphrase)
|
||||
rescue OpenSSL::OpenSSLError
|
||||
raise ArgumentError.new("Invalid keyfile or passphrase")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -79,23 +79,30 @@ module Google
|
|||
#
|
||||
# @param [Fixnum] status
|
||||
# HTTP status code of response
|
||||
# @param [Hurley::Header] 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
|
||||
def check_status(status, body = nil)
|
||||
def check_status(status, header = nil, body = nil, message = nil)
|
||||
case status
|
||||
when 400, 402...500
|
||||
error = parse_error(body)
|
||||
if error
|
||||
logger.debug { error }
|
||||
fail Google::Apis::RateLimitError, error if RATE_LIMIT_ERRORS.include?(error['reason'])
|
||||
message = error['reason'] if error.has_key?('reason')
|
||||
raise Google::Apis::RateLimitError.new(message,
|
||||
status_code: status,
|
||||
header: header,
|
||||
body: body) if RATE_LIMIT_ERRORS.include?(message)
|
||||
end
|
||||
super(status, error)
|
||||
super(status, header, body, message)
|
||||
else
|
||||
super(status, body)
|
||||
super(status, header, body, message)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ module Google
|
|||
# @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)
|
||||
check_status(status, body)
|
||||
check_status(status, header, body)
|
||||
decode_response_body(header[:content_type], body)
|
||||
end
|
||||
|
||||
|
@ -167,28 +167,38 @@ module Google
|
|||
#
|
||||
# @param [Fixnum] status
|
||||
# HTTP status code of response
|
||||
# @param
|
||||
# @param [Hurley::Header] 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
|
||||
def check_status(status, body = nil)
|
||||
def check_status(status, header = nil, body = nil, message = nil)
|
||||
# TODO: 304 Not Modified depends on context...
|
||||
case status
|
||||
when 200...300
|
||||
nil
|
||||
when 301, 302, 303, 307
|
||||
fail Google::Apis::RedirectError, header[:location]
|
||||
message ||= sprintf('Redirect to %s', header[:location])
|
||||
raise Google::Apis::RedirectError.new(message, status_code: status, header: header, body: body)
|
||||
when 401
|
||||
fail Google::Apis::AuthorizationError, body
|
||||
message ||= 'Unauthorized'
|
||||
raise Google::Apis::AuthorizationError.new(message, status_code: status, header: header, body: body)
|
||||
when 304, 400, 402...500
|
||||
fail Google::Apis::ClientError, body
|
||||
message ||= 'Invalid request'
|
||||
raise Google::Apis::ClientError.new(message, status_code: status, header: header, body: body)
|
||||
when 500...600
|
||||
fail Google::Apis::ServerError, body
|
||||
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))
|
||||
fail Google::Apis::TransmissionError, body
|
||||
message ||= 'Unknown error'
|
||||
raise Google::Apis::TransmissionError.new(message, status_code: status, header: header, body: body)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,7 +16,11 @@ module Google
|
|||
module Apis
|
||||
# Base error, capable of wrapping another
|
||||
class Error < StandardError
|
||||
def initialize(err)
|
||||
attr_reader :status_code
|
||||
attr_reader :header
|
||||
attr_reader :body
|
||||
|
||||
def initialize(err, status_code: nil, header: nil, body: nil)
|
||||
@cause = nil
|
||||
|
||||
if err.respond_to?(:backtrace)
|
||||
|
@ -25,6 +29,9 @@ module Google
|
|||
else
|
||||
super(err.to_s)
|
||||
end
|
||||
@status_code = status_code
|
||||
@header = header.dup unless header.nil?
|
||||
@body = body
|
||||
end
|
||||
|
||||
def backtrace
|
||||
|
@ -35,7 +42,7 @@ module Google
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# An error which is raised when there is an unexpected response or other
|
||||
# transport error that prevents an operation from succeeding.
|
||||
class TransmissionError < Error
|
||||
|
|
|
@ -123,7 +123,27 @@ RSpec.describe Google::Apis::Core::HttpCommand do
|
|||
command.options.retries = 1
|
||||
expect { command.execute(client) }.to raise_error(Google::Apis::ServerError)
|
||||
end
|
||||
|
||||
|
||||
context('with retries exceeded') do
|
||||
before(:example) do
|
||||
command.options.retries = 1
|
||||
end
|
||||
|
||||
let(:err) do
|
||||
begin
|
||||
command.execute(client)
|
||||
rescue Google::Apis::Error => e
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
it 'should raise error with HTTP status code' do
|
||||
expect(err.status_code).to eq 500
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context('with callbacks') do
|
||||
it 'should return the response body after retries' do
|
||||
expect { |b| command.execute(client, &b) }.to yield_successive_args(
|
||||
|
|
Loading…
Reference in New Issue