Froze rails gems
[depot.git] / vendor / rails / activeresource / lib / active_resource / connection.rb
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 # Raised when a Timeout::Error occurs.
22 class TimeoutError < ConnectionError
23 def initialize(message)
24 @message = message
25 end
26 def to_s; @message ;end
27 end
28
29 # 3xx Redirection
30 class Redirection < ConnectionError # :nodoc:
31 def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
32 end
33
34 # 4xx Client Error
35 class ClientError < ConnectionError; end # :nodoc:
36
37 # 400 Bad Request
38 class BadRequest < ClientError; end # :nodoc
39
40 # 401 Unauthorized
41 class UnauthorizedAccess < ClientError; end # :nodoc
42
43 # 403 Forbidden
44 class ForbiddenAccess < ClientError; end # :nodoc
45
46 # 404 Not Found
47 class ResourceNotFound < ClientError; end # :nodoc:
48
49 # 409 Conflict
50 class ResourceConflict < ClientError; end # :nodoc:
51
52 # 5xx Server Error
53 class ServerError < ConnectionError; end # :nodoc:
54
55 # 405 Method Not Allowed
56 class MethodNotAllowed < ClientError # :nodoc:
57 def allowed_methods
58 @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
59 end
60 end
61
62 # Class to handle connections to remote web services.
63 # This class is used by ActiveResource::Base to interface with REST
64 # services.
65 class Connection
66
67 HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
68 :put => 'Content-Type',
69 :post => 'Content-Type',
70 :delete => 'Accept'
71 }
72
73 attr_reader :site, :user, :password, :timeout
74 attr_accessor :format
75
76 class << self
77 def requests
78 @@requests ||= []
79 end
80 end
81
82 # The +site+ parameter is required and will set the +site+
83 # attribute to the URI for the remote resource service.
84 def initialize(site, format = ActiveResource::Formats[:xml])
85 raise ArgumentError, 'Missing site URI' unless site
86 @user = @password = nil
87 self.site = site
88 self.format = format
89 end
90
91 # Set URI for remote service.
92 def site=(site)
93 @site = site.is_a?(URI) ? site : URI.parse(site)
94 @user = URI.decode(@site.user) if @site.user
95 @password = URI.decode(@site.password) if @site.password
96 end
97
98 # Set user for remote service.
99 def user=(user)
100 @user = user
101 end
102
103 # Set password for remote service.
104 def password=(password)
105 @password = password
106 end
107
108 # Set the number of seconds after which HTTP requests to the remote service should time out.
109 def timeout=(timeout)
110 @timeout = timeout
111 end
112
113 # Execute a GET request.
114 # Used to get (find) resources.
115 def get(path, headers = {})
116 format.decode(request(:get, path, build_request_headers(headers, :get)).body)
117 end
118
119 # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
120 # Used to delete resources.
121 def delete(path, headers = {})
122 request(:delete, path, build_request_headers(headers, :delete))
123 end
124
125 # Execute a PUT request (see HTTP protocol documentation if unfamiliar).
126 # Used to update resources.
127 def put(path, body = '', headers = {})
128 request(:put, path, body.to_s, build_request_headers(headers, :put))
129 end
130
131 # Execute a POST request.
132 # Used to create new resources.
133 def post(path, body = '', headers = {})
134 request(:post, path, body.to_s, build_request_headers(headers, :post))
135 end
136
137 # Execute a HEAD request.
138 # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
139 def head(path, headers = {})
140 request(:head, path, build_request_headers(headers))
141 end
142
143
144 private
145 # Makes request to remote service.
146 def request(method, path, *arguments)
147 logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
148 result = nil
149 time = Benchmark.realtime { result = http.send(method, path, *arguments) }
150 logger.info "--> %d %s (%d %.2fs)" % [result.code, result.message, result.body ? result.body.length : 0, time] if logger
151 handle_response(result)
152 rescue Timeout::Error => e
153 raise TimeoutError.new(e.message)
154 end
155
156 # Handles response and error codes from remote service.
157 def handle_response(response)
158 case response.code.to_i
159 when 301,302
160 raise(Redirection.new(response))
161 when 200...400
162 response
163 when 400
164 raise(BadRequest.new(response))
165 when 401
166 raise(UnauthorizedAccess.new(response))
167 when 403
168 raise(ForbiddenAccess.new(response))
169 when 404
170 raise(ResourceNotFound.new(response))
171 when 405
172 raise(MethodNotAllowed.new(response))
173 when 409
174 raise(ResourceConflict.new(response))
175 when 422
176 raise(ResourceInvalid.new(response))
177 when 401...500
178 raise(ClientError.new(response))
179 when 500...600
180 raise(ServerError.new(response))
181 else
182 raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
183 end
184 end
185
186 # Creates new Net::HTTP instance for communication with
187 # remote service and resources.
188 def http
189 http = Net::HTTP.new(@site.host, @site.port)
190 http.use_ssl = @site.is_a?(URI::HTTPS)
191 http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
192 http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
193 http
194 end
195
196 def default_header
197 @default_header ||= {}
198 end
199
200 # Builds headers for request to remote service.
201 def build_request_headers(headers, http_method=nil)
202 authorization_header.update(default_header).update(http_format_header(http_method)).update(headers)
203 end
204
205 # Sets authorization header
206 def authorization_header
207 (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
208 end
209
210 def http_format_header(http_method)
211 {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
212 end
213
214 def logger #:nodoc:
215 Base.logger
216 end
217 end
218 end