activeresource_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,283 @@
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
+ # 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
+
37
+ # 3xx Redirection
38
+ class Redirection < ConnectionError # :nodoc:
39
+ def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
40
+ end
41
+
42
+ # 4xx Client Error
43
+ class ClientError < ConnectionError; end # :nodoc:
44
+
45
+ # 400 Bad Request
46
+ class BadRequest < ClientError; end # :nodoc
47
+
48
+ # 401 Unauthorized
49
+ class UnauthorizedAccess < ClientError; end # :nodoc
50
+
51
+ # 403 Forbidden
52
+ class ForbiddenAccess < ClientError; end # :nodoc
53
+
54
+ # 404 Not Found
55
+ class ResourceNotFound < ClientError; end # :nodoc:
56
+
57
+ # 409 Conflict
58
+ class ResourceConflict < ClientError; end # :nodoc:
59
+
60
+ # 410 Gone
61
+ class ResourceGone < ClientError; end # :nodoc:
62
+
63
+ # 5xx Server Error
64
+ class ServerError < ConnectionError; end # :nodoc:
65
+
66
+ # 405 Method Not Allowed
67
+ class MethodNotAllowed < ClientError # :nodoc:
68
+ def allowed_methods
69
+ @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
70
+ end
71
+ end
72
+
73
+ # Class to handle connections to remote web services.
74
+ # This class is used by ActiveResource::Base to interface with REST
75
+ # services.
76
+ class Connection
77
+
78
+ HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
79
+ :put => 'Content-Type',
80
+ :post => 'Content-Type',
81
+ :delete => 'Accept',
82
+ :head => 'Accept'
83
+ }
84
+
85
+ attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options
86
+ attr_accessor :format
87
+
88
+ class << self
89
+ def requests
90
+ @@requests ||= []
91
+ end
92
+ end
93
+
94
+ # The +site+ parameter is required and will set the +site+
95
+ # attribute to the URI for the remote resource service.
96
+ def initialize(site, format = ActiveResource::Formats[:xml])
97
+ raise ArgumentError, 'Missing site URI' unless site
98
+ @user = @password = nil
99
+ self.site = site
100
+ self.format = format
101
+ end
102
+
103
+ # Set URI for remote service.
104
+ def site=(site)
105
+ @site = site.is_a?(URI) ? site : URI.parse(site)
106
+ @user = URI.decode(@site.user) if @site.user
107
+ @password = URI.decode(@site.password) if @site.password
108
+ end
109
+
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.
116
+ def user=(user)
117
+ @user = user
118
+ end
119
+
120
+ # Set password for remote service.
121
+ def password=(password)
122
+ @password = password
123
+ end
124
+
125
+ # Set the number of seconds after which HTTP requests to the remote service should time out.
126
+ def timeout=(timeout)
127
+ @timeout = timeout
128
+ end
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
+
135
+ # Execute a GET request.
136
+ # Used to get (find) resources.
137
+ def get(path, headers = {})
138
+ format.decode(request(:get, path, build_request_headers(headers, :get)).body)
139
+ end
140
+
141
+ # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
142
+ # Used to delete resources.
143
+ def delete(path, headers = {})
144
+ request(:delete, path, build_request_headers(headers, :delete))
145
+ end
146
+
147
+ # Execute a PUT request (see HTTP protocol documentation if unfamiliar).
148
+ # Used to update resources.
149
+ def put(path, body = '', headers = {})
150
+ request(:put, path, body.to_s, build_request_headers(headers, :put))
151
+ end
152
+
153
+ # Execute a POST request.
154
+ # Used to create new resources.
155
+ def post(path, body = '', headers = {})
156
+ request(:post, path, body.to_s, build_request_headers(headers, :post))
157
+ end
158
+
159
+ # Execute a HEAD request.
160
+ # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
161
+ def head(path, headers = {})
162
+ request(:head, path, build_request_headers(headers, :head))
163
+ end
164
+
165
+
166
+ private
167
+ # Makes request to remote service.
168
+ def request(method, path, *arguments)
169
+ logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
170
+ result = nil
171
+ ms = Benchmark.ms { result = http.send(method, path, *arguments) }
172
+ logger.info "--> %d %s (%d %.0fms)" % [result.code, result.message, result.body ? result.body.length : 0, ms] if logger
173
+ handle_response(result)
174
+ rescue Timeout::Error => e
175
+ raise TimeoutError.new(e.message)
176
+ rescue OpenSSL::SSL::SSLError => e
177
+ raise SSLError.new(e.message)
178
+ end
179
+
180
+ # Handles response and error codes from remote service.
181
+ def handle_response(response)
182
+ case response.code.to_i
183
+ when 301,302
184
+ raise(Redirection.new(response))
185
+ when 200...400
186
+ response
187
+ when 400
188
+ raise(BadRequest.new(response))
189
+ when 401
190
+ raise(UnauthorizedAccess.new(response))
191
+ when 403
192
+ raise(ForbiddenAccess.new(response))
193
+ when 404
194
+ raise(ResourceNotFound.new(response))
195
+ when 405
196
+ raise(MethodNotAllowed.new(response))
197
+ when 409
198
+ raise(ResourceConflict.new(response))
199
+ when 410
200
+ raise(ResourceGone.new(response))
201
+ when 422
202
+ raise(ResourceInvalid.new(response))
203
+ when 401...500
204
+ raise(ClientError.new(response))
205
+ when 500...600
206
+ raise(ServerError.new(response))
207
+ else
208
+ raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
209
+ end
210
+ end
211
+
212
+ # Creates new Net::HTTP instance for communication with
213
+ # remote service and resources.
214
+ def http
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
+
258
+ http
259
+ end
260
+
261
+ def default_header
262
+ @default_header ||= {}
263
+ end
264
+
265
+ # Builds headers for request to remote service.
266
+ def build_request_headers(headers, http_method=nil)
267
+ authorization_header.update(default_header).update(http_format_header(http_method)).update(headers)
268
+ end
269
+
270
+ # Sets authorization header
271
+ def authorization_header
272
+ (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
273
+ end
274
+
275
+ def http_format_header(http_method)
276
+ {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
277
+ end
278
+
279
+ def logger #:nodoc:
280
+ Base.logger
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,120 @@
1
+ module ActiveResource
2
+ # A module to support custom REST methods and sub-resources, allowing you to break out
3
+ # of the "default" REST methods with your own custom resource requests. For example,
4
+ # say you use Rails to expose a REST service and configure your routes with:
5
+ #
6
+ # map.resources :people, :new => { :register => :post },
7
+ # :member => { :promote => :put, :deactivate => :delete }
8
+ # :collection => { :active => :get }
9
+ #
10
+ # This route set creates routes for the following HTTP requests:
11
+ #
12
+ # POST /people/new/register.xml # PeopleController.register
13
+ # PUT /people/1/promote.xml # PeopleController.promote with :id => 1
14
+ # DELETE /people/1/deactivate.xml # PeopleController.deactivate with :id => 1
15
+ # GET /people/active.xml # PeopleController.active
16
+ #
17
+ # Using this module, Active Resource can use these custom REST methods just like the
18
+ # standard methods.
19
+ #
20
+ # class Person < ActiveResource::Base
21
+ # self.site = "http://37s.sunrise.i:3000"
22
+ # end
23
+ #
24
+ # Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.xml
25
+ # # => { :id => 1, :name => 'Ryan' }
26
+ #
27
+ # Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.xml
28
+ # Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.xml
29
+ #
30
+ # Person.get(:active) # GET /people/active.xml
31
+ # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
32
+ #
33
+ module CustomMethods
34
+ def self.included(base)
35
+ base.class_eval do
36
+ extend ActiveResource::CustomMethods::ClassMethods
37
+ include ActiveResource::CustomMethods::InstanceMethods
38
+
39
+ class << self
40
+ alias :orig_delete :delete
41
+
42
+ # Invokes a GET to a given custom REST method. For example:
43
+ #
44
+ # Person.get(:active) # GET /people/active.xml
45
+ # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
46
+ #
47
+ # Person.get(:active, :awesome => true) # GET /people/active.xml?awesome=true
48
+ # # => [{:id => 1, :name => 'Ryan'}]
49
+ #
50
+ # Note: the objects returned from this method are not automatically converted
51
+ # into ActiveResource::Base instances - they are ordinary Hashes. If you are expecting
52
+ # ActiveResource::Base instances, use the <tt>find</tt> class method with the
53
+ # <tt>:from</tt> option. For example:
54
+ #
55
+ # Person.find(:all, :from => :active)
56
+ def get(custom_method_name, options = {})
57
+ connection.get(custom_method_collection_url(custom_method_name, options), headers)
58
+ end
59
+
60
+ def post(custom_method_name, options = {}, body = '')
61
+ connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
62
+ end
63
+
64
+ def put(custom_method_name, options = {}, body = '')
65
+ connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
66
+ end
67
+
68
+ def delete(custom_method_name, options = {})
69
+ # Need to jump through some hoops to retain the original class 'delete' method
70
+ if custom_method_name.is_a?(Symbol)
71
+ connection.delete(custom_method_collection_url(custom_method_name, options), headers)
72
+ else
73
+ orig_delete(custom_method_name, options)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ module ClassMethods
81
+ def custom_method_collection_url(method_name, options = {})
82
+ prefix_options, query_options = split_options(options)
83
+ "#{prefix(prefix_options)}#{collection_name}/#{method_name}.#{format.extension}#{query_string(query_options)}"
84
+ end
85
+ end
86
+
87
+ module InstanceMethods
88
+ def get(method_name, options = {})
89
+ connection.get(custom_method_element_url(method_name, options), self.class.headers)
90
+ end
91
+
92
+ def post(method_name, options = {}, body = nil)
93
+ request_body = body.blank? ? encode : body
94
+ if new?
95
+ connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers)
96
+ else
97
+ connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers)
98
+ end
99
+ end
100
+
101
+ def put(method_name, options = {}, body = '')
102
+ connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
103
+ end
104
+
105
+ def delete(method_name, options = {})
106
+ connection.delete(custom_method_element_url(method_name, options), self.class.headers)
107
+ end
108
+
109
+
110
+ private
111
+ def custom_method_element_url(method_name, options = {})
112
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
113
+ end
114
+
115
+ def custom_method_new_element_url(method_name, options = {})
116
+ "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -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
@@ -0,0 +1,23 @@
1
+ module ActiveResource
2
+ module Formats
3
+ module JsonFormat
4
+ extend self
5
+
6
+ def extension
7
+ "json"
8
+ end
9
+
10
+ def mime_type
11
+ "application/json"
12
+ end
13
+
14
+ def encode(hash, options = nil)
15
+ ActiveSupport::JSON.encode(hash, options)
16
+ end
17
+
18
+ def decode(json)
19
+ ActiveSupport::JSON.decode(json)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveResource
2
+ module Formats
3
+ module XmlFormat
4
+ extend self
5
+
6
+ def extension
7
+ "xml"
8
+ end
9
+
10
+ def mime_type
11
+ "application/xml"
12
+ end
13
+
14
+ def encode(hash, options={})
15
+ hash.to_xml(options)
16
+ end
17
+
18
+ def decode(xml)
19
+ from_xml_data(Hash.from_xml(xml))
20
+ end
21
+
22
+ private
23
+ # Manipulate from_xml Hash, because xml_simple is not exactly what we
24
+ # want for Active Resource.
25
+ def from_xml_data(data)
26
+ if data.is_a?(Hash) && data.keys.size == 1
27
+ data.values.first
28
+ else
29
+ data
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveResource
2
+ module Formats
3
+ # Lookup the format class from a mime type reference symbol. Example:
4
+ #
5
+ # ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat
6
+ # ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
7
+ def self.[](mime_type_reference)
8
+ ActiveResource::Formats.const_get(mime_type_reference.to_s.camelize + "Format")
9
+ end
10
+ end
11
+ end
12
+
13
+ require 'active_resource/formats/xml_format'
14
+ require 'active_resource/formats/json_format'