Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/activeresource/lib/active_resource/connection.rb

Revision 8900, 5.5 kB (checked in by bitsweat, 1 year ago)

URI.decode site username/password. Closes #11169 [Ernesto Jimenez]

Line 
1 require 'net/https'
2 require 'date'
3 require 'time'
4 require 'uri'
5 require 'benchmark'
6
7 module ActiveResource
8   class ConnectionError < StandardError # :nodoc:
9     attr_reader :response
10
11     def initialize(response, message = nil)
12       @response = response
13       @message  = message
14     end
15
16     def to_s
17       "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
18     end
19   end
20
21   # 3xx Redirection
22   class Redirection < ConnectionError # :nodoc:
23     def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end   
24   end
25
26   # 4xx Client Error
27   class ClientError < ConnectionError; end # :nodoc:
28  
29   # 400 Bad Request
30   class BadRequest < ClientError; end # :nodoc
31  
32   # 401 Unauthorized
33   class UnauthorizedAccess < ClientError; end # :nodoc
34  
35   # 403 Forbidden
36   class ForbiddenAccess < ClientError; end # :nodoc
37  
38   # 404 Not Found
39   class ResourceNotFound < ClientError; end # :nodoc:
40  
41   # 409 Conflict
42   class ResourceConflict < ClientError; end # :nodoc:
43
44   # 5xx Server Error
45   class ServerError < ConnectionError; end # :nodoc:
46
47   # 405 Method Not Allowed
48   class MethodNotAllowed < ClientError # :nodoc:
49     def allowed_methods
50       @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
51     end
52   end
53
54   # Class to handle connections to remote web services.
55   # This class is used by ActiveResource::Base to interface with REST
56   # services.
57   class Connection
58     attr_reader :site, :user, :password
59     attr_accessor :format
60
61     class << self
62       def requests
63         @@requests ||= []
64       end
65     end
66
67     # The +site+ parameter is required and will set the +site+
68     # attribute to the URI for the remote resource service.
69     def initialize(site, format = ActiveResource::Formats[:xml])
70       raise ArgumentError, 'Missing site URI' unless site
71       @user = @password = nil
72       self.site = site
73       self.format = format
74     end
75
76     # Set URI for remote service.
77     def site=(site)
78       @site = site.is_a?(URI) ? site : URI.parse(site)
79       @user = URI.decode(@site.user) if @site.user
80       @password = URI.decode(@site.password) if @site.password
81     end
82
83     # Set user for remote service.
84     def user=(user)
85       @user = user
86     end
87
88     # Set password for remote service.
89     def password=(password)
90       @password = password
91     end
92
93     # Execute a GET request.
94     # Used to get (find) resources.
95     def get(path, headers = {})
96       format.decode(request(:get, path, build_request_headers(headers)).body)
97     end
98
99     # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
100     # Used to delete resources.
101     def delete(path, headers = {})
102       request(:delete, path, build_request_headers(headers))
103     end
104
105     # Execute a PUT request (see HTTP protocol documentation if unfamiliar).
106     # Used to update resources.
107     def put(path, body = '', headers = {})
108       request(:put, path, body.to_s, build_request_headers(headers))
109     end
110
111     # Execute a POST request.
112     # Used to create new resources.
113     def post(path, body = '', headers = {})
114       request(:post, path, body.to_s, build_request_headers(headers))
115     end
116
117     # Execute a HEAD request.
118     # Used to ...
119     def head(path, headers= {})
120       request(:head, path, build_request_headers(headers))
121     end
122
123
124     private
125       # Makes request to remote service.
126       def request(method, path, *arguments)
127         logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
128         result = nil
129         time = Benchmark.realtime { result = http.send(method, path, *arguments) }
130         logger.info "--> #{result.code} #{result.message} (#{result.body ? result.body : 0}b %.2fs)" % time if logger
131         handle_response(result)
132       end
133
134       # Handles response and error codes from remote service.
135       def handle_response(response)
136         case response.code.to_i
137           when 301,302
138             raise(Redirection.new(response))
139           when 200...400
140             response
141           when 400
142             raise(BadRequest.new(response))
143           when 401
144             raise(UnauthorizedAccess.new(response))
145           when 403
146             raise(ForbiddenAccess.new(response))
147           when 404
148             raise(ResourceNotFound.new(response))
149           when 405
150             raise(MethodNotAllowed.new(response))
151           when 409
152             raise(ResourceConflict.new(response))
153           when 422
154             raise(ResourceInvalid.new(response))
155           when 401...500
156             raise(ClientError.new(response))
157           when 500...600
158             raise(ServerError.new(response))
159           else
160             raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
161         end
162       end
163
164       # Creates new Net::HTTP instance for communication with
165       # remote service and resources.
166       def http
167         http             = Net::HTTP.new(@site.host, @site.port)
168         http.use_ssl     = @site.is_a?(URI::HTTPS)
169         http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
170         http
171       end
172
173       def default_header
174         @default_header ||= { 'Content-Type' => format.mime_type }
175       end
176      
177       # Builds headers for request to remote service.
178       def build_request_headers(headers)
179         authorization_header.update(default_header).update(headers)
180       end
181      
182       # Sets authorization header
183       def authorization_header
184         (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
185       end
186
187       def logger #:nodoc:
188         ActiveResource::Base.logger
189       end
190   end
191 end
Note: See TracBrowser for help on using the browser.