restfulie 0.6.0 → 0.7.0
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.
- 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
|
+
|