restfulie 0.8.1 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/README.textile +35 -13
- data/Rakefile +51 -19
- data/lib/restfulie.rb +2 -1
- data/lib/restfulie/client.rb +7 -0
- data/lib/restfulie/client/cache.rb +11 -0
- data/lib/restfulie/client/cache/basic.rb +75 -0
- data/lib/restfulie/client/cache/fake.rb +15 -0
- data/lib/restfulie/client/cache/http_ext.rb +121 -0
- data/lib/restfulie/client/cache/restrictions.rb +18 -0
- data/lib/restfulie/client/http/link_request_builder.rb +1 -0
- data/lib/restfulie/client/http/request_adapter.rb +7 -3
- data/lib/restfulie/client/http/request_builder.rb +12 -5
- data/lib/restfulie/client/http/request_history.rb +5 -3
- data/lib/restfulie/client/http/request_marshaller.rb +3 -1
- data/lib/restfulie/common/representation.rb +1 -1
- data/lib/restfulie/common/representation/atom/base.rb +10 -1
- data/lib/restfulie/common/representation/links.rb +11 -0
- data/lib/restfulie/server.rb +6 -0
- data/lib/restfulie/server/action_controller.rb +2 -0
- data/lib/restfulie/server/action_controller/base.rb +1 -7
- data/lib/restfulie/server/action_controller/cacheable_responder.rb +71 -0
- data/lib/restfulie/server/action_controller/created_responder.rb +19 -0
- data/lib/restfulie/server/action_controller/restful_responder.rb +4 -39
- data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +2 -1
- data/lib/restfulie/server/core_ext/array.rb +7 -2
- data/lib/restfulie/version.rb +1 -1
- metadata +27 -5
- data/lib/restfulie/common/representation/xml.rb +0 -26
data/Gemfile
CHANGED
data/README.textile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
h1. Web site
|
2
2
|
|
3
|
-
Restfulie's website can be found at "http://restfulie.
|
4
|
-
|
3
|
+
Restfulie's website can be found at "http://restfulie.caelumobjects.com":http://restfulie.caelumobjects.com
|
4
|
+
From there you can see some videos and read the "Restfulie from Scratch guide":http://restfulie.caelumobjects.com/caelumobjects-restful-rails.pdf.
|
5
5
|
|
6
6
|
h1. Quit pretending
|
7
7
|
|
@@ -17,15 +17,6 @@ h2. Why would I use restfulie?
|
|
17
17
|
# HATEOAS --> consumed resources will not affect your software whenever they change their flow
|
18
18
|
# Adaptability --> clients are able to adapt to your changes
|
19
19
|
|
20
|
-
h2. Documentation
|
21
|
-
|
22
|
-
Appart from the simple server and client examples provided here, you can find the following links useful:
|
23
|
-
|
24
|
-
* "RDocs":http://rdoc.info/projects/caelum/restfulie
|
25
|
-
* "Official website":http://restfulie.caelumobjects.com
|
26
|
-
* "How-tos":http://restfulie.caelumobjects.com/rails
|
27
|
-
* "Buying through Rest: Rest to the enterprise (video)":guilhermesilveira.wordpress.com/2010/04/13/buying-through-rest-applying-rest-to-the-enterprise/
|
28
|
-
|
29
20
|
h2. Simple server example
|
30
21
|
|
31
22
|
In the server side, all you need to do is notify inherited_resources which media types you are able to represent your resource:
|
@@ -75,11 +66,20 @@ puts receipt.id
|
|
75
66
|
|
76
67
|
In order to create a full REST client, "watch this video":http://guilhermesilveira.wordpress.com.
|
77
68
|
|
78
|
-
h2.
|
69
|
+
h2. Full examples
|
79
70
|
|
80
71
|
You can view an entire application running Restfulie under *spec/integration/order/server* and *spec/integration/order/client* in this git repository.
|
81
72
|
|
82
|
-
"You can also download a full example of a REST based agent and server":http://github.com/caelum/
|
73
|
+
"You can also download a full example of a REST based agent and server":http://github.com/caelum/full-restfulie-examples using Restfulie, according to the Rest Architecture Maturity Model.
|
74
|
+
|
75
|
+
h2. Documentation
|
76
|
+
|
77
|
+
Appart from the simple server and client examples provided here, you can find the following links useful:
|
78
|
+
|
79
|
+
* "RDocs":http://rdoc.info/projects/caelum/restfulie
|
80
|
+
* "Official website":http://restfulie.caelumobjects.com
|
81
|
+
* "How-tos":http://restfulie.caelumobjects.com/caelumobjects-restful-rails.pdf
|
82
|
+
* "Buying through Rest: Rest to the enterprise (video)":http://guilhermesilveira.wordpress.com/2010/04/13/buying-through-rest-applying-rest-to-the-enterprise/
|
83
83
|
|
84
84
|
h2. Installing
|
85
85
|
|
@@ -114,3 +114,25 @@ try {
|
|
114
114
|
var pageTracker = _gat._getTracker("UA-11770776-1");
|
115
115
|
pageTracker._trackPageview();
|
116
116
|
} catch(err) {}</script>
|
117
|
+
|
118
|
+
h2. Contributions
|
119
|
+
|
120
|
+
Restfulie was created and is maintained by "http://caelumobjects.com":Caelum, and has received enormous contributions from all those developers:
|
121
|
+
|
122
|
+
Project Leader
|
123
|
+
Guilherme Silveira, "http://caelum.com.br":Caelum
|
124
|
+
|
125
|
+
Caue Guerra, caelum
|
126
|
+
George Guimaraes, abril and plataforma
|
127
|
+
Fabio Akita
|
128
|
+
Diego Carrion
|
129
|
+
Leandro Silva
|
130
|
+
Gavin-John Noonan
|
131
|
+
Antonio Marques
|
132
|
+
Luis Cipriani, abril
|
133
|
+
Everton Ribeiro, abril
|
134
|
+
Paulo Ahagon, abril
|
135
|
+
Elomar França
|
136
|
+
Thomas Stefano
|
137
|
+
"http://www.caelum.com.br":"David Paniz"
|
138
|
+
"http://www.caikesouza.com/blog":Caike Souza
|
data/Rakefile
CHANGED
@@ -15,7 +15,6 @@ AUTHOR = "Guilherme Silveira, Caue Guerra, Luis Cipriani, Everton Ribeiro, Geo
|
|
15
15
|
EMAIL = "guilherme.silveira@caelum.com.br"
|
16
16
|
HOMEPAGE = "http://restfulie.caelumobjects.com"
|
17
17
|
|
18
|
-
|
19
18
|
spec = Gem::Specification.new do |s|
|
20
19
|
s.name = GEM
|
21
20
|
s.version = GEM_VERSION
|
@@ -34,22 +33,40 @@ spec = Gem::Specification.new do |s|
|
|
34
33
|
s.homepage = HOMEPAGE
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
pid = process "fake_server"
|
48
|
-
puts "fake_server pid >>>> #{pid}"
|
36
|
+
# optionally loads a task if the required gems exist
|
37
|
+
def optionally
|
38
|
+
begin
|
39
|
+
yield
|
40
|
+
rescue LoadError; end
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_server_and_invoke_test(task_name)
|
44
|
+
IO.popen("ruby ./spec/units/client/fake_server.rb") do |pipe|
|
45
|
+
wait_server(4567)
|
49
46
|
Rake::Task[task_name].invoke
|
50
|
-
|
47
|
+
Process.kill 'INT', pipe.pid
|
51
48
|
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def wait_server(port=3000)
|
52
|
+
(1..15).each do
|
53
|
+
begin
|
54
|
+
Net::HTTP.get(URI.parse("http://localhost:#{port}/"))
|
55
|
+
return
|
56
|
+
rescue
|
57
|
+
sleep 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
raise "Waited for the server but it did not finish"
|
61
|
+
end
|
52
62
|
|
63
|
+
desc 'Start server'
|
64
|
+
task :server do
|
65
|
+
process 'fake_server'
|
66
|
+
end
|
67
|
+
|
68
|
+
namespace :test do
|
69
|
+
|
53
70
|
desc "Execute integration Order tests"
|
54
71
|
task :integration do
|
55
72
|
integration_path = "spec/integration/order/server"
|
@@ -99,6 +116,7 @@ namespace :test do
|
|
99
116
|
start_server_and_invoke_test('test:spec:all')
|
100
117
|
puts "Execution integration tests... (task test:integration)"
|
101
118
|
Rake::Task["test:integration"].invoke()
|
119
|
+
Rake::Task["test:examples"].invoke()
|
102
120
|
end
|
103
121
|
task :common do
|
104
122
|
start_server_and_invoke_test('test:spec:common')
|
@@ -113,6 +131,22 @@ namespace :test do
|
|
113
131
|
start_server_and_invoke_test('test:rcov:rcov')
|
114
132
|
end
|
115
133
|
end
|
134
|
+
|
135
|
+
desc "runs all example tests"
|
136
|
+
task :examples do
|
137
|
+
Rake::Task["install"].invoke()
|
138
|
+
|
139
|
+
target_dir = "full-examples/rest_from_scratch/part_3"
|
140
|
+
system "cd #{target_dir} && rake db:reset db:seed"
|
141
|
+
|
142
|
+
IO.popen("ruby #{target_dir}/script/server") do |pipe|
|
143
|
+
wait_server
|
144
|
+
system "cd #{target_dir} && rake spec"
|
145
|
+
Process.kill 'INT', pipe.pid
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
116
150
|
end
|
117
151
|
|
118
152
|
Rake::GemPackageTask.new(spec) do |pkg|
|
@@ -121,16 +155,14 @@ end
|
|
121
155
|
|
122
156
|
Rake::RDocTask.new("rdoc") do |rdoc|
|
123
157
|
rdoc.options << '--line-numbers' << '--inline-source'
|
124
|
-
# rdoc.rdoc_files.include('lib/**/**/*.rb')
|
125
158
|
end
|
126
159
|
|
127
|
-
|
160
|
+
optionally do
|
128
161
|
require 'yard'
|
129
162
|
YARD::Rake::YardocTask.new do |t|
|
130
|
-
t.files = ['lib/restfulie/**/*.rb', 'README.textile']
|
131
|
-
# t.options = ['--any', '--extra', '--opts'] # optional
|
163
|
+
t.files = ['lib/restfulie/**/*.rb', 'README.textile']
|
132
164
|
end
|
133
|
-
|
165
|
+
end
|
134
166
|
|
135
167
|
desc "Install the gem locally"
|
136
168
|
task :install => [:package] do
|
data/lib/restfulie.rb
CHANGED
@@ -7,7 +7,8 @@ require 'restfulie/server'
|
|
7
7
|
# Shortcut to Restfulie::Client::EntryPoint
|
8
8
|
module Restfulie
|
9
9
|
extend Restfulie::Client::EntryPoint
|
10
|
-
|
10
|
+
|
11
|
+
# creates a new entry point for executing requests
|
11
12
|
def self.at(uri)
|
12
13
|
Object.new.send(:extend, Restfulie::Client::EntryPoint).at(uri)
|
13
14
|
end
|
data/lib/restfulie/client.rb
CHANGED
@@ -7,6 +7,13 @@ module Restfulie
|
|
7
7
|
autoload :EntryPoint, 'restfulie/client/entry_point'
|
8
8
|
autoload :Base, 'restfulie/client/base'
|
9
9
|
autoload :Mikyung, 'restfulie/client/mikyung'
|
10
|
+
autoload :Cache, 'restfulie/client/cache'
|
11
|
+
|
12
|
+
mattr_accessor :cache_provider, :cache_store
|
13
|
+
|
14
|
+
Restfulie::Client.cache_store = ActiveSupport::Cache::MemoryStore.new
|
15
|
+
Restfulie::Client.cache_provider = Restfulie::Client::Cache::Basic.new
|
16
|
+
|
10
17
|
end
|
11
18
|
end
|
12
19
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Restfulie
|
2
|
+
module Client
|
3
|
+
module Cache
|
4
|
+
autoload :Basic, 'restfulie/client/cache/basic'
|
5
|
+
autoload :Fake, 'restfulie/client/cache/fake'
|
6
|
+
autoload :Restrictions, 'restfulie/client/cache/restrictions'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
require 'restfulie/client/cache/http_ext'
|
11
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Basic cache implementation for restfulie.
|
2
|
+
#
|
3
|
+
# It uses the request headers and uri to store it in memory.
|
4
|
+
# This cache might not be optimal for long running clients, which should use a memcached based one.
|
5
|
+
# Use Restfulie::Client.cache_provider to change the provider
|
6
|
+
module Restfulie::Client::Cache
|
7
|
+
class Basic
|
8
|
+
|
9
|
+
def put(key, req, response)
|
10
|
+
if Restfulie::Client::Cache::Restrictions.may_cache?(response)
|
11
|
+
Restfulie::Common::Logger.logger.debug "caching #{key} #{response}"
|
12
|
+
cache_add(key, req, response)
|
13
|
+
end
|
14
|
+
response
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(key, request, verb)
|
18
|
+
|
19
|
+
# debugger
|
20
|
+
response = cache_get(key, request, verb)
|
21
|
+
return nil if response.nil?
|
22
|
+
|
23
|
+
if response.has_expired_cache?
|
24
|
+
remove(key)
|
25
|
+
else
|
26
|
+
Restfulie::Common::Logger.logger.debug "RETURNING cache #{key}"
|
27
|
+
cache_hit response
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
# removes all elements from the cache
|
33
|
+
def clear
|
34
|
+
cache.clear
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# allows response enhancement when the cache was hit with it
|
40
|
+
def cache_hit(response)
|
41
|
+
response
|
42
|
+
end
|
43
|
+
|
44
|
+
def cache_add(key, req, response)
|
45
|
+
values = (cache.read(key) || []).dup
|
46
|
+
values << [req, response]
|
47
|
+
cache.write(key, values)
|
48
|
+
end
|
49
|
+
|
50
|
+
def cache_get(key, req, verb)
|
51
|
+
return nil unless cache.exist?(key)
|
52
|
+
found = cache.read(key).find do |cached|
|
53
|
+
old_req = cached.first
|
54
|
+
old_response = cached.last
|
55
|
+
if old_response.vary_headers_for(old_req) == old_response.vary_headers_for(req) &&
|
56
|
+
old_response.method == verb
|
57
|
+
old_response
|
58
|
+
else
|
59
|
+
false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
found ? found.last : nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove(key)
|
66
|
+
cache.delete(key)
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def cache
|
71
|
+
Restfulie::Client.cache_store
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Fake cache that does not cache anything
|
2
|
+
# Use Restfulie::Client.cache_provider = Restfulie::Client::Cache::Fake.new
|
3
|
+
module Restfulie::Client::Cache
|
4
|
+
class Fake
|
5
|
+
def put(url, req, response)
|
6
|
+
response
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(url, req)
|
10
|
+
end
|
11
|
+
|
12
|
+
def clear
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
# an extesion to http responses
|
4
|
+
module Restfulie::Client::HTTP::ResponseStatus
|
5
|
+
|
6
|
+
attr_accessor :previous
|
7
|
+
|
8
|
+
# determines if this response code was successful (according to http specs: 200~299)
|
9
|
+
def is_successful?
|
10
|
+
code.to_i >= 200 && code.to_i <= 299
|
11
|
+
end
|
12
|
+
|
13
|
+
# determines if this response code was successful (according to http specs: 100~199)
|
14
|
+
def is_informational?
|
15
|
+
code.to_i >= 100 && code.to_i <= 199
|
16
|
+
end
|
17
|
+
|
18
|
+
# determines if this response code was successful (according to http specs: 300~399)
|
19
|
+
def is_redirection?
|
20
|
+
code.to_i >= 300 && code.to_i <= 399
|
21
|
+
end
|
22
|
+
|
23
|
+
# determines if this response code was successful (according to http specs: 400~499)
|
24
|
+
def is_client_error?
|
25
|
+
code.to_i >= 400 && code.to_i <= 499
|
26
|
+
end
|
27
|
+
|
28
|
+
# determines if this response code was successful (according to http specs: 500~599)
|
29
|
+
def is_server_error?
|
30
|
+
code.to_i >= 500 && code.to_i <= 599
|
31
|
+
end
|
32
|
+
|
33
|
+
def etag
|
34
|
+
self['Etag']
|
35
|
+
end
|
36
|
+
|
37
|
+
def last_modified
|
38
|
+
self['Last-Modified']
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
module Restfulie::Client::HTTP::ResponseCacheCheck
|
44
|
+
|
45
|
+
def cache_max_age
|
46
|
+
val = header_value_from('cache-control', /^\s*max-age=(\d+)/)
|
47
|
+
if val
|
48
|
+
val.to_i
|
49
|
+
else
|
50
|
+
0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def header_value_from(header, expression)
|
55
|
+
h = value_for(headers[header], expression)
|
56
|
+
return nil if h.nil?
|
57
|
+
h.match(expression)[1]
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_expired_cache?
|
61
|
+
return true if headers['date'].nil?
|
62
|
+
max_time = Time.rfc2822(headers['date']) + cache_max_age.seconds
|
63
|
+
Time.now > max_time
|
64
|
+
end
|
65
|
+
|
66
|
+
# checks if the header's max-age is available and no no-store if available.
|
67
|
+
def may_cache?
|
68
|
+
may_cache_field?(headers['cache-control'])
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns whether this cache control field allows caching
|
72
|
+
#
|
73
|
+
# may_cache_field(['max-age=2000', 'no-store']) == false
|
74
|
+
# may_cache_field('max-age=2000,no-store') == false
|
75
|
+
# may_cache_field('max-age=2000') == true
|
76
|
+
def may_cache_field?(field)
|
77
|
+
return false if field.nil?
|
78
|
+
|
79
|
+
if field.kind_of? Array
|
80
|
+
field.each do |f|
|
81
|
+
return false if !may_cache_field?(f)
|
82
|
+
end
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
|
86
|
+
max_age_header = value_for(field, /^max-age=(\d+)/)
|
87
|
+
return false if max_age_header.nil?
|
88
|
+
|
89
|
+
if value_for(field, /^no-store/)
|
90
|
+
false
|
91
|
+
else
|
92
|
+
true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# extracts the header value for an specific expression, which can be located at the start or in the middle
|
97
|
+
# of the expression
|
98
|
+
def value_for(value, expression)
|
99
|
+
value.split(",").find { |obj| obj.strip =~ expression }
|
100
|
+
end
|
101
|
+
|
102
|
+
# extracts all header values related to the Vary header from this response, in order
|
103
|
+
# to implement Vary support from the HTTP Specification
|
104
|
+
#
|
105
|
+
# example
|
106
|
+
# if the response Vary header is 'Accept','Accept-Language', we have
|
107
|
+
# vary_headers_for({'Accept'=>'application/xml', 'Date' =>'...', 'Accept-Language'=>'de'}) == ['application/xml', 'de']
|
108
|
+
# vary_headers_for({'Date' => '...', 'Accept-Language'=>'de'}) == [nil, 'de']
|
109
|
+
def vary_headers_for(request)
|
110
|
+
return nil if headers['vary'].nil?
|
111
|
+
headers['vary'].split(',').map do |key|
|
112
|
+
request[key.strip]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
class Restfulie::Client::HTTP::Response
|
119
|
+
include Restfulie::Client::HTTP::ResponseStatus
|
120
|
+
include Restfulie::Client::HTTP::ResponseCacheCheck
|
121
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Restfulie::Client::Cache
|
2
|
+
module Restrictions
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# checks whether this request verb and its cache headers allow caching
|
6
|
+
def may_cache?(response)
|
7
|
+
may_cache_method?(response.method) && response.may_cache?
|
8
|
+
end
|
9
|
+
|
10
|
+
# only Post and Get requests are cacheable so far
|
11
|
+
def may_cache_method?(verb)
|
12
|
+
verb == :get || verb == :post
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -125,7 +125,7 @@ module Restfulie
|
|
125
125
|
def request(method, path, *args)
|
126
126
|
request!(method, path, *args)
|
127
127
|
rescue Error::RESTError => se
|
128
|
-
se.response
|
128
|
+
[[@host, path], nil, se.response]
|
129
129
|
end
|
130
130
|
|
131
131
|
# Executes a request against your server and return a response instance.
|
@@ -142,14 +142,18 @@ module Restfulie
|
|
142
142
|
|
143
143
|
::Restfulie::Common::Logger.logger.info(request_to_s(method, path, *args)) if ::Restfulie::Common::Logger.logger
|
144
144
|
begin
|
145
|
-
|
145
|
+
http_request = get_connection_provider
|
146
|
+
response = Restfulie::Client.cache_provider.get([@host, path], http_request, method)
|
147
|
+
return [[@host, path], http_request, response] if response
|
148
|
+
response = ResponseHandler.handle(method, path, http_request.send(method, path, *args))
|
146
149
|
rescue Exception => e
|
150
|
+
Restfulie::Common::Logger.logger.error(e)
|
147
151
|
raise Error::ServerNotAvailableError.new(self, Response.new(method, path, 503, nil, {}), e )
|
148
152
|
end
|
149
153
|
|
150
154
|
case response.code
|
151
155
|
when 100..299
|
152
|
-
response
|
156
|
+
[[@host, path], http_request, response]
|
153
157
|
when 300..399
|
154
158
|
raise Error::Redirection.new(self, response)
|
155
159
|
when 400
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
1
3
|
module Restfulie
|
2
4
|
module Client
|
3
5
|
module HTTP #:nodoc:
|
@@ -48,8 +50,8 @@ module Restfulie
|
|
48
50
|
host.path
|
49
51
|
end
|
50
52
|
|
51
|
-
def get
|
52
|
-
request(:get, path, headers)
|
53
|
+
def get(params = {})
|
54
|
+
request(:get, add_querystring(path, params), headers)
|
53
55
|
end
|
54
56
|
|
55
57
|
def head
|
@@ -71,9 +73,9 @@ module Restfulie
|
|
71
73
|
def delete
|
72
74
|
request(:delete, path, headers)
|
73
75
|
end
|
74
|
-
|
75
|
-
def get!
|
76
|
-
request!(:get, path, headers)
|
76
|
+
|
77
|
+
def get!(params = {})
|
78
|
+
request!(:get, add_querystring(path, params), headers)
|
77
79
|
end
|
78
80
|
|
79
81
|
def head!
|
@@ -97,6 +99,11 @@ module Restfulie
|
|
97
99
|
end
|
98
100
|
|
99
101
|
protected
|
102
|
+
|
103
|
+
def add_querystring(path, params)
|
104
|
+
params = params.map { |param, value| "#{param}=#{value}"}.join("&")
|
105
|
+
params.blank? ? path : URI.escape("#{path}?#{params}")
|
106
|
+
end
|
100
107
|
|
101
108
|
def headers=(h)
|
102
109
|
@headers = h
|
@@ -14,12 +14,14 @@ module Restfulie
|
|
14
14
|
module RequestHistory
|
15
15
|
include RequestBuilder
|
16
16
|
|
17
|
-
attr_accessor_with_default :max_to_remind, 10
|
18
|
-
|
19
17
|
def snapshots
|
20
18
|
@snapshots ||= []
|
21
19
|
end
|
22
20
|
|
21
|
+
def max_to_remind
|
22
|
+
10
|
23
|
+
end
|
24
|
+
|
23
25
|
def request!(method=nil, path=nil, *args)#:nodoc:
|
24
26
|
if method == nil || path == nil
|
25
27
|
raise 'History not selected' unless @snapshot
|
@@ -37,7 +39,7 @@ module Restfulie
|
|
37
39
|
def request(method=nil, path=nil, *args)#:nodoc:
|
38
40
|
request!(method, path, *args)
|
39
41
|
rescue Error::RESTError => se
|
40
|
-
se.response
|
42
|
+
[[@host, path], nil, se.response]
|
41
43
|
end
|
42
44
|
|
43
45
|
def history(number)
|
@@ -60,7 +60,9 @@ module Restfulie
|
|
60
60
|
args = add_representation_headers(method, path, unmarshaller, *args)
|
61
61
|
end
|
62
62
|
|
63
|
-
response = super(method, path, *args)
|
63
|
+
key, req, response = super(method, path, *args)
|
64
|
+
Restfulie::Client.cache_provider.put(key, req, response)
|
65
|
+
|
64
66
|
parse_response(response)
|
65
67
|
end
|
66
68
|
|
@@ -4,7 +4,7 @@ module Restfulie
|
|
4
4
|
autoload :Atom, 'restfulie/common/representation/atom'
|
5
5
|
autoload :Generic, 'restfulie/common/representation/generic'
|
6
6
|
autoload :Json, 'restfulie/common/representation/json'
|
7
|
-
autoload :
|
7
|
+
autoload :Links, 'restfulie/common/representation/links'
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -63,7 +63,16 @@ module Restfulie
|
|
63
63
|
def updated=(value)
|
64
64
|
set_text("updated", value)
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
|
+
def published
|
68
|
+
value = text("published")
|
69
|
+
Time.parse(value) unless value.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def published=(value)
|
73
|
+
set_text("published", value)
|
74
|
+
end
|
75
|
+
|
67
76
|
# text
|
68
77
|
def rights
|
69
78
|
text("rights")
|
data/lib/restfulie/server.rb
CHANGED
@@ -3,6 +3,8 @@ module Restfulie
|
|
3
3
|
module ActionController #:nodoc:
|
4
4
|
if defined?(::ActionController) || defined?(::ApplicationController)
|
5
5
|
autoload :ParamsParser, 'restfulie/server/action_controller/params_parser'
|
6
|
+
autoload :CacheableResponder, 'restfulie/server/action_controller/cacheable_responder'
|
7
|
+
autoload :CreatedResponder, 'restfulie/server/action_controller/created_responder'
|
6
8
|
autoload :RestfulResponder, 'restfulie/server/action_controller/restful_responder'
|
7
9
|
autoload :Base, 'restfulie/server/action_controller/base'
|
8
10
|
end
|
@@ -25,7 +25,7 @@ module Restfulie
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def include_restfulie?
|
28
|
-
defined?(Restfulie::Server::ActionController::Base) &&
|
28
|
+
defined?(Restfulie::Server::ActionController::Base) && self.include?(Restfulie::Server::ActionController::Base)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -45,9 +45,3 @@ module Restfulie
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
49
|
-
class ActionController::Base
|
50
|
-
def self.acts_as_restfulie
|
51
|
-
include Restfulie::Server::ActionController::Base
|
52
|
-
end
|
53
|
-
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Expires
|
2
|
+
def do_http_cache(responder)
|
3
|
+
if responder.options[:expires_in]
|
4
|
+
responder.controller.send :expires_in,responder.options[:expires_in]
|
5
|
+
true
|
6
|
+
else
|
7
|
+
false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class LastModifieds
|
13
|
+
# default implementation that will check whether caching can be applied
|
14
|
+
def do_http_cache?(responder)
|
15
|
+
responder.resources.flatten.select do |resource|
|
16
|
+
resource.respond_to?(:updated_at)
|
17
|
+
end &&
|
18
|
+
responder.controller.response.last_modified.nil? &&
|
19
|
+
!new_record?(responder)
|
20
|
+
end
|
21
|
+
|
22
|
+
def do_http_cache(responder)
|
23
|
+
return false unless do_http_cache?(responder)
|
24
|
+
|
25
|
+
timestamp = responder.resources.flatten.select do |resource|
|
26
|
+
resource.respond_to?(:updated_at)
|
27
|
+
end.map do |resource|
|
28
|
+
(resource.updated_at || Time.now).utc
|
29
|
+
end.max
|
30
|
+
|
31
|
+
responder.controller.response.last_modified = timestamp if timestamp
|
32
|
+
end
|
33
|
+
|
34
|
+
def new_record?(responder)
|
35
|
+
responder.resource.respond_to?(:new_record?) && responder.resource.new_record?
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
module Restfulie
|
41
|
+
module Server
|
42
|
+
module ActionController
|
43
|
+
module CacheableResponder
|
44
|
+
|
45
|
+
CACHES = [::Expires.new, ::LastModifieds.new]
|
46
|
+
|
47
|
+
def to_format
|
48
|
+
cached = CACHES.inject(false) do |cached, cache|
|
49
|
+
cached || cache.do_http_cache(self)
|
50
|
+
end
|
51
|
+
if ::ActionController::Base.perform_caching && !cached.nil?
|
52
|
+
set_public_cache_control!
|
53
|
+
head :not_modified if fresh = request.fresh?(controller.response)
|
54
|
+
fresh
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_public_cache_control!
|
61
|
+
cache_control = controller.response.headers["Cache-Control"].split(",").map {|k| k.strip }
|
62
|
+
cache_control.delete("private")
|
63
|
+
cache_control.delete("no-cache")
|
64
|
+
cache_control << "public"
|
65
|
+
controller.response.headers["Cache-Control"] = cache_control.join(', ')
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Restfulie
|
2
|
+
module Server
|
3
|
+
module ActionController
|
4
|
+
|
5
|
+
# Adds support to answering as a 201 when the resource has been just created
|
6
|
+
module CreatedResponder
|
7
|
+
|
8
|
+
def to_format
|
9
|
+
if [201, :created].include? options[:status]
|
10
|
+
head :status => 201, :location => controller.url_for(resource)
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,45 +2,10 @@ module Restfulie
|
|
2
2
|
module Server
|
3
3
|
module ActionController
|
4
4
|
class RestfulResponder < ::ActionController::Responder
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
protected
|
11
|
-
def do_http_cache!
|
12
|
-
timestamp = resources.flatten.select do |resource|
|
13
|
-
resource.respond_to?(:updated_at)
|
14
|
-
end.map do |resource|
|
15
|
-
(resource.updated_at || Time.now).utc
|
16
|
-
end.max
|
17
|
-
|
18
|
-
controller.response.last_modified = timestamp if timestamp
|
19
|
-
set_public_cache_control!
|
20
|
-
|
21
|
-
head :not_modified if fresh = request.fresh?(controller.response)
|
22
|
-
fresh
|
23
|
-
end
|
24
|
-
|
25
|
-
def do_http_cache?
|
26
|
-
get? &&
|
27
|
-
@http_cache != false &&
|
28
|
-
::ActionController::Base.perform_caching &&
|
29
|
-
!new_record? &&
|
30
|
-
controller.response.last_modified.nil?
|
31
|
-
end
|
32
|
-
|
33
|
-
def new_record?
|
34
|
-
resource.respond_to?(:new_record?) && resource.new_record?
|
35
|
-
end
|
36
|
-
|
37
|
-
def set_public_cache_control!
|
38
|
-
cache_control = controller.response.headers["Cache-Control"].split(",").map {|k| k.strip }
|
39
|
-
cache_control.delete("private")
|
40
|
-
cache_control.delete("no-cache")
|
41
|
-
cache_control << "public"
|
42
|
-
controller.response.headers["Cache-Control"] = cache_control.join(', ')
|
43
|
-
end
|
5
|
+
|
6
|
+
include CacheableResponder
|
7
|
+
include CreatedResponder
|
8
|
+
|
44
9
|
end
|
45
10
|
end
|
46
11
|
end
|
@@ -11,10 +11,11 @@ module Restfulie
|
|
11
11
|
"extend Restfulie::Server::ActionView::Helpers; " +
|
12
12
|
"code_block = lambda { #{template.source} };" +
|
13
13
|
"builder = code_block.call; " +
|
14
|
+
"self.response.headers['Link'] = Restfulie::Common::Representation::Links.extract_link_header(builder.links) if builder.respond_to?(:links); " +
|
14
15
|
"builder.to_s"
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
20
|
-
end
|
21
|
+
end
|
@@ -12,8 +12,9 @@ class Array
|
|
12
12
|
feed = Restfulie::Common::Representation::Atom::Feed.new
|
13
13
|
# TODO: Define better feed attributes
|
14
14
|
# Array#to_s can return a very long string
|
15
|
-
feed.title
|
16
|
-
feed.updated
|
15
|
+
feed.title = "Collection of #{map {|i| i.class.name }.uniq.to_sentence}"
|
16
|
+
feed.updated = updated_at
|
17
|
+
feed.published = published_at
|
17
18
|
# TODO: this id does not comply with Rest standards yet
|
18
19
|
feed.id = hash
|
19
20
|
|
@@ -43,4 +44,8 @@ class Array
|
|
43
44
|
def updated_at(field = :updated_at)
|
44
45
|
map { |item| item.send(field) if item.respond_to?(field) }.compact.max || Time.now
|
45
46
|
end
|
47
|
+
|
48
|
+
def published_at(field = :published_at)
|
49
|
+
map { |item| item.send(field) if item.respond_to?(field) }.compact.min || Time.now
|
50
|
+
end
|
46
51
|
end
|
data/lib/restfulie/version.rb
CHANGED
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restfulie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 57
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
+
- 9
|
8
9
|
- 1
|
9
|
-
version: 0.
|
10
|
+
version: 0.9.1
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Guilherme Silveira, Caue Guerra, Luis Cipriani, Everton Ribeiro, George Guimaraes, Paulo Ahagon
|
@@ -14,16 +15,18 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-08-22 00:00:00 -03:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: nokogiri
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
27
30
|
segments:
|
28
31
|
- 1
|
29
32
|
- 4
|
@@ -35,9 +38,11 @@ dependencies:
|
|
35
38
|
name: actionpack
|
36
39
|
prerelease: false
|
37
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
38
42
|
requirements:
|
39
43
|
- - ">="
|
40
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
41
46
|
segments:
|
42
47
|
- 2
|
43
48
|
- 3
|
@@ -49,9 +54,11 @@ dependencies:
|
|
49
54
|
name: activesupport
|
50
55
|
prerelease: false
|
51
56
|
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
52
58
|
requirements:
|
53
59
|
- - ">="
|
54
60
|
- !ruby/object:Gem::Version
|
61
|
+
hash: 7
|
55
62
|
segments:
|
56
63
|
- 2
|
57
64
|
- 3
|
@@ -63,9 +70,11 @@ dependencies:
|
|
63
70
|
name: responders_backport
|
64
71
|
prerelease: false
|
65
72
|
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
66
74
|
requirements:
|
67
75
|
- - ~>
|
68
76
|
- !ruby/object:Gem::Version
|
77
|
+
hash: 27
|
69
78
|
segments:
|
70
79
|
- 0
|
71
80
|
- 1
|
@@ -77,9 +86,11 @@ dependencies:
|
|
77
86
|
name: json_pure
|
78
87
|
prerelease: false
|
79
88
|
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
80
90
|
requirements:
|
81
91
|
- - ">="
|
82
92
|
- !ruby/object:Gem::Version
|
93
|
+
hash: 23
|
83
94
|
segments:
|
84
95
|
- 1
|
85
96
|
- 2
|
@@ -97,6 +108,11 @@ extra_rdoc_files: []
|
|
97
108
|
|
98
109
|
files:
|
99
110
|
- lib/restfulie/client/base.rb
|
111
|
+
- lib/restfulie/client/cache/basic.rb
|
112
|
+
- lib/restfulie/client/cache/fake.rb
|
113
|
+
- lib/restfulie/client/cache/http_ext.rb
|
114
|
+
- lib/restfulie/client/cache/restrictions.rb
|
115
|
+
- lib/restfulie/client/cache.rb
|
100
116
|
- lib/restfulie/client/configuration.rb
|
101
117
|
- lib/restfulie/client/entry_point.rb
|
102
118
|
- lib/restfulie/client/ext/atom_ext.rb
|
@@ -168,10 +184,12 @@ files:
|
|
168
184
|
- lib/restfulie/common/representation/json/link.rb
|
169
185
|
- lib/restfulie/common/representation/json/link_collection.rb
|
170
186
|
- lib/restfulie/common/representation/json.rb
|
171
|
-
- lib/restfulie/common/representation/
|
187
|
+
- lib/restfulie/common/representation/links.rb
|
172
188
|
- lib/restfulie/common/representation.rb
|
173
189
|
- lib/restfulie/common.rb
|
174
190
|
- lib/restfulie/server/action_controller/base.rb
|
191
|
+
- lib/restfulie/server/action_controller/cacheable_responder.rb
|
192
|
+
- lib/restfulie/server/action_controller/created_responder.rb
|
175
193
|
- lib/restfulie/server/action_controller/params_parser.rb
|
176
194
|
- lib/restfulie/server/action_controller/patch.rb
|
177
195
|
- lib/restfulie/server/action_controller/restful_responder.rb
|
@@ -202,23 +220,27 @@ rdoc_options: []
|
|
202
220
|
require_paths:
|
203
221
|
- lib
|
204
222
|
required_ruby_version: !ruby/object:Gem::Requirement
|
223
|
+
none: false
|
205
224
|
requirements:
|
206
225
|
- - ">="
|
207
226
|
- !ruby/object:Gem::Version
|
227
|
+
hash: 3
|
208
228
|
segments:
|
209
229
|
- 0
|
210
230
|
version: "0"
|
211
231
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
232
|
+
none: false
|
212
233
|
requirements:
|
213
234
|
- - ">="
|
214
235
|
- !ruby/object:Gem::Version
|
236
|
+
hash: 3
|
215
237
|
segments:
|
216
238
|
- 0
|
217
239
|
version: "0"
|
218
240
|
requirements: []
|
219
241
|
|
220
242
|
rubyforge_project:
|
221
|
-
rubygems_version: 1.3.
|
243
|
+
rubygems_version: 1.3.7
|
222
244
|
signing_key:
|
223
245
|
specification_version: 3
|
224
246
|
summary: Hypermedia aware resource based library in ruby (client side) and ruby on rails (server side).
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Restfulie
|
2
|
-
module Common
|
3
|
-
module Representation
|
4
|
-
# Implements the interface for marshal Xml media type requests (application/xml)
|
5
|
-
class XmlD
|
6
|
-
cattr_reader :media_type_name
|
7
|
-
@@media_type_name = 'application/xml'
|
8
|
-
|
9
|
-
cattr_reader :headers
|
10
|
-
@@headers = {
|
11
|
-
:post => { 'Content-Type' => media_type_name }
|
12
|
-
}
|
13
|
-
|
14
|
-
def unmarshal(string)
|
15
|
-
Hash.from_xml(string)
|
16
|
-
end
|
17
|
-
|
18
|
-
def marshal(entity, rel)
|
19
|
-
return entity if entity.kind_of? String
|
20
|
-
return entity.values.first.to_xml(:root => entity.keys.first) if entity.kind_of?(Hash) && entity.size==1
|
21
|
-
entity.to_xml
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|