lib/rest_connection.rb in rest_connection-0.1.1 vs lib/rest_connection.rb in rest_connection-0.1.2
- old
+ new
@@ -130,32 +130,63 @@
if uri.scheme == 'https'
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
headers = @settings[:common_headers]
- headers.merge!("Cookie" => @cookie) if @cookie
http.start do |http|
- req = yield(uri, headers)
- unless @cookie
- req.basic_auth(@settings[:user], @settings[:pass]) if @settings[:user]
+ @max_retries = 3
+ ret = nil
+ begin
+ headers.delete("Cookie")
+ headers.merge!("Cookie" => @cookie) if @cookie
+ req = yield(uri, headers)
+ logger("#{req.method}: #{req.path}")
+ logger("\trequest body: #{req.body}") if req.body and req.body !~ /password/
+ req.basic_auth(@settings[:user], @settings[:pass]) if @settings[:user] unless @cookie
+
+ response, body = http.request(req)
+ ret = handle_response(response)
+ rescue Exception => e
+ raise unless error_handler(e)
+ retry
end
- logger("#{req.method}: #{req.path}")
- logger("\trequest body: #{req.body}") if req.body and req.body !~ /password/
- response, body = http.request(req)
- handle_response(response)
+ ret
end
end
+ def error_handler(e)
+ case e
+ when EOFError, Timeout::Error
+ if @max_retries >= 0
+ logger("Caught #{e}. Retrying...")
+ @max_retries -= 1
+ return true
+ end
+ when RestConnection::Errors::Forbidden
+ if @max_retries >= 0
+ if e.response.body =~ /(session|cookie).*(invalid|expired)/i
+ logger("Caught '#{e.response.body}'. Refreshing cookie...")
+ refresh_cookie if respond_to?(:refresh_cookie)
+ else
+ return false
+ end
+ @max_retries -= 1
+ return true
+ end
+ end
+ return false
+ end
+
# connection.get("/root/login", :test_header => "x", :test_header2 => "y")
# href = "/api/base_new" if this begins with a slash then the url will be used as absolute path.
# href = "servers" this will be concat'd on to the api_url from the settings
# additional_parameters = Hash or String of parameters to pass to HTTP::Get
def get(href, additional_parameters = "")
rest_connect do |base_uri,headers|
- href = "#{base_uri}/#{href}" unless begins_with_slash(href)
+ new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
params = requestify(additional_parameters) || ""
- new_path = URI.escape(href + @settings[:extension] + "?") + params
+ new_path = URI.escape(new_href + @settings[:extension] + "?") + params
Net::HTTP::Get.new(new_path, headers)
end
end
# connection.post(server_url + "/start")
@@ -163,12 +194,12 @@
# href = "/api/base_new" if this begins with a slash then the url will be used as absolute path.
# href = "servers" this will be concat'd on to the api_url from the settings
# additional_parameters = Hash or String of parameters to pass to HTTP::Post
def post(href, additional_parameters = {})
rest_connect do |base_uri, headers|
- href = "#{base_uri}/#{href}" unless begins_with_slash(href)
- res = Net::HTTP::Post.new(href , headers)
+ new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
+ res = Net::HTTP::Post.new(new_href , headers)
unless additional_parameters.empty?
res.set_content_type('application/json')
res.body = additional_parameters.to_json
end
#res.set_form_data(additional_parameters, '&')
@@ -181,12 +212,12 @@
# href = "/api/base" if this begins with a slash then the url will be used as absolute path.
# href = "servers" this will be concat'd on to the api_url from the settings
# additional_parameters = Hash or String of parameters to pass to HTTP::Put
def put(href, additional_parameters = {})
rest_connect do |base_uri, headers|
- href = "#{base_uri}/#{href}" unless begins_with_slash(href)
- new_path = URI.escape(href)
+ new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
+ new_path = URI.escape(new_href)
req = Net::HTTP::Put.new(new_path, headers)
req.set_content_type('application/json')
req.body = additional_parameters.to_json
req
end
@@ -197,12 +228,12 @@
# href = "/api/base_new" if this begins with a slash then the url will be used as absolute path.
# href = "servers" this will be concat'd on to the api_url from the settings
# additional_parameters = Hash or String of parameters to pass to HTTP::Delete
def delete(href, additional_parameters = {})
rest_connect do |base_uri, headers|
- href = "#{base_uri}/#{href}" unless begins_with_slash(href)
- new_path = URI.escape(href)
+ new_href = (href =~ /^\// ? href : "#{base_uri}/#{href}")
+ new_path = URI.escape(new_href)
req = Net::HTTP::Delete.new(href, headers)
req.set_content_type('application/json')
req.body = additional_parameters.to_json
req
end
@@ -224,18 +255,14 @@
end
else
return res
end
else
- raise "invalid response HTTP code: #{res.code.to_i}, #{res.code}, #{res.body}"
+ raise RestConnection::Errors.status_error(res)
end
end
- def begins_with_slash(href)
- href =~ /^\//
- end
-
def logger(message)
init_message = "Initializing Logging using "
if @@logger.nil?
if ENV['REST_CONNECTION_LOG']
@@logger = Logger.new(ENV['REST_CONNECTION_LOG'])
@@ -248,11 +275,11 @@
end
if @settings.nil?
@@logger.info(message)
else
- @@logger.info("[API v#{@settings[:common_headers]['X_API_VERSION']} ]" + message)
+ @@logger.info("[API v#{@settings[:common_headers]['X_API_VERSION']}] " + message)
end
end
# used by requestify to build parameters strings
def name_with_prefix(prefix, name)
@@ -270,8 +297,109 @@
parameters
else
"#{prefix}=#{CGI.escape(parameters.to_s)}"
end
end
+ end
+ module Errors
+ # HTTPStatusErrors, borrowed lovingly from the excon gem <3
+ class HTTPStatusError < StandardError
+ attr_reader :request, :response
+
+ def initialize(msg, response = nil, request = nil)
+ super(msg)
+ @request = request
+ @response = response
+ end
+ end
+
+ class Continue < HTTPStatusError; end # 100
+ class SwitchingProtocols < HTTPStatusError; end # 101
+ class OK < HTTPStatusError; end # 200
+ class Created < HTTPStatusError; end # 201
+ class Accepted < HTTPStatusError; end # 202
+ class NonAuthoritativeInformation < HTTPStatusError; end # 203
+ class NoContent < HTTPStatusError; end # 204
+ class ResetContent < HTTPStatusError; end # 205
+ class PartialContent < HTTPStatusError; end # 206
+ class MultipleChoices < HTTPStatusError; end # 300
+ class MovedPermanently < HTTPStatusError; end # 301
+ class Found < HTTPStatusError; end # 302
+ class SeeOther < HTTPStatusError; end # 303
+ class NotModified < HTTPStatusError; end # 304
+ class UseProxy < HTTPStatusError; end # 305
+ class TemporaryRedirect < HTTPStatusError; end # 307
+ class BadRequest < HTTPStatusError; end # 400
+ class Unauthorized < HTTPStatusError; end # 401
+ class PaymentRequired < HTTPStatusError; end # 402
+ class Forbidden < HTTPStatusError; end # 403
+ class NotFound < HTTPStatusError; end # 404
+ class MethodNotAllowed < HTTPStatusError; end # 405
+ class NotAcceptable < HTTPStatusError; end # 406
+ class ProxyAuthenticationRequired < HTTPStatusError; end # 407
+ class RequestTimeout < HTTPStatusError; end # 408
+ class Conflict < HTTPStatusError; end # 409
+ class Gone < HTTPStatusError; end # 410
+ class LengthRequired < HTTPStatusError; end # 411
+ class PreconditionFailed < HTTPStatusError; end # 412
+ class RequestEntityTooLarge < HTTPStatusError; end # 413
+ class RequestURITooLong < HTTPStatusError; end # 414
+ class UnsupportedMediaType < HTTPStatusError; end # 415
+ class RequestedRangeNotSatisfiable < HTTPStatusError; end # 416
+ class ExpectationFailed < HTTPStatusError; end # 417
+ class UnprocessableEntity < HTTPStatusError; end # 422
+ class InternalServerError < HTTPStatusError; end # 500
+ class NotImplemented < HTTPStatusError; end # 501
+ class BadGateway < HTTPStatusError; end # 502
+ class ServiceUnavailable < HTTPStatusError; end # 503
+ class GatewayTimeout < HTTPStatusError; end # 504
+
+ # Messages for nicer exceptions, from rfc2616
+ def self.status_error(response)
+ @errors ||= {
+ 100 => [RestConnection::Errors::Continue, 'Continue'],
+ 101 => [RestConnection::Errors::SwitchingProtocols, 'Switching Protocols'],
+ 200 => [RestConnection::Errors::OK, 'OK'],
+ 201 => [RestConnection::Errors::Created, 'Created'],
+ 202 => [RestConnection::Errors::Accepted, 'Accepted'],
+ 203 => [RestConnection::Errors::NonAuthoritativeInformation, 'Non-Authoritative Information'],
+ 204 => [RestConnection::Errors::NoContent, 'No Content'],
+ 205 => [RestConnection::Errors::ResetContent, 'Reset Content'],
+ 206 => [RestConnection::Errors::PartialContent, 'Partial Content'],
+ 300 => [RestConnection::Errors::MultipleChoices, 'Multiple Choices'],
+ 301 => [RestConnection::Errors::MovedPermanently, 'Moved Permanently'],
+ 302 => [RestConnection::Errors::Found, 'Found'],
+ 303 => [RestConnection::Errors::SeeOther, 'See Other'],
+ 304 => [RestConnection::Errors::NotModified, 'Not Modified'],
+ 305 => [RestConnection::Errors::UseProxy, 'Use Proxy'],
+ 307 => [RestConnection::Errors::TemporaryRedirect, 'Temporary Redirect'],
+ 400 => [RestConnection::Errors::BadRequest, 'Bad Request'],
+ 401 => [RestConnection::Errors::Unauthorized, 'Unauthorized'],
+ 402 => [RestConnection::Errors::PaymentRequired, 'Payment Required'],
+ 403 => [RestConnection::Errors::Forbidden, 'Forbidden'],
+ 404 => [RestConnection::Errors::NotFound, 'Not Found'],
+ 405 => [RestConnection::Errors::MethodNotAllowed, 'Method Not Allowed'],
+ 406 => [RestConnection::Errors::NotAcceptable, 'Not Acceptable'],
+ 407 => [RestConnection::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
+ 408 => [RestConnection::Errors::RequestTimeout, 'Request Timeout'],
+ 409 => [RestConnection::Errors::Conflict, 'Conflict'],
+ 410 => [RestConnection::Errors::Gone, 'Gone'],
+ 411 => [RestConnection::Errors::LengthRequired, 'Length Required'],
+ 412 => [RestConnection::Errors::PreconditionFailed, 'Precondition Failed'],
+ 413 => [RestConnection::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
+ 414 => [RestConnection::Errors::RequestURITooLong, 'Request-URI Too Long'],
+ 415 => [RestConnection::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
+ 416 => [RestConnection::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
+ 417 => [RestConnection::Errors::ExpectationFailed, 'Expectation Failed'],
+ 422 => [RestConnection::Errors::UnprocessableEntity, 'Unprocessable Entity'],
+ 500 => [RestConnection::Errors::InternalServerError, 'InternalServerError'],
+ 501 => [RestConnection::Errors::NotImplemented, 'Not Implemented'],
+ 502 => [RestConnection::Errors::BadGateway, 'Bad Gateway'],
+ 503 => [RestConnection::Errors::ServiceUnavailable, 'Service Unavailable'],
+ 504 => [RestConnection::Errors::GatewayTimeout, 'Gateway Timeout']
+ }
+ error, message = @errors[response.code.to_i] || [RestConnection::Errors::HTTPStatusError, 'Unknown']
+ error.new("Invalid response HTTP code: #{response.code.to_i}: #{response.body}", response)
+ end
end
end