activeresource 2.3.3 → 2.3.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeresource might be problematic. Click here for more details.
- data/CHANGELOG +13 -1
- data/Rakefile +1 -1
- data/lib/active_resource/base.rb +77 -3
- data/lib/active_resource/connection.rb +73 -8
- data/lib/active_resource/exceptions.rb +66 -0
- data/lib/active_resource/validations.rb +20 -4
- data/lib/active_resource/version.rb +1 -1
- data/test/base/load_test.rb +16 -1
- data/test/base_errors_test.rb +56 -19
- data/test/base_test.rb +147 -0
- data/test/connection_test.rb +42 -0
- data/test/debug.log +7710 -10
- data/test/fixtures/proxy.rb +4 -0
- metadata +9 -9
data/CHANGELOG
CHANGED
@@ -1,7 +1,19 @@
|
|
1
|
-
*2.3.
|
1
|
+
*2.3.4 (September 4, 2009)*
|
2
|
+
|
3
|
+
* Add support for errors in JSON format. #1956 [Fabien Jakimowicz]
|
4
|
+
|
5
|
+
* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh]
|
6
|
+
|
7
|
+
* More thorough SSL support. #2370 [Roy Nicholson]
|
8
|
+
|
9
|
+
* HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet]
|
10
|
+
|
11
|
+
|
12
|
+
*2.3.3 (July 12, 2009)*
|
2
13
|
|
3
14
|
* No changes, just a version bump.
|
4
15
|
|
16
|
+
|
5
17
|
*2.3.2 [Final] (March 15, 2009)*
|
6
18
|
|
7
19
|
* Nothing new, just included in 2.3.2
|
data/Rakefile
CHANGED
@@ -66,7 +66,7 @@ spec = Gem::Specification.new do |s|
|
|
66
66
|
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
67
67
|
end
|
68
68
|
|
69
|
-
s.add_dependency('activesupport', '= 2.3.
|
69
|
+
s.add_dependency('activesupport', '= 2.3.4' + PKG_BUILD)
|
70
70
|
|
71
71
|
s.require_path = 'lib'
|
72
72
|
s.autorequire = 'active_resource'
|
data/lib/active_resource/base.rb
CHANGED
@@ -93,6 +93,8 @@ module ActiveResource
|
|
93
93
|
#
|
94
94
|
# Many REST APIs will require authentication, usually in the form of basic
|
95
95
|
# HTTP authentication. Authentication can be specified by:
|
96
|
+
#
|
97
|
+
# === HTTP Basic Authentication
|
96
98
|
# * putting the credentials in the URL for the +site+ variable.
|
97
99
|
#
|
98
100
|
# class Person < ActiveResource::Base
|
@@ -112,6 +114,19 @@ module ActiveResource
|
|
112
114
|
#
|
113
115
|
# Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
|
114
116
|
# as usernames. In those situations you should use the separate user and password option.
|
117
|
+
#
|
118
|
+
# === Certificate Authentication
|
119
|
+
#
|
120
|
+
# * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options.
|
121
|
+
#
|
122
|
+
# class Person < ActiveResource::Base
|
123
|
+
# self.site = "https://secure.api.people.com/"
|
124
|
+
# self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file))
|
125
|
+
# :key => OpenSSL::PKey::RSA.new(File.open(pem_file)),
|
126
|
+
# :ca_path => "/path/to/OpenSSL/formatted/CA_Certs",
|
127
|
+
# :verify_mode => OpenSSL::SSL::VERIFY_PEER}
|
128
|
+
# end
|
129
|
+
#
|
115
130
|
# == Errors & Validation
|
116
131
|
#
|
117
132
|
# Error handling and validation is handled in much the same manner as you're used to seeing in
|
@@ -138,6 +153,7 @@ module ActiveResource
|
|
138
153
|
# * 404 - ActiveResource::ResourceNotFound
|
139
154
|
# * 405 - ActiveResource::MethodNotAllowed
|
140
155
|
# * 409 - ActiveResource::ResourceConflict
|
156
|
+
# * 410 - ActiveResource::ResourceGone
|
141
157
|
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
|
142
158
|
# * 401..499 - ActiveResource::ClientError
|
143
159
|
# * 500..599 - ActiveResource::ServerError
|
@@ -158,7 +174,7 @@ module ActiveResource
|
|
158
174
|
#
|
159
175
|
# Active Resource supports validations on resources and will return errors if any these validations fail
|
160
176
|
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
|
161
|
-
# a response code of <tt>422</tt> and an XML representation of the validation errors. The save operation will
|
177
|
+
# a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
|
162
178
|
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
|
163
179
|
#
|
164
180
|
# ryan = Person.find(1)
|
@@ -167,10 +183,14 @@ module ActiveResource
|
|
167
183
|
#
|
168
184
|
# # When
|
169
185
|
# # PUT http://api.people.com:3000/people/1.xml
|
186
|
+
# # or
|
187
|
+
# # PUT http://api.people.com:3000/people/1.json
|
170
188
|
# # is requested with invalid values, the response is:
|
171
189
|
# #
|
172
190
|
# # Response (422):
|
173
191
|
# # <errors type="array"><error>First cannot be empty</error></errors>
|
192
|
+
# # or
|
193
|
+
# # {"errors":["First cannot be empty"]}
|
174
194
|
# #
|
175
195
|
#
|
176
196
|
# ryan.errors.invalid?(:first) # => true
|
@@ -246,6 +266,22 @@ module ActiveResource
|
|
246
266
|
end
|
247
267
|
end
|
248
268
|
|
269
|
+
# Gets the \proxy variable if a proxy is required
|
270
|
+
def proxy
|
271
|
+
# Not using superclass_delegating_reader. See +site+ for explanation
|
272
|
+
if defined?(@proxy)
|
273
|
+
@proxy
|
274
|
+
elsif superclass != Object && superclass.proxy
|
275
|
+
superclass.proxy.dup.freeze
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Sets the URI of the http proxy to the value in the +proxy+ argument.
|
280
|
+
def proxy=(proxy)
|
281
|
+
@connection = nil
|
282
|
+
@proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
|
283
|
+
end
|
284
|
+
|
249
285
|
# Gets the \user for REST HTTP authentication.
|
250
286
|
def user
|
251
287
|
# Not using superclass_delegating_reader. See +site+ for explanation
|
@@ -315,15 +351,42 @@ module ActiveResource
|
|
315
351
|
end
|
316
352
|
end
|
317
353
|
|
354
|
+
# Options that will get applied to an SSL connection.
|
355
|
+
#
|
356
|
+
# * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
|
357
|
+
# * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
|
358
|
+
# * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contrain several CA certificates.
|
359
|
+
# * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
|
360
|
+
# * <tt>:verify_mode</tt> - Flags for server the certification verification at begining of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
|
361
|
+
# * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
|
362
|
+
# * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
|
363
|
+
# * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
|
364
|
+
# * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
|
365
|
+
def ssl_options=(opts={})
|
366
|
+
@connection = nil
|
367
|
+
@ssl_options = opts
|
368
|
+
end
|
369
|
+
|
370
|
+
# Returns the SSL options hash.
|
371
|
+
def ssl_options
|
372
|
+
if defined?(@ssl_options)
|
373
|
+
@ssl_options
|
374
|
+
elsif superclass != Object && superclass.ssl_options
|
375
|
+
superclass.ssl_options
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
318
379
|
# An instance of ActiveResource::Connection that is the base \connection to the remote service.
|
319
380
|
# The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
|
320
381
|
# or not (defaults to <tt>false</tt>).
|
321
382
|
def connection(refresh = false)
|
322
383
|
if defined?(@connection) || superclass == Object
|
323
384
|
@connection = Connection.new(site, format) if refresh || @connection.nil?
|
385
|
+
@connection.proxy = proxy if proxy
|
324
386
|
@connection.user = user if user
|
325
387
|
@connection.password = password if password
|
326
388
|
@connection.timeout = timeout if timeout
|
389
|
+
@connection.ssl_options = ssl_options if ssl_options
|
327
390
|
@connection
|
328
391
|
else
|
329
392
|
superclass.connection
|
@@ -557,7 +620,7 @@ module ActiveResource
|
|
557
620
|
response.code.to_i == 200
|
558
621
|
end
|
559
622
|
# id && !find_single(id, options).nil?
|
560
|
-
rescue ActiveResource::ResourceNotFound
|
623
|
+
rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
|
561
624
|
false
|
562
625
|
end
|
563
626
|
|
@@ -611,6 +674,11 @@ module ActiveResource
|
|
611
674
|
site.is_a?(URI) ? site.dup : URI.parse(site)
|
612
675
|
end
|
613
676
|
|
677
|
+
# Accepts a URI and creates the proxy URI from that.
|
678
|
+
def create_proxy_uri_from(proxy)
|
679
|
+
proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
|
680
|
+
end
|
681
|
+
|
614
682
|
# contains a set of the current prefix parameters.
|
615
683
|
def prefix_parameters
|
616
684
|
@prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
|
@@ -952,7 +1020,13 @@ module ActiveResource
|
|
952
1020
|
case value
|
953
1021
|
when Array
|
954
1022
|
resource = find_or_create_resource_for_collection(key)
|
955
|
-
value.map
|
1023
|
+
value.map do |attrs|
|
1024
|
+
if attrs.is_a?(String) || attrs.is_a?(Numeric)
|
1025
|
+
attrs.duplicable? ? attrs.dup : attrs
|
1026
|
+
else
|
1027
|
+
resource.new(attrs)
|
1028
|
+
end
|
1029
|
+
end
|
956
1030
|
when Hash
|
957
1031
|
resource = find_or_create_resource_for(key)
|
958
1032
|
resource.new(value)
|
@@ -26,6 +26,14 @@ module ActiveResource
|
|
26
26
|
def to_s; @message ;end
|
27
27
|
end
|
28
28
|
|
29
|
+
# Raised when a OpenSSL::SSL::SSLError occurs.
|
30
|
+
class SSLError < ConnectionError
|
31
|
+
def initialize(message)
|
32
|
+
@message = message
|
33
|
+
end
|
34
|
+
def to_s; @message ;end
|
35
|
+
end
|
36
|
+
|
29
37
|
# 3xx Redirection
|
30
38
|
class Redirection < ConnectionError # :nodoc:
|
31
39
|
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
|
@@ -49,6 +57,9 @@ module ActiveResource
|
|
49
57
|
# 409 Conflict
|
50
58
|
class ResourceConflict < ClientError; end # :nodoc:
|
51
59
|
|
60
|
+
# 410 Gone
|
61
|
+
class ResourceGone < ClientError; end # :nodoc:
|
62
|
+
|
52
63
|
# 5xx Server Error
|
53
64
|
class ServerError < ConnectionError; end # :nodoc:
|
54
65
|
|
@@ -67,10 +78,11 @@ module ActiveResource
|
|
67
78
|
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
|
68
79
|
:put => 'Content-Type',
|
69
80
|
:post => 'Content-Type',
|
70
|
-
:delete => 'Accept'
|
81
|
+
:delete => 'Accept',
|
82
|
+
:head => 'Accept'
|
71
83
|
}
|
72
84
|
|
73
|
-
attr_reader :site, :user, :password, :timeout
|
85
|
+
attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options
|
74
86
|
attr_accessor :format
|
75
87
|
|
76
88
|
class << self
|
@@ -95,7 +107,12 @@ module ActiveResource
|
|
95
107
|
@password = URI.decode(@site.password) if @site.password
|
96
108
|
end
|
97
109
|
|
98
|
-
# Set
|
110
|
+
# Set the proxy for remote service.
|
111
|
+
def proxy=(proxy)
|
112
|
+
@proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Set the user for remote service.
|
99
116
|
def user=(user)
|
100
117
|
@user = user
|
101
118
|
end
|
@@ -110,6 +127,11 @@ module ActiveResource
|
|
110
127
|
@timeout = timeout
|
111
128
|
end
|
112
129
|
|
130
|
+
# Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
|
131
|
+
def ssl_options=(opts={})
|
132
|
+
@ssl_options = opts
|
133
|
+
end
|
134
|
+
|
113
135
|
# Execute a GET request.
|
114
136
|
# Used to get (find) resources.
|
115
137
|
def get(path, headers = {})
|
@@ -137,7 +159,7 @@ module ActiveResource
|
|
137
159
|
# Execute a HEAD request.
|
138
160
|
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
|
139
161
|
def head(path, headers = {})
|
140
|
-
request(:head, path, build_request_headers(headers))
|
162
|
+
request(:head, path, build_request_headers(headers, :head))
|
141
163
|
end
|
142
164
|
|
143
165
|
|
@@ -151,6 +173,8 @@ module ActiveResource
|
|
151
173
|
handle_response(result)
|
152
174
|
rescue Timeout::Error => e
|
153
175
|
raise TimeoutError.new(e.message)
|
176
|
+
rescue OpenSSL::SSL::SSLError => e
|
177
|
+
raise SSLError.new(e.message)
|
154
178
|
end
|
155
179
|
|
156
180
|
# Handles response and error codes from remote service.
|
@@ -172,6 +196,8 @@ module ActiveResource
|
|
172
196
|
raise(MethodNotAllowed.new(response))
|
173
197
|
when 409
|
174
198
|
raise(ResourceConflict.new(response))
|
199
|
+
when 410
|
200
|
+
raise(ResourceGone.new(response))
|
175
201
|
when 422
|
176
202
|
raise(ResourceInvalid.new(response))
|
177
203
|
when 401...500
|
@@ -186,10 +212,49 @@ module ActiveResource
|
|
186
212
|
# Creates new Net::HTTP instance for communication with
|
187
213
|
# remote service and resources.
|
188
214
|
def http
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
215
|
+
configure_http(new_http)
|
216
|
+
end
|
217
|
+
|
218
|
+
def new_http
|
219
|
+
if @proxy
|
220
|
+
Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
|
221
|
+
else
|
222
|
+
Net::HTTP.new(@site.host, @site.port)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def configure_http(http)
|
227
|
+
http = apply_ssl_options(http)
|
228
|
+
|
229
|
+
# Net::HTTP timeouts default to 60 seconds.
|
230
|
+
if @timeout
|
231
|
+
http.open_timeout = @timeout
|
232
|
+
http.read_timeout = @timeout
|
233
|
+
end
|
234
|
+
|
235
|
+
http
|
236
|
+
end
|
237
|
+
|
238
|
+
def apply_ssl_options(http)
|
239
|
+
return http unless @site.is_a?(URI::HTTPS)
|
240
|
+
|
241
|
+
http.use_ssl = true
|
242
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
243
|
+
return http unless defined?(@ssl_options)
|
244
|
+
|
245
|
+
http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
|
246
|
+
http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file]
|
247
|
+
|
248
|
+
http.cert = @ssl_options[:cert] if @ssl_options[:cert]
|
249
|
+
http.key = @ssl_options[:key] if @ssl_options[:key]
|
250
|
+
|
251
|
+
http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
|
252
|
+
http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
|
253
|
+
|
254
|
+
http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
|
255
|
+
http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
|
256
|
+
http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
|
257
|
+
|
193
258
|
http
|
194
259
|
end
|
195
260
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveResource
|
2
|
+
class ConnectionError < StandardError # :nodoc:
|
3
|
+
attr_reader :response
|
4
|
+
|
5
|
+
def initialize(response, message = nil)
|
6
|
+
@response = response
|
7
|
+
@message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Raised when a Timeout::Error occurs.
|
16
|
+
class TimeoutError < ConnectionError
|
17
|
+
def initialize(message)
|
18
|
+
@message = message
|
19
|
+
end
|
20
|
+
def to_s; @message ;end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised when a OpenSSL::SSL::SSLError occurs.
|
24
|
+
class SSLError < ConnectionError
|
25
|
+
def initialize(message)
|
26
|
+
@message = message
|
27
|
+
end
|
28
|
+
def to_s; @message ;end
|
29
|
+
end
|
30
|
+
|
31
|
+
# 3xx Redirection
|
32
|
+
class Redirection < ConnectionError # :nodoc:
|
33
|
+
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
|
34
|
+
end
|
35
|
+
|
36
|
+
# 4xx Client Error
|
37
|
+
class ClientError < ConnectionError; end # :nodoc:
|
38
|
+
|
39
|
+
# 400 Bad Request
|
40
|
+
class BadRequest < ClientError; end # :nodoc
|
41
|
+
|
42
|
+
# 401 Unauthorized
|
43
|
+
class UnauthorizedAccess < ClientError; end # :nodoc
|
44
|
+
|
45
|
+
# 403 Forbidden
|
46
|
+
class ForbiddenAccess < ClientError; end # :nodoc
|
47
|
+
|
48
|
+
# 404 Not Found
|
49
|
+
class ResourceNotFound < ClientError; end # :nodoc:
|
50
|
+
|
51
|
+
# 409 Conflict
|
52
|
+
class ResourceConflict < ClientError; end # :nodoc:
|
53
|
+
|
54
|
+
# 410 Gone
|
55
|
+
class ResourceGone < ClientError; end # :nodoc:
|
56
|
+
|
57
|
+
# 5xx Server Error
|
58
|
+
class ServerError < ConnectionError; end # :nodoc:
|
59
|
+
|
60
|
+
# 405 Method Not Allowed
|
61
|
+
class MethodNotAllowed < ClientError # :nodoc:
|
62
|
+
def allowed_methods
|
63
|
+
@response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -199,11 +199,10 @@ module ActiveResource
|
|
199
199
|
alias_method :count, :size
|
200
200
|
alias_method :length, :size
|
201
201
|
|
202
|
-
# Grabs errors from
|
203
|
-
def
|
202
|
+
# Grabs errors from an array of messages (like ActiveRecord::Validations)
|
203
|
+
def from_array(messages)
|
204
204
|
clear
|
205
205
|
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
|
206
|
-
messages = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
|
207
206
|
messages.each do |message|
|
208
207
|
attr_message = humanized_attributes.keys.detect do |attr_name|
|
209
208
|
if message[0, attr_name.size + 1] == "#{attr_name} "
|
@@ -214,6 +213,18 @@ module ActiveResource
|
|
214
213
|
add_to_base message if attr_message.nil?
|
215
214
|
end
|
216
215
|
end
|
216
|
+
|
217
|
+
# Grabs errors from the json response.
|
218
|
+
def from_json(json)
|
219
|
+
array = ActiveSupport::JSON.decode(json)['errors'] rescue []
|
220
|
+
from_array array
|
221
|
+
end
|
222
|
+
|
223
|
+
# Grabs errors from the XML response.
|
224
|
+
def from_xml(xml)
|
225
|
+
array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
|
226
|
+
from_array array
|
227
|
+
end
|
217
228
|
end
|
218
229
|
|
219
230
|
# Module to support validation and errors with Active Resource objects. The module overrides
|
@@ -248,7 +259,12 @@ module ActiveResource
|
|
248
259
|
save_without_validation
|
249
260
|
true
|
250
261
|
rescue ResourceInvalid => error
|
251
|
-
|
262
|
+
case error.response['Content-Type']
|
263
|
+
when 'application/xml'
|
264
|
+
errors.from_xml(error.response.body)
|
265
|
+
when 'application/json'
|
266
|
+
errors.from_json(error.response.body)
|
267
|
+
end
|
252
268
|
false
|
253
269
|
end
|
254
270
|
|