restfulie 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +83 -7
- data/Rakefile +98 -13
- data/lib/restfulie/client/base.rb +48 -53
- data/lib/restfulie/client/configuration.rb +69 -0
- data/lib/restfulie/client/http/adapter.rb +487 -0
- data/lib/restfulie/client/http/atom_ext.rb +87 -0
- data/lib/restfulie/client/http/cache.rb +28 -0
- data/lib/restfulie/client/http/error.rb +80 -0
- data/lib/restfulie/client/http/marshal.rb +147 -0
- data/lib/restfulie/client/http.rb +13 -0
- data/lib/restfulie/client.rb +8 -56
- data/lib/restfulie/common/builder/builder_base.rb +58 -0
- data/lib/restfulie/common/builder/helpers.rb +22 -0
- data/lib/restfulie/common/builder/marshalling/atom.rb +197 -0
- data/lib/restfulie/common/builder/marshalling/base.rb +12 -0
- data/lib/restfulie/common/builder/marshalling/json.rb +2 -0
- data/lib/restfulie/common/builder/marshalling.rb +16 -0
- data/lib/restfulie/common/builder/rules/collection_rule.rb +10 -0
- data/lib/restfulie/common/builder/rules/link.rb +20 -0
- data/lib/restfulie/common/builder/rules/links.rb +9 -0
- data/lib/restfulie/common/builder/rules/member_rule.rb +8 -0
- data/lib/restfulie/common/builder/rules/namespace.rb +25 -0
- data/lib/restfulie/common/builder/rules/rules_base.rb +76 -0
- data/lib/restfulie/common/builder.rb +16 -0
- data/lib/restfulie/common/errors.rb +9 -0
- data/lib/restfulie/{logger.rb → common/logger.rb} +3 -5
- data/lib/restfulie/common/representation/atom.rb +48 -0
- data/lib/restfulie/common/representation/generic.rb +33 -0
- data/lib/restfulie/common/representation/xml.rb +24 -0
- data/lib/restfulie/common/representation.rb +10 -0
- data/lib/restfulie/common.rb +23 -0
- data/lib/restfulie/server/action_controller/base.rb +31 -0
- data/lib/restfulie/server/action_controller/params_parser.rb +62 -0
- data/lib/restfulie/server/action_controller/restful_responder.rb +39 -0
- data/lib/restfulie/server/action_controller/routing/restful_route.rb +14 -0
- data/lib/restfulie/server/action_controller/routing.rb +12 -0
- data/lib/restfulie/server/action_controller.rb +15 -0
- data/lib/restfulie/server/action_view/helpers.rb +45 -0
- data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +15 -0
- data/lib/restfulie/server/action_view/template_handlers.rb +13 -0
- data/lib/restfulie/server/action_view.rb +8 -0
- data/lib/restfulie/server/configuration.rb +21 -0
- data/lib/restfulie/server/core_ext/array.rb +45 -0
- data/lib/restfulie/server/core_ext.rb +1 -0
- data/lib/restfulie/server/restfulie_controller.rb +1 -17
- data/lib/restfulie/server.rb +15 -0
- data/lib/restfulie.rb +4 -72
- data/lib/vendor/atom/configuration.rb +24 -0
- data/lib/vendor/atom/pub.rb +250 -0
- data/lib/vendor/atom/xml/parser.rb +373 -0
- data/lib/vendor/atom.rb +771 -0
- metadata +94 -33
- data/lib/restfulie/client/atom_media_type.rb +0 -75
- data/lib/restfulie/client/cache.rb +0 -103
- data/lib/restfulie/client/entry_point.rb +0 -94
- data/lib/restfulie/client/extensions/http.rb +0 -116
- data/lib/restfulie/client/helper.rb +0 -28
- data/lib/restfulie/client/instance.rb +0 -158
- data/lib/restfulie/client/request_execution.rb +0 -321
- data/lib/restfulie/client/state.rb +0 -36
- data/lib/restfulie/media_type.rb +0 -143
- data/lib/restfulie/media_type_control.rb +0 -115
- data/lib/restfulie/media_type_defaults.rb +0 -51
- data/lib/restfulie/server/atom_media_type.rb +0 -115
- data/lib/restfulie/server/base.rb +0 -91
- data/lib/restfulie/server/controller.rb +0 -122
- data/lib/restfulie/server/instance.rb +0 -102
- data/lib/restfulie/server/marshalling.rb +0 -47
- data/lib/restfulie/server/opensearch/description.rb +0 -54
- data/lib/restfulie/server/opensearch.rb +0 -18
- data/lib/restfulie/server/transition.rb +0 -93
- data/lib/restfulie/unmarshalling.rb +0 -131
- data/lib/vendor/jeokkarak/hashi.rb +0 -65
- data/lib/vendor/jeokkarak/jeokkarak.rb +0 -81
@@ -0,0 +1,487 @@
|
|
1
|
+
module Restfulie::Client::HTTP #:nodoc:
|
2
|
+
|
3
|
+
#=Response
|
4
|
+
# Default response class
|
5
|
+
class Response
|
6
|
+
|
7
|
+
attr_reader :method
|
8
|
+
attr_reader :path
|
9
|
+
attr_reader :code
|
10
|
+
attr_reader :body
|
11
|
+
attr_reader :headers
|
12
|
+
attr_reader :request
|
13
|
+
|
14
|
+
def initialize(method, path, code, body, headers, request)
|
15
|
+
@method = method
|
16
|
+
@path = path
|
17
|
+
@code = code
|
18
|
+
@body = body
|
19
|
+
@headers = headers
|
20
|
+
@request = request
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
#=ResponseHandler
|
30
|
+
# You can change instance registering a class according to the code.
|
31
|
+
#
|
32
|
+
#==Example
|
33
|
+
#
|
34
|
+
# class RequestExecutor
|
35
|
+
# include RequestAdapter
|
36
|
+
# def initialize(host)
|
37
|
+
# self.host=host
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class FakeResponse < Restfulie::Client::HTTP::Response
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Restfulie::Client::HTTP::ResponseHandler.register(201,FakeResponse)
|
45
|
+
# @re = Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com')
|
46
|
+
# puts @re.as('application/atom+xml').get!('/posts').class.to_i #=> FakeResponse
|
47
|
+
#
|
48
|
+
module ResponseHandler
|
49
|
+
|
50
|
+
@@response_handlers = {}
|
51
|
+
##
|
52
|
+
# :singleton-method:
|
53
|
+
# Response handlers attribute reader
|
54
|
+
# * code: HTTP status code
|
55
|
+
def self.handlers(code)
|
56
|
+
@@response_handlers[code]
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# :singleton-method:
|
61
|
+
# Use to register response handlers
|
62
|
+
#
|
63
|
+
# * <tt>code: HTTP status code</tt>
|
64
|
+
# * <tt>response_class: Response class</tt>
|
65
|
+
#
|
66
|
+
#==Example:
|
67
|
+
# class FakeResponse < ::Restfulie::Client::HTTP::Response
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# Restfulie::Client::HTTP::ResponseHandler.register(200,FakeResponse)
|
71
|
+
#
|
72
|
+
def self.register(code,response_class)
|
73
|
+
@@response_handlers[code] = response_class
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# :singleton-method:
|
78
|
+
# Request Adapter uses this method to choose response instance
|
79
|
+
#
|
80
|
+
# *<tt>method: :get,:post,:delete,:head,:put</tt>
|
81
|
+
# *<tt>path: '/posts'</tt>
|
82
|
+
# *<tt>http_response</tt>
|
83
|
+
#
|
84
|
+
def self.handle(method, path, http_response, request)
|
85
|
+
response_class = @@response_handlers[http_response.code.to_i] || Response
|
86
|
+
headers = {}
|
87
|
+
http_response.header.each { |k, v| headers[k] = v }
|
88
|
+
response_class.new( method, path, http_response.code.to_i, http_response.body, headers, request)
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Request Adapter provides a minimal interface to exchange information between server over HTTP protocol through simple adapters.
|
95
|
+
#
|
96
|
+
# All the concrete adapters follow the interface laid down in this module.
|
97
|
+
# Default connection provider is net/http
|
98
|
+
#
|
99
|
+
#==Example
|
100
|
+
#
|
101
|
+
# @re = ::Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com') #this class includes RequestAdapter module.
|
102
|
+
# puts @re.as('application/atom+xml').get!('/posts').title #=> 'Hello World!'
|
103
|
+
#
|
104
|
+
module RequestAdapter
|
105
|
+
|
106
|
+
attr_reader :host
|
107
|
+
attr_accessor :cookies
|
108
|
+
attr_writer :default_headers
|
109
|
+
|
110
|
+
def host=(host)
|
111
|
+
if host.is_a?(::URI)
|
112
|
+
@host = host
|
113
|
+
else
|
114
|
+
@host = ::URI.parse(host)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def default_headers
|
119
|
+
@default_headers ||= {}
|
120
|
+
end
|
121
|
+
|
122
|
+
#GET HTTP verb without {Error}
|
123
|
+
# * <tt>path: '/posts'</tt>
|
124
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
125
|
+
def get(path, *args)
|
126
|
+
request(:get, path, *args)
|
127
|
+
end
|
128
|
+
|
129
|
+
#HEAD HTTP verb without {Error}
|
130
|
+
# * <tt>path: '/posts'</tt>
|
131
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
132
|
+
def head(path, *args)
|
133
|
+
request(:head, path, *args)
|
134
|
+
end
|
135
|
+
|
136
|
+
#POST HTTP verb without {Error}
|
137
|
+
# * <tt>path: '/posts'</tt>
|
138
|
+
# * <tt>payload: 'some text'</tt>
|
139
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
140
|
+
def post(path, payload, *args)
|
141
|
+
request(:post, path, payload, *args)
|
142
|
+
end
|
143
|
+
|
144
|
+
#PUT HTTP verb without {Error}
|
145
|
+
# * <tt>path: '/posts'</tt>
|
146
|
+
# * <tt>payload: 'some text'</tt>
|
147
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
148
|
+
def put(path, payload, *args)
|
149
|
+
request(:put, path, payload, *args)
|
150
|
+
end
|
151
|
+
|
152
|
+
#DELETE HTTP verb without {Error}
|
153
|
+
# * <tt>path: '/posts'</tt>
|
154
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
155
|
+
def delete(path, *args)
|
156
|
+
request(:delete, path, *args)
|
157
|
+
end
|
158
|
+
|
159
|
+
#GET HTTP verb {Error}
|
160
|
+
# * <tt>path: '/posts'</tt>
|
161
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
162
|
+
def get!(path, *args)
|
163
|
+
request!(:get, path, *args)
|
164
|
+
end
|
165
|
+
|
166
|
+
#HEAD HTTP verb {Error}
|
167
|
+
# * <tt>path: '/posts'</tt>
|
168
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
169
|
+
def head!(path, *args)
|
170
|
+
request!(:head, path, *args)
|
171
|
+
end
|
172
|
+
|
173
|
+
#POST HTTP verb {Error}
|
174
|
+
# * <tt>path: '/posts'</tt>
|
175
|
+
# * <tt>payload: 'some text'</tt>
|
176
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
177
|
+
def post!(path, payload, *args)
|
178
|
+
request!(:post, path, payload, *args)
|
179
|
+
end
|
180
|
+
|
181
|
+
#PUT HTTP verb {Error}
|
182
|
+
# * <tt>path: '/posts'</tt>
|
183
|
+
# * <tt>payload: 'some text'</tt>
|
184
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
185
|
+
def put!(path, payload, *args)
|
186
|
+
request!(:put, path, payload, *args)
|
187
|
+
end
|
188
|
+
|
189
|
+
#DELETE HTTP verb {Error}
|
190
|
+
# * <tt>path: '/posts'</tt>
|
191
|
+
# * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
192
|
+
def delete!(path, *args)
|
193
|
+
request!(:delete, path, *args)
|
194
|
+
end
|
195
|
+
|
196
|
+
#Executes a request against your server and return a response instance without {Error}
|
197
|
+
# * <tt>method: :get,:post,:delete,:head,:put</tt>
|
198
|
+
# * <tt>path: '/posts'</tt>
|
199
|
+
# * <tt>args: payload: 'some text' and/or headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
200
|
+
def request(method, path, *args)
|
201
|
+
request!(method, path, *args)
|
202
|
+
rescue Error::RESTError => se
|
203
|
+
se.response
|
204
|
+
end
|
205
|
+
|
206
|
+
#Executes a request against your server and return a response instance.
|
207
|
+
# * <tt>method: :get,:post,:delete,:head,:put</tt>
|
208
|
+
# * <tt>path: '/posts'</tt>
|
209
|
+
# * <tt>args: payload: 'some text' and/or headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
210
|
+
def request!(method, path, *args)
|
211
|
+
headers = default_headers.merge(args.extract_options!)
|
212
|
+
unless @host.user.blank? && @host.password.blank?
|
213
|
+
headers["Authorization"] = "Basic " + ["#{@host.user}:#{@host.password}"].pack("m").delete("\r\n")
|
214
|
+
end
|
215
|
+
headers['cookie'] = @cookies if @cookies
|
216
|
+
args << headers
|
217
|
+
|
218
|
+
::Restfulie::Common::Logger.logger.info(request_to_s(method, path, *args)) if ::Restfulie::Common::Logger.logger
|
219
|
+
begin
|
220
|
+
connection = get_connection_provider.send(method, path, *args)
|
221
|
+
response = ResponseHandler.handle(method, path, connection, self).parse
|
222
|
+
rescue Exception => e
|
223
|
+
raise Error::ServerNotAvailableError.new(self, Response.new(method, path, 503, nil, {}, self), e )
|
224
|
+
end
|
225
|
+
|
226
|
+
case response.code
|
227
|
+
when 100..299
|
228
|
+
response
|
229
|
+
when 300..399
|
230
|
+
raise Error::Redirection.new(self, response)
|
231
|
+
when 400
|
232
|
+
raise Error::BadRequest.new(self, response)
|
233
|
+
when 401
|
234
|
+
raise Error::Unauthorized.new(self, response)
|
235
|
+
when 403
|
236
|
+
raise Error::Forbidden.new(self, response)
|
237
|
+
when 404
|
238
|
+
raise Error::NotFound.new(self, response)
|
239
|
+
when 405
|
240
|
+
raise Error::MethodNotAllowed.new(self, response)
|
241
|
+
when 407
|
242
|
+
raise Error::ProxyAuthenticationRequired.new(self, response)
|
243
|
+
when 409
|
244
|
+
raise Error::Conflict.new(self, response)
|
245
|
+
when 410
|
246
|
+
raise Error::Gone.new(self, response)
|
247
|
+
when 412
|
248
|
+
raise Error::PreconditionFailed.new(self, response)
|
249
|
+
when 402, 406, 408, 411, 413..499
|
250
|
+
raise Error::ClientError.new(self, response)
|
251
|
+
when 501
|
252
|
+
raise Error::NotImplemented.new(self, response)
|
253
|
+
when 500, 502..599
|
254
|
+
raise Error::ServerError.new(self, response)
|
255
|
+
else
|
256
|
+
raise Error::UnknownError.new(self, response)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def get_connection_provider
|
263
|
+
@connection ||= ::Net::HTTP.new(@host.host, @host.port)
|
264
|
+
end
|
265
|
+
|
266
|
+
protected
|
267
|
+
|
268
|
+
def request_to_s(method, path, *args)
|
269
|
+
result = ["#{method.to_s.upcase} #{path}"]
|
270
|
+
|
271
|
+
arguments = args.dup
|
272
|
+
headers = arguments.extract_options!
|
273
|
+
|
274
|
+
if [:post, :put].include?(method)
|
275
|
+
body = arguments.shift
|
276
|
+
end
|
277
|
+
|
278
|
+
result << headers.collect { |key, value| "#{key}: #{value}" }.join("\n")
|
279
|
+
|
280
|
+
(result + [body ? (body.inspect + "\n") : nil]).compact.join("\n") << "\n"
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
#=RequestBuilder
|
286
|
+
# Uses RequestAdapater to create a HTTP Request DSL
|
287
|
+
#
|
288
|
+
#==Example:
|
289
|
+
#
|
290
|
+
# @builder = ::Restfulie::Client::HTTP::RequestBuilderExecutor.new("http://restfulie.com") #this class includes RequestBuilder module.
|
291
|
+
# @builder.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200
|
292
|
+
#
|
293
|
+
module RequestBuilder
|
294
|
+
include RequestAdapter
|
295
|
+
|
296
|
+
#Set host
|
297
|
+
def at(url)
|
298
|
+
self.host = url
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
#Set Content-Type and Accept headers
|
303
|
+
def as(content_type)
|
304
|
+
headers['Content-Type'] = content_type
|
305
|
+
accepts(content_type)
|
306
|
+
end
|
307
|
+
|
308
|
+
#Set Accept headers
|
309
|
+
def accepts(content_type)
|
310
|
+
headers['Accept'] = content_type
|
311
|
+
self
|
312
|
+
end
|
313
|
+
|
314
|
+
#
|
315
|
+
#Merge internal header
|
316
|
+
#
|
317
|
+
# * <tt>headers (e.g. {'Cache-control' => 'no-cache'})</tt>
|
318
|
+
#
|
319
|
+
def with(headers)
|
320
|
+
self.headers.merge!(headers)
|
321
|
+
self
|
322
|
+
end
|
323
|
+
|
324
|
+
def headers
|
325
|
+
@headers || @headers = {}
|
326
|
+
end
|
327
|
+
|
328
|
+
#Path (e.g. http://restfulie.com/posts => /posts)
|
329
|
+
def path
|
330
|
+
host.path
|
331
|
+
end
|
332
|
+
|
333
|
+
def get
|
334
|
+
request(:get, path, headers)
|
335
|
+
end
|
336
|
+
|
337
|
+
def head
|
338
|
+
request(:head, path, headers)
|
339
|
+
end
|
340
|
+
|
341
|
+
def post(payload)
|
342
|
+
request(:post, path, payload, headers)
|
343
|
+
end
|
344
|
+
|
345
|
+
def put(payload)
|
346
|
+
request(:put, path, payload, headers)
|
347
|
+
end
|
348
|
+
|
349
|
+
def delete
|
350
|
+
request(:delete, path, headers)
|
351
|
+
end
|
352
|
+
|
353
|
+
def get!
|
354
|
+
request!(:get, path, headers)
|
355
|
+
end
|
356
|
+
|
357
|
+
def head!
|
358
|
+
request!(:head, path, headers)
|
359
|
+
end
|
360
|
+
|
361
|
+
def post!(payload)
|
362
|
+
request!(:post, path, payload, headers)
|
363
|
+
end
|
364
|
+
|
365
|
+
def put!(payload)
|
366
|
+
request!(:put, path, payload, headers)
|
367
|
+
end
|
368
|
+
|
369
|
+
def delete!
|
370
|
+
request!(:delete, path, headers)
|
371
|
+
end
|
372
|
+
|
373
|
+
protected
|
374
|
+
|
375
|
+
def headers=(h)
|
376
|
+
@headers = h
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
#=RequestHistory
|
382
|
+
# Uses RequestBuilder and remind previous requests
|
383
|
+
#
|
384
|
+
#==Example:
|
385
|
+
#
|
386
|
+
# @executor = ::Restfulie::Client::HTTP::RequestHistoryExecutor.new("http://restfulie.com") #this class includes RequestHistory module.
|
387
|
+
# @executor.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #first request
|
388
|
+
# @executor.at('/blogs').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #second request
|
389
|
+
# @executor.request_history!(0) #doing first request again
|
390
|
+
#
|
391
|
+
module RequestHistory
|
392
|
+
include RequestBuilder
|
393
|
+
|
394
|
+
attr_accessor_with_default :max_to_remind, 10
|
395
|
+
|
396
|
+
def snapshots
|
397
|
+
@snapshots || @snapshots = []
|
398
|
+
end
|
399
|
+
|
400
|
+
def request!(method=nil, path=nil, *args)#:nodoc:
|
401
|
+
if method == nil || path == nil
|
402
|
+
raise 'History not selected' unless @snapshot
|
403
|
+
super( @snapshot[:method], @snapshot[:path], *@snapshot[:args] )
|
404
|
+
else
|
405
|
+
@snapshot = make_snapshot(method, path, *args)
|
406
|
+
unless snapshots.include?(@snapshot)
|
407
|
+
snapshots.shift if snapshots.size >= max_to_remind
|
408
|
+
snapshots << @snapshot
|
409
|
+
end
|
410
|
+
super
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def request(method=nil, path=nil, *args)#:nodoc:
|
415
|
+
request!(method, path, *args)
|
416
|
+
rescue Error::RESTError => se
|
417
|
+
se.response
|
418
|
+
end
|
419
|
+
|
420
|
+
def history(number)
|
421
|
+
@snapshot = snapshots[number]
|
422
|
+
raise "Undefined snapshot for #{number}" unless @snapshot
|
423
|
+
self.host = @snapshot[:host]
|
424
|
+
self.cookies = @snapshot[:cookies]
|
425
|
+
self.headers = @snapshot[:headers]
|
426
|
+
self.default_headers = @snapshot[:default_headers]
|
427
|
+
at(@snapshot[:path])
|
428
|
+
end
|
429
|
+
|
430
|
+
private
|
431
|
+
|
432
|
+
def make_snapshot(method, path, *args)
|
433
|
+
arguments = args.dup
|
434
|
+
cutom_headers = arguments.extract_options!
|
435
|
+
{ :host => self.host.dup,
|
436
|
+
:default_headers => self.default_headers.dup,
|
437
|
+
:headers => self.headers.dup,
|
438
|
+
:cookies => self.cookies,
|
439
|
+
:method => method,
|
440
|
+
:path => path,
|
441
|
+
:args => arguments << self.headers.merge(cutom_headers) }
|
442
|
+
end
|
443
|
+
|
444
|
+
end
|
445
|
+
|
446
|
+
#=This class includes RequestAdapter module.
|
447
|
+
class RequestExecutor
|
448
|
+
include RequestAdapter
|
449
|
+
|
450
|
+
# * <tt> host (e.g. 'http://restfulie.com') </tt>
|
451
|
+
# * <tt> default_headers (e.g. {'Cache-control' => 'no-cache'} ) </tt>
|
452
|
+
def initialize(host, default_headers = {})
|
453
|
+
self.host=host
|
454
|
+
self.default_headers=default_headers
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
458
|
+
|
459
|
+
#=This class includes RequestBuilder module.
|
460
|
+
class RequestBuilderExecutor
|
461
|
+
include RequestBuilder
|
462
|
+
|
463
|
+
# * <tt> host (e.g. 'http://restfulie.com') </tt>
|
464
|
+
# * <tt> default_headers (e.g. {'Cache-control' => 'no-cache'} ) </tt>
|
465
|
+
def initialize(host, default_headers = {})
|
466
|
+
self.host=host
|
467
|
+
self.default_headers=default_headers
|
468
|
+
end
|
469
|
+
def host=(host)
|
470
|
+
super
|
471
|
+
at(self.host.path)
|
472
|
+
end
|
473
|
+
def at(path)
|
474
|
+
@path = path
|
475
|
+
self
|
476
|
+
end
|
477
|
+
def path
|
478
|
+
@path
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
#=This class inherits RequestBuilderExecutor and include RequestHistory module.
|
483
|
+
class RequestHistoryExecutor < RequestBuilderExecutor
|
484
|
+
include RequestHistory
|
485
|
+
end
|
486
|
+
|
487
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Restfulie::Client::HTTP#:nodoc:
|
2
|
+
|
3
|
+
# Offer easy access to Atom link relationships, such as <tt>post.next</tt> for
|
4
|
+
# <tt><link rel="next" href="http://resource.entrypoint.com/post/12" type="application/atom+xml" /></tt> relationships.
|
5
|
+
module AtomLinkShortcut
|
6
|
+
def method_missing(method_sym,*args)#:nodoc:
|
7
|
+
selected_links = links.select{ |l| l.rel == method_sym.to_s }
|
8
|
+
super if selected_links.empty?
|
9
|
+
link = (selected_links.size == 1) ? selected_links.first : selected_links
|
10
|
+
|
11
|
+
return link unless link.instance_variable_defined?(:@type)
|
12
|
+
link.accepts(link.type)
|
13
|
+
|
14
|
+
representation = Restfulie::Client::HTTP::RequestMarshaller.content_type_for(link.type)
|
15
|
+
return representation.prepare_link_for(link) if representation
|
16
|
+
link
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Offers a way to access Atom entries element's in namespaced extensions.
|
22
|
+
module AtomElementShortcut
|
23
|
+
def method_missing(method_sym,*args)
|
24
|
+
return super(method_sym, *args) unless simple_extensions
|
25
|
+
|
26
|
+
found = find_extension_entry_for(method_sym)
|
27
|
+
return super(method_sym, *args) if found.empty?
|
28
|
+
result = found.collect do |pair|
|
29
|
+
pair.last.length==1 ? pair.last.first : pair.last
|
30
|
+
end
|
31
|
+
result.length==1 ? result.first : result
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to?(method_sym)
|
35
|
+
return super(method_sym) unless simple_extensions
|
36
|
+
|
37
|
+
found = find_extension_entry_for(method_sym)
|
38
|
+
(found.length!=0) || super(method_sym)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def find_extension_entry_for(method_sym)
|
43
|
+
start = -(method_sym.to_s.length + 1)
|
44
|
+
found = simple_extensions.select do |k, v|
|
45
|
+
method_sym.to_s == k[start..-2]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Offers a way to access Atom entries element's in namespaced extensions.
|
51
|
+
module AtomElementShortcut
|
52
|
+
def method_missing(method_sym,*args)
|
53
|
+
return super(method_sym, *args) unless simple_extensions
|
54
|
+
|
55
|
+
start = -(method_sym.to_s.length + 1)
|
56
|
+
found = simple_extensions.select do |k, v|
|
57
|
+
method_sym.to_s == k[start..-2]
|
58
|
+
end
|
59
|
+
return super(method_sym, *args) if found.empty?
|
60
|
+
result = found.collect do |pair|
|
61
|
+
pair.last.length==1 ? pair.last.first : pair.last
|
62
|
+
end
|
63
|
+
result.length==1 ? result.first : result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Gives to Atom::Link capabilities to fetch related resources.
|
68
|
+
module LinkRequestBuilder
|
69
|
+
include RequestMarshaller
|
70
|
+
def path#:nodoc:
|
71
|
+
at(href)
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# inject new behavior in rAtom instances to enable easily access to link relationships.
|
77
|
+
::Atom::Feed.instance_eval {
|
78
|
+
include AtomLinkShortcut
|
79
|
+
include AtomElementShortcut
|
80
|
+
}
|
81
|
+
::Atom::Entry.instance_eval {
|
82
|
+
include AtomLinkShortcut
|
83
|
+
include AtomElementShortcut
|
84
|
+
}
|
85
|
+
::Atom::Link.instance_eval { include LinkRequestBuilder }
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Restfulie::Client::HTTP
|
2
|
+
|
3
|
+
module Cache
|
4
|
+
|
5
|
+
def store
|
6
|
+
@store || @store = ::ActiveSupport::Cache::MemoryStore.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def get
|
10
|
+
store.fetch(@uri) do
|
11
|
+
request(:get, @uri, @headers)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def head
|
16
|
+
store.fetch(@uri) do
|
17
|
+
request(:head, @uri, @headers)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class RequestBuilderExecutorWithCache < RequestBuilderExecutor
|
24
|
+
include Cache
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Restfulie::Client::HTTP#:nodoc:
|
2
|
+
|
3
|
+
#Client errors
|
4
|
+
module Error
|
5
|
+
|
6
|
+
#Generic error class and superclass of all other errors raised by client restfulie
|
7
|
+
class BaseError < StandardError; end
|
8
|
+
|
9
|
+
class TranslationError < BaseError; end
|
10
|
+
|
11
|
+
# Standard error thrown on major client exceptions
|
12
|
+
class RESTError < StandardError
|
13
|
+
|
14
|
+
attr_reader :response
|
15
|
+
attr_reader :request
|
16
|
+
|
17
|
+
def initialize(request, response)
|
18
|
+
@request = request
|
19
|
+
@response = response
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"HTTP error #{@response.code} when invoking #{@request.host}#{::URI.decode(@response.path)} via #{@response.method}. " +
|
24
|
+
((@response.body.blank?) ? "No additional data was sent." : "The complete response was:\n" + @response.body)
|
25
|
+
rescue
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
#Represents the HTTP code 503
|
32
|
+
class ServerNotAvailableError < RESTError
|
33
|
+
def initialize(request, response, exception)
|
34
|
+
super(request, response)
|
35
|
+
set_backtrace(exception.backtrace)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class UnknownError < RESTError; end
|
40
|
+
|
41
|
+
#Represents the HTTP code 300 range
|
42
|
+
class Redirection < RESTError; end
|
43
|
+
|
44
|
+
class ClientError < RESTError; end
|
45
|
+
|
46
|
+
#Represents the HTTP code 400
|
47
|
+
class BadRequest < ClientError; end
|
48
|
+
|
49
|
+
#Represents the HTTP code 401
|
50
|
+
class Unauthorized < ClientError; end
|
51
|
+
|
52
|
+
#Represents the HTTP code 403
|
53
|
+
class Forbidden < ClientError; end
|
54
|
+
|
55
|
+
#Represents the HTTP code 404
|
56
|
+
class NotFound < ClientError; end
|
57
|
+
|
58
|
+
#Represents the HTTP code 405
|
59
|
+
class MethodNotAllowed < ClientError; end
|
60
|
+
|
61
|
+
#Represents the HTTP code 412
|
62
|
+
class PreconditionFailed < ClientError; end
|
63
|
+
|
64
|
+
#Represents the HTTP code 407
|
65
|
+
class ProxyAuthenticationRequired < ClientError; end
|
66
|
+
|
67
|
+
#Represents the HTTP code 409
|
68
|
+
class Conflict < ClientError; end
|
69
|
+
|
70
|
+
#Represents the HTTP code 410
|
71
|
+
class Gone < ClientError; end
|
72
|
+
|
73
|
+
#Represents the HTTP code 500
|
74
|
+
class ServerError < RESTError; end
|
75
|
+
|
76
|
+
#Represents the HTTP code 501
|
77
|
+
class NotImplemented < ServerError; end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|