2006-06-25 14:44:22 +00:00
|
|
|
require 'net/https'
|
|
|
|
require 'date'
|
|
|
|
require 'time'
|
|
|
|
require 'uri'
|
2006-10-23 22:21:09 +00:00
|
|
|
require 'benchmark'
|
2006-06-25 14:44:22 +00:00
|
|
|
|
|
|
|
module ActiveResource
|
2007-06-23 17:29:54 +00:00
|
|
|
class ConnectionError < StandardError # :nodoc:
|
2006-06-25 14:44:22 +00:00
|
|
|
attr_reader :response
|
|
|
|
|
|
|
|
def initialize(response, message = nil)
|
|
|
|
@response = response
|
|
|
|
@message = message
|
|
|
|
end
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2006-06-25 14:44:22 +00:00
|
|
|
def to_s
|
|
|
|
"Failed with #{response.code}"
|
|
|
|
end
|
|
|
|
end
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2007-06-23 17:29:54 +00:00
|
|
|
# 4xx Client Error
|
|
|
|
class ClientError < ConnectionError; end # :nodoc:
|
|
|
|
|
|
|
|
# 404 Not Found
|
|
|
|
class ResourceNotFound < ClientError; end # :nodoc:
|
|
|
|
|
|
|
|
# 409 Conflict
|
|
|
|
class ResourceConflict < ClientError; end # :nodoc:
|
2006-06-25 14:44:22 +00:00
|
|
|
|
2007-06-23 17:29:54 +00:00
|
|
|
# 5xx Server Error
|
|
|
|
class ServerError < ConnectionError; end # :nodoc:
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2007-05-26 20:57:08 +00:00
|
|
|
# 405 Method Not Allowed
|
2007-06-23 17:29:54 +00:00
|
|
|
class MethodNotAllowed < ClientError # :nodoc:
|
2007-05-26 20:57:08 +00:00
|
|
|
def allowed_methods
|
|
|
|
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2007-06-23 17:29:54 +00:00
|
|
|
# Class to handle connections to remote web services.
|
|
|
|
# This class is used by ActiveResource::Base to interface with REST
|
|
|
|
# services.
|
2006-06-25 14:44:22 +00:00
|
|
|
class Connection
|
2006-09-29 16:25:49 +00:00
|
|
|
attr_reader :site
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2006-06-25 14:44:22 +00:00
|
|
|
class << self
|
|
|
|
def requests
|
|
|
|
@@requests ||= []
|
|
|
|
end
|
2006-09-01 01:15:10 +00:00
|
|
|
|
|
|
|
def default_header
|
|
|
|
class << self ; attr_reader :default_header end
|
|
|
|
@default_header = { 'Content-Type' => 'application/xml' }
|
|
|
|
end
|
2006-06-25 14:44:22 +00:00
|
|
|
end
|
|
|
|
|
2007-06-23 17:29:54 +00:00
|
|
|
# The +site+ parameter is required and will set the +site+
|
|
|
|
# attribute to the URI for the remote resource service.
|
2006-06-25 14:44:22 +00:00
|
|
|
def initialize(site)
|
2006-12-05 19:12:51 +00:00
|
|
|
raise ArgumentError, 'Missing site URI' unless site
|
|
|
|
self.site = site
|
2006-09-29 16:25:49 +00:00
|
|
|
end
|
2006-12-05 19:12:51 +00:00
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Set URI for remote service.
|
2006-09-29 16:25:49 +00:00
|
|
|
def site=(site)
|
|
|
|
@site = site.is_a?(URI) ? site : URI.parse(site)
|
2006-06-25 14:44:22 +00:00
|
|
|
end
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Execute a GET request.
|
|
|
|
# Used to get (find) resources.
|
2007-04-29 03:14:36 +00:00
|
|
|
def get(path, headers = {})
|
|
|
|
xml_from_response(request(:get, path, build_request_headers(headers)))
|
2006-06-25 14:44:22 +00:00
|
|
|
end
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
|
|
|
|
# Used to delete resources.
|
2007-04-29 03:14:36 +00:00
|
|
|
def delete(path, headers = {})
|
|
|
|
request(:delete, path, build_request_headers(headers))
|
2006-06-25 14:44:22 +00:00
|
|
|
end
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Execute a PUT request (see HTTP protocol documentation if unfamiliar).
|
|
|
|
# Used to update resources.
|
2007-04-29 03:14:36 +00:00
|
|
|
def put(path, body = '', headers = {})
|
2007-06-19 18:40:28 +00:00
|
|
|
request(:put, path, body.to_s, build_request_headers(headers))
|
2006-06-25 14:44:22 +00:00
|
|
|
end
|
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Execute a POST request.
|
|
|
|
# Used to create new resources.
|
2007-04-29 03:14:36 +00:00
|
|
|
def post(path, body = '', headers = {})
|
2007-06-19 18:40:28 +00:00
|
|
|
request(:post, path, body.to_s, build_request_headers(headers))
|
2006-06-25 14:44:22 +00:00
|
|
|
end
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2007-04-19 22:18:03 +00:00
|
|
|
def xml_from_response(response)
|
2007-06-21 15:07:15 +00:00
|
|
|
from_xml_data(Hash.from_xml(response.body))
|
2007-04-19 22:18:03 +00:00
|
|
|
end
|
|
|
|
|
2006-06-25 14:44:22 +00:00
|
|
|
private
|
2007-01-16 06:34:10 +00:00
|
|
|
# Makes request to remote service.
|
2006-10-23 22:21:09 +00:00
|
|
|
def request(method, path, *arguments)
|
2006-12-05 19:12:51 +00:00
|
|
|
logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
|
2006-10-23 22:21:09 +00:00
|
|
|
result = nil
|
|
|
|
time = Benchmark.realtime { result = http.send(method, path, *arguments) }
|
|
|
|
logger.info "--> #{result.code} #{result.message} (#{result.body.length}b %.2fs)" % time if logger
|
|
|
|
handle_response(result)
|
2006-08-31 09:31:11 +00:00
|
|
|
end
|
2006-06-25 14:44:22 +00:00
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Handles response and error codes from remote service.
|
2006-08-31 09:31:11 +00:00
|
|
|
def handle_response(response)
|
2006-06-25 14:44:22 +00:00
|
|
|
case response.code.to_i
|
2006-08-31 09:31:11 +00:00
|
|
|
when 200...400
|
2006-06-25 14:44:22 +00:00
|
|
|
response
|
|
|
|
when 404
|
|
|
|
raise(ResourceNotFound.new(response))
|
2007-05-26 20:57:08 +00:00
|
|
|
when 405
|
|
|
|
raise(MethodNotAllowed.new(response))
|
2006-09-09 00:11:12 +00:00
|
|
|
when 409
|
|
|
|
raise(ResourceConflict.new(response))
|
2007-01-17 00:46:32 +00:00
|
|
|
when 422
|
|
|
|
raise(ResourceInvalid.new(response))
|
2006-09-08 00:07:30 +00:00
|
|
|
when 401...500
|
2006-06-25 14:44:22 +00:00
|
|
|
raise(ClientError.new(response))
|
|
|
|
when 500...600
|
|
|
|
raise(ServerError.new(response))
|
|
|
|
else
|
|
|
|
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Creates new (or uses currently instantiated) Net::HTTP instance for communication with
|
|
|
|
# remote service and resources.
|
2006-06-25 14:44:22 +00:00
|
|
|
def http
|
|
|
|
unless @http
|
|
|
|
@http = Net::HTTP.new(@site.host, @site.port)
|
|
|
|
@http.use_ssl = @site.is_a?(URI::HTTPS)
|
|
|
|
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @http.use_ssl
|
|
|
|
end
|
2006-08-31 09:31:11 +00:00
|
|
|
|
2006-06-25 14:44:22 +00:00
|
|
|
@http
|
|
|
|
end
|
2006-09-29 16:25:49 +00:00
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Builds headers for request to remote service.
|
2007-04-29 03:14:36 +00:00
|
|
|
def build_request_headers(headers)
|
|
|
|
authorization_header.update(self.class.default_header).update(headers)
|
2006-09-29 16:25:49 +00:00
|
|
|
end
|
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
# Sets authorization header; authentication information is pulled from credentials provided with site URI.
|
2006-09-29 16:25:49 +00:00
|
|
|
def authorization_header
|
|
|
|
(@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {})
|
|
|
|
end
|
2006-10-23 22:21:09 +00:00
|
|
|
|
2007-01-16 06:34:10 +00:00
|
|
|
def logger #:nodoc:
|
2006-10-23 22:21:09 +00:00
|
|
|
ActiveResource::Base.logger
|
|
|
|
end
|
2006-12-12 15:29:54 +00:00
|
|
|
|
|
|
|
# Manipulate from_xml Hash, because xml_simple is not exactly what we
|
|
|
|
# want for ActiveResource.
|
|
|
|
def from_xml_data(data)
|
2007-06-21 15:07:15 +00:00
|
|
|
if data.is_a?(Hash) && data.keys.size == 1
|
|
|
|
data.values.first
|
|
|
|
else
|
|
|
|
data
|
2006-12-12 15:29:54 +00:00
|
|
|
end
|
|
|
|
end
|
2006-06-25 14:44:22 +00:00
|
|
|
end
|
2007-06-23 17:29:54 +00:00
|
|
|
end
|