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
|
# @param [Fixnum] status
|
||||||
# HTTP status code of response
|
# HTTP status code of response
|
||||||
|
# @param [Hurley::Header] header
|
||||||
|
# HTTP response headers
|
||||||
# @param [String] body
|
# @param [String] body
|
||||||
# HTTP response body
|
# HTTP response body
|
||||||
|
# @param [String] message
|
||||||
|
# Error message text
|
||||||
# @return [void]
|
# @return [void]
|
||||||
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
# @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::ClientError] The request is invalid and should not be retried without modification
|
||||||
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
# @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
|
case status
|
||||||
when 400, 402...500
|
when 400, 402...500
|
||||||
error = parse_error(body)
|
error = parse_error(body)
|
||||||
if error
|
if error
|
||||||
logger.debug { error }
|
message = error['reason'] if error.has_key?('reason')
|
||||||
fail Google::Apis::RateLimitError, error if RATE_LIMIT_ERRORS.include?(error['reason'])
|
raise Google::Apis::RateLimitError.new(message,
|
||||||
|
status_code: status,
|
||||||
|
header: header,
|
||||||
|
body: body) if RATE_LIMIT_ERRORS.include?(message)
|
||||||
end
|
end
|
||||||
super(status, error)
|
super(status, header, body, message)
|
||||||
else
|
else
|
||||||
super(status, body)
|
super(status, header, body, message)
|
||||||
end
|
end
|
||||||
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::ClientError] The request is invalid and should not be retried without modification
|
||||||
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
||||||
def process_response(status, header, body)
|
def process_response(status, header, body)
|
||||||
check_status(status, body)
|
check_status(status, header, body)
|
||||||
decode_response_body(header[:content_type], body)
|
decode_response_body(header[:content_type], body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -167,28 +167,38 @@ module Google
|
||||||
#
|
#
|
||||||
# @param [Fixnum] status
|
# @param [Fixnum] status
|
||||||
# HTTP status code of response
|
# HTTP status code of response
|
||||||
|
# @param
|
||||||
|
# @param [Hurley::Header] header
|
||||||
|
# HTTP response headers
|
||||||
# @param [String] body
|
# @param [String] body
|
||||||
# HTTP response body
|
# HTTP response body
|
||||||
|
# @param [String] message
|
||||||
|
# Error message text
|
||||||
# @return [void]
|
# @return [void]
|
||||||
# @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried
|
# @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::ClientError] The request is invalid and should not be retried without modification
|
||||||
# @raise [Google::Apis::AuthorizationError] Authorization is required
|
# @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...
|
# TODO: 304 Not Modified depends on context...
|
||||||
case status
|
case status
|
||||||
when 200...300
|
when 200...300
|
||||||
nil
|
nil
|
||||||
when 301, 302, 303, 307
|
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
|
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
|
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
|
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
|
else
|
||||||
logger.warn(sprintf('Encountered unexpected status code %s', status))
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,11 @@ module Google
|
||||||
module Apis
|
module Apis
|
||||||
# Base error, capable of wrapping another
|
# Base error, capable of wrapping another
|
||||||
class Error < StandardError
|
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
|
@cause = nil
|
||||||
|
|
||||||
if err.respond_to?(:backtrace)
|
if err.respond_to?(:backtrace)
|
||||||
|
@ -25,6 +29,9 @@ module Google
|
||||||
else
|
else
|
||||||
super(err.to_s)
|
super(err.to_s)
|
||||||
end
|
end
|
||||||
|
@status_code = status_code
|
||||||
|
@header = header.dup unless header.nil?
|
||||||
|
@body = body
|
||||||
end
|
end
|
||||||
|
|
||||||
def backtrace
|
def backtrace
|
||||||
|
@ -35,7 +42,7 @@ module Google
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# An error which is raised when there is an unexpected response or other
|
# An error which is raised when there is an unexpected response or other
|
||||||
# transport error that prevents an operation from succeeding.
|
# transport error that prevents an operation from succeeding.
|
||||||
class TransmissionError < Error
|
class TransmissionError < Error
|
||||||
|
|
|
@ -123,7 +123,27 @@ RSpec.describe Google::Apis::Core::HttpCommand do
|
||||||
command.options.retries = 1
|
command.options.retries = 1
|
||||||
expect { command.execute(client) }.to raise_error(Google::Apis::ServerError)
|
expect { command.execute(client) }.to raise_error(Google::Apis::ServerError)
|
||||||
end
|
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
|
context('with callbacks') do
|
||||||
it 'should return the response body after retries' do
|
it 'should return the response body after retries' do
|
||||||
expect { |b| command.execute(client, &b) }.to yield_successive_args(
|
expect { |b| command.execute(client, &b) }.to yield_successive_args(
|
||||||
|
|
Loading…
Reference in New Issue