activeresource_csi 2.3.5.p6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'