Lots going on
[lyrix.git] / vendor / rails / activeresource / lib / active_resource / connection.rb
blob07f3c98eda3400bc27eca78f3049ff49f798a1d2
1 require 'net/https'
2 require 'date'
3 require 'time'
4 require 'uri'
5 require 'benchmark'
7 module ActiveResource
8   class ConnectionError < StandardError # :nodoc:
9     attr_reader :response
11     def initialize(response, message = nil)
12       @response = response
13       @message  = message
14     end
16     def to_s
17       "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
18     end
19   end
21   # 3xx Redirection
22   class Redirection < ConnectionError # :nodoc:
23     def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end    
24   end 
26   # 4xx Client Error
27   class ClientError < ConnectionError; end # :nodoc:
28   
29   # 404 Not Found
30   class ResourceNotFound < ClientError; end # :nodoc:
31   
32   # 409 Conflict
33   class ResourceConflict < ClientError; end # :nodoc:
35   # 5xx Server Error
36   class ServerError < ConnectionError; end # :nodoc:
38   # 405 Method Not Allowed
39   class MethodNotAllowed < ClientError # :nodoc:
40     def allowed_methods
41       @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
42     end
43   end
45   # Class to handle connections to remote web services.
46   # This class is used by ActiveResource::Base to interface with REST
47   # services.
48   class Connection
49     attr_reader :site
51     class << self
52       def requests
53         @@requests ||= []
54       end
55       
56       def default_header
57         class << self ; attr_reader :default_header end
58         @default_header = { 'Content-Type' => 'application/xml' }
59       end
60     end
62     # The +site+ parameter is required and will set the +site+
63     # attribute to the URI for the remote resource service.
64     def initialize(site)
65       raise ArgumentError, 'Missing site URI' unless site
66       self.site = site
67     end
69     # Set URI for remote service.
70     def site=(site)
71       @site = site.is_a?(URI) ? site : URI.parse(site)
72     end
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)))
78     end
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))
84     end
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))
90     end
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))
96     end
98     def xml_from_response(response)
99       from_xml_data(Hash.from_xml(response.body))
100     end
102     private
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
106         result = nil
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)
110       end
112       # Handles response and error codes from remote service.
113       def handle_response(response)
114         case response.code.to_i
115           when 301,302
116             raise(Redirection.new(response))
117           when 200...400
118             response
119           when 404
120             raise(ResourceNotFound.new(response))
121           when 405
122             raise(MethodNotAllowed.new(response))
123           when 409
124             raise(ResourceConflict.new(response))
125           when 422
126             raise(ResourceInvalid.new(response))
127           when 401...500
128             raise(ClientError.new(response))
129           when 500...600
130             raise(ServerError.new(response))
131           else
132             raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
133         end
134       end
136       # Creates new (or uses currently instantiated) Net::HTTP instance for communication with
137       # remote service and resources.
138       def http
139         unless @http
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
143         end
145         @http
146       end
147       
148       # Builds headers for request to remote service.
149       def build_request_headers(headers)
150         authorization_header.update(self.class.default_header).update(headers)
151       end
152       
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") } : {})
156       end
158       def logger #:nodoc:
159         ActiveResource::Base.logger
160       end
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
166           data.values.first
167         else
168           data
169         end
170       end
171   end