8 class ConnectionError < StandardError # :nodoc:
11 def initialize(response, message = nil)
17 "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
22 class Redirection < ConnectionError # :nodoc:
23 def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
27 class ClientError < ConnectionError; end # :nodoc:
30 class ResourceNotFound < ClientError; end # :nodoc:
33 class ResourceConflict < ClientError; end # :nodoc:
36 class ServerError < ConnectionError; end # :nodoc:
38 # 405 Method Not Allowed
39 class MethodNotAllowed < ClientError # :nodoc:
41 @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
45 # Class to handle connections to remote web services.
46 # This class is used by ActiveResource::Base to interface with REST
57 class << self ; attr_reader :default_header end
58 @default_header = { 'Content-Type' => 'application/xml' }
62 # The +site+ parameter is required and will set the +site+
63 # attribute to the URI for the remote resource service.
65 raise ArgumentError, 'Missing site URI' unless site
69 # Set URI for remote service.
71 @site = site.is_a?(URI) ? site : URI.parse(site)
74 # Execute a GET request.
75 # Used to get (find) resources.
76 def get(path, headers = {})
77 xml_from_response(request(:get, path, build_request_headers(headers)))
80 # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
81 # Used to delete resources.
82 def delete(path, headers = {})
83 request(:delete, path, build_request_headers(headers))
86 # Execute a PUT request (see HTTP protocol documentation if unfamiliar).
87 # Used to update resources.
88 def put(path, body = '', headers = {})
89 request(:put, path, body.to_s, build_request_headers(headers))
92 # Execute a POST request.
93 # Used to create new resources.
94 def post(path, body = '', headers = {})
95 request(:post, path, body.to_s, build_request_headers(headers))
98 def xml_from_response(response)
99 from_xml_data(Hash.from_xml(response.body))
103 # Makes request to remote service.
104 def request(method, path, *arguments)
105 logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
107 time = Benchmark.realtime { result = http.send(method, path, *arguments) }
108 logger.info "--> #{result.code} #{result.message} (#{result.body.length}b %.2fs)" % time if logger
109 handle_response(result)
112 # Handles response and error codes from remote service.
113 def handle_response(response)
114 case response.code.to_i
116 raise(Redirection.new(response))
120 raise(ResourceNotFound.new(response))
122 raise(MethodNotAllowed.new(response))
124 raise(ResourceConflict.new(response))
126 raise(ResourceInvalid.new(response))
128 raise(ClientError.new(response))
130 raise(ServerError.new(response))
132 raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
136 # Creates new (or uses currently instantiated) Net::HTTP instance for communication with
137 # remote service and resources.
140 @http = Net::HTTP.new(@site.host, @site.port)
141 @http.use_ssl = @site.is_a?(URI::HTTPS)
142 @http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @http.use_ssl
148 # Builds headers for request to remote service.
149 def build_request_headers(headers)
150 authorization_header.update(self.class.default_header).update(headers)
153 # Sets authorization header; authentication information is pulled from credentials provided with site URI.
154 def authorization_header
155 (@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {})
159 ActiveResource::Base.logger
162 # Manipulate from_xml Hash, because xml_simple is not exactly what we
163 # want for ActiveResource.
164 def from_xml_data(data)
165 if data.is_a?(Hash) && data.keys.size == 1