restfulie 0.9.3 → 1.0.0.beta1
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/Gemfile +2 -0
- data/Gemfile.lock +2 -2
- data/README.textile +4 -2
- data/Rakefile +12 -13
- data/lib/restfulie/client/base.rb +9 -2
- data/lib/restfulie/client/cache/basic.rb +6 -5
- data/lib/restfulie/client/cache/http_ext.rb +10 -8
- data/lib/restfulie/client/cache/restrictions.rb +1 -6
- data/lib/restfulie/client/dsl.rb +66 -0
- data/lib/restfulie/client/entry_point.rb +34 -9
- data/lib/restfulie/client/ext/atom_ext.rb +4 -2
- data/lib/restfulie/client/ext/json_ext.rb +5 -1
- data/lib/restfulie/client/feature/base.rb +75 -0
- data/lib/restfulie/client/feature/base_request.rb +35 -0
- data/lib/restfulie/client/feature/cache.rb +16 -0
- data/lib/restfulie/client/feature/enhance_response.rb +12 -0
- data/lib/restfulie/client/feature/follow_request.rb +41 -0
- data/lib/restfulie/client/feature/history.rb +26 -0
- data/lib/restfulie/client/feature/history_request.rb +19 -0
- data/lib/restfulie/client/feature/open_search/pattern_matcher.rb +25 -0
- data/lib/restfulie/client/feature/open_search.rb +21 -0
- data/lib/restfulie/client/feature/serialize_body.rb +32 -0
- data/lib/restfulie/client/feature/setup_header.rb +22 -0
- data/lib/restfulie/client/feature/throw_error.rb +41 -0
- data/lib/restfulie/client/feature/verb.rb +119 -0
- data/lib/restfulie/client/feature.rb +5 -0
- data/lib/restfulie/client/http/response_holder.rb +26 -6
- data/lib/restfulie/client/http.rb +1 -21
- data/lib/restfulie/client/master_delegator.rb +31 -0
- data/lib/restfulie/client/mikyung/core.rb +5 -4
- data/lib/restfulie/client/mikyung/steady_state_walker.rb +1 -1
- data/lib/restfulie/client/mikyung.rb +1 -8
- data/lib/restfulie/client.rb +3 -1
- data/lib/restfulie/common/converter/atom/base.rb +2 -0
- data/lib/restfulie/common/converter/form_url_encoded.rb +16 -0
- data/lib/restfulie/common/converter/json/base.rb +5 -2
- data/lib/restfulie/common/converter/open_search/descriptor.rb +32 -0
- data/lib/restfulie/common/converter/open_search.rb +16 -0
- data/lib/restfulie/common/converter/xml/base.rb +3 -1
- data/lib/restfulie/common/converter/xml/builder.rb +3 -2
- data/lib/restfulie/common/converter/xml/helpers.rb +4 -4
- data/lib/restfulie/common/converter/xml/link.rb +5 -0
- data/lib/restfulie/common/converter/xml/links.rb +1 -5
- data/lib/restfulie/common/converter.rb +25 -4
- data/lib/restfulie/common/core_ext/hash.rb +6 -0
- data/lib/restfulie/common/links.rb +9 -0
- data/lib/restfulie/common/representation/atom/base.rb +34 -33
- data/lib/restfulie/common/representation/atom/xml.rb +5 -10
- data/lib/restfulie/common/representation/generic.rb +0 -12
- data/lib/restfulie/common/representation/json/keys_as_methods.rb +2 -0
- data/lib/restfulie/common/representation.rb +2 -9
- data/lib/restfulie/common.rb +2 -1
- data/lib/restfulie/server/action_controller/trait/cacheable.rb +81 -0
- data/lib/restfulie/server/action_controller/trait/created.rb +17 -0
- data/lib/restfulie/server/action_controller/trait/save_prior_to_create.rb +13 -0
- data/lib/restfulie/server/action_controller/trait.rb +9 -0
- data/lib/restfulie/server/action_controller.rb +1 -5
- data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +1 -1
- data/lib/restfulie/server.rb +6 -0
- data/lib/restfulie/version.rb +4 -4
- data/lib/restfulie.rb +21 -3
- metadata +37 -26
- data/lib/restfulie/client/ext/xml_ext.rb +0 -4
- data/lib/restfulie/client/http/link_request_builder.rb +0 -16
- data/lib/restfulie/client/http/request_adapter.rb +0 -213
- data/lib/restfulie/client/http/request_builder.rb +0 -114
- data/lib/restfulie/client/http/request_builder_executor.rb +0 -24
- data/lib/restfulie/client/http/request_executor.rb +0 -17
- data/lib/restfulie/client/http/request_follow.rb +0 -42
- data/lib/restfulie/client/http/request_follow_executor.rb +0 -10
- data/lib/restfulie/client/http/request_history.rb +0 -71
- data/lib/restfulie/client/http/request_history_executor.rb +0 -10
- data/lib/restfulie/client/http/request_marshaller.rb +0 -129
- data/lib/restfulie/client/http/request_marshaller_executor.rb +0 -10
- data/lib/restfulie/client/http/response.rb +0 -23
- data/lib/restfulie/client/http/response_handler.rb +0 -67
- data/lib/restfulie/server/action_controller/cacheable_responder.rb +0 -77
- data/lib/restfulie/server/action_controller/created_responder.rb +0 -19
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -72,7 +72,7 @@ GEM
|
|
72
72
|
thor (~> 0.14.0)
|
73
73
|
rake (0.8.7)
|
74
74
|
rcov (0.9.8)
|
75
|
-
|
75
|
+
respondie (0.9.0)
|
76
76
|
rspec (2.0.0.beta.19)
|
77
77
|
rspec-core (= 2.0.0.beta.19)
|
78
78
|
rspec-expectations (= 2.0.0.beta.19)
|
@@ -118,7 +118,7 @@ DEPENDENCIES
|
|
118
118
|
rack-conneg
|
119
119
|
rails (>= 3.0.0)
|
120
120
|
rcov
|
121
|
-
|
121
|
+
respondie
|
122
122
|
rspec-rails (>= 2.0.0.beta.19)
|
123
123
|
ruby-debug
|
124
124
|
sinatra
|
data/README.textile
CHANGED
@@ -51,7 +51,7 @@ That's it. Restfulie will take care of rendering a valid representation accordin
|
|
51
51
|
end
|
52
52
|
</pre>
|
53
53
|
|
54
|
-
You can go through an entire application by "watching this video":http://guilhermesilveira.wordpress.com or "downloading an example application":http://github.com/caelum/
|
54
|
+
You can go through an entire application by "watching this video":http://guilhermesilveira.wordpress.com or "downloading an example application contained within the restfulie ":http://github.com/caelum/restfulie/tree/master/full-examples/rest_from_scratch/
|
55
55
|
|
56
56
|
h2. Simple client example
|
57
57
|
|
@@ -70,7 +70,7 @@ h2. Full examples
|
|
70
70
|
|
71
71
|
You can view an entire application running Restfulie under *spec/integration/order/server* and *spec/integration/order/client* in this git repository.
|
72
72
|
|
73
|
-
"You can also download a full example of a REST based agent and server":http://github.com/caelum/full-
|
73
|
+
"You can also download a full example of a REST based agent and server":http://github.com/caelum/restfulie/tree/master/full-examples/rest_from_scratch/ using Restfulie, according to the Rest Architecture Maturity Model.
|
74
74
|
|
75
75
|
h2. Documentation
|
76
76
|
|
@@ -89,6 +89,8 @@ Execute:
|
|
89
89
|
gem install restfulie
|
90
90
|
</pre>
|
91
91
|
|
92
|
+
For use in Rails 2.3, make sure to require the responders_backport gem in addition to the restfulie gem, either in environment.rb or in the Gemfile.
|
93
|
+
|
92
94
|
h2. Building the project
|
93
95
|
|
94
96
|
If you want to build the project and run its tests, remember to install all (client and server) required gem.
|
data/Rakefile
CHANGED
@@ -95,23 +95,22 @@ namespace :test do
|
|
95
95
|
FakeServer.start_server_and_run_spec "full-examples/rest_from_scratch/part_3"
|
96
96
|
end
|
97
97
|
|
98
|
-
task :
|
98
|
+
task :sinatra do
|
99
|
+
FakeServer.start_sinatra do
|
100
|
+
puts "Press something to quit"
|
101
|
+
gets
|
102
|
+
end
|
103
|
+
end
|
99
104
|
|
100
|
-
|
101
|
-
# Spec::Rake::SpecTask.new('rcov') do |t|
|
102
|
-
# t.spec_opts = %w(-fs --color)
|
103
|
-
# t.spec_files = FileList['spec/units/**/*_spec.rb']
|
104
|
-
# t.rcov = true
|
105
|
-
# t.rcov_opts = ["-e", "/Library*", "-e", "~/.rvm", "-e", "spec", "-i", "bin"]
|
106
|
-
# end
|
107
|
-
# desc 'Run coverage test with fake server'
|
108
|
-
# task :run do
|
109
|
-
# start_server_and_invoke_test('test:rcov:rcov')
|
110
|
-
# end
|
111
|
-
# end
|
105
|
+
task :all => ["spec","integration"]
|
112
106
|
|
113
107
|
end
|
114
108
|
|
109
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
110
|
+
# t.spec_files = FileList['spec_*.rb']
|
111
|
+
t.spec_opts = ['--colour', '--format progress']
|
112
|
+
end
|
113
|
+
|
115
114
|
Rake::GemPackageTask.new(spec) do |pkg|
|
116
115
|
pkg.gem_spec = spec
|
117
116
|
end
|
@@ -1,7 +1,14 @@
|
|
1
1
|
module Restfulie
|
2
2
|
module Client#:nodoc
|
3
3
|
module Base
|
4
|
-
|
4
|
+
|
5
|
+
def method_missing(sym, *args, &block)
|
6
|
+
if @base_position.respond_to?(sym)
|
7
|
+
@base_position.send sym, *args, &block
|
8
|
+
else
|
9
|
+
super(sym, *args, &block)
|
10
|
+
end
|
11
|
+
end
|
5
12
|
|
6
13
|
def self.included(base)#:nodoc
|
7
14
|
base.extend(self)
|
@@ -15,7 +22,7 @@ module Restfulie
|
|
15
22
|
def configure
|
16
23
|
configuration = EntryPoint.configuration_of(resource_name)
|
17
24
|
raise "Undefined configuration for #{resource_name}" unless configuration
|
18
|
-
at(configuration.entry_point)
|
25
|
+
@base_position = Restfulie.at(configuration.entry_point)
|
19
26
|
configuration.representations.each do |representation_name,representation|
|
20
27
|
register_representation(representation_name,representation)
|
21
28
|
end
|
@@ -14,10 +14,10 @@ module Restfulie::Client::Cache
|
|
14
14
|
response
|
15
15
|
end
|
16
16
|
|
17
|
-
def get(key, request
|
17
|
+
def get(key, request)
|
18
18
|
|
19
19
|
# debugger
|
20
|
-
response = cache_get(key, request
|
20
|
+
response = cache_get(key, request)
|
21
21
|
return nil if response.nil?
|
22
22
|
|
23
23
|
if response.has_expired_cache?
|
@@ -47,13 +47,14 @@ module Restfulie::Client::Cache
|
|
47
47
|
cache.write(key, values)
|
48
48
|
end
|
49
49
|
|
50
|
-
def cache_get(key, req
|
50
|
+
def cache_get(key, req)
|
51
51
|
return nil unless cache.exist?(key)
|
52
52
|
found = cache.read(key).find do |cached|
|
53
53
|
old_req = cached.first
|
54
54
|
old_response = cached.last
|
55
|
-
|
56
|
-
|
55
|
+
|
56
|
+
headers_match = old_response.vary_headers_for(old_req) == old_response.vary_headers_for(req)
|
57
|
+
if headers_match && old_response.verb == req.verb
|
57
58
|
old_response
|
58
59
|
else
|
59
60
|
false
|
@@ -59,13 +59,13 @@ module Restfulie::Client::HTTP::ResponseCacheCheck
|
|
59
59
|
|
60
60
|
def has_expired_cache?
|
61
61
|
return true if headers['date'].nil?
|
62
|
-
max_time = Time.rfc2822(headers['date']) + cache_max_age.seconds
|
62
|
+
max_time = Time.rfc2822(headers['date'][0]) + cache_max_age.seconds
|
63
63
|
Time.now > max_time
|
64
64
|
end
|
65
65
|
|
66
66
|
# checks if the header's max-age is available and no no-store if available.
|
67
67
|
def may_cache?
|
68
|
-
may_cache_field?(headers['cache-control'])
|
68
|
+
may_cache_method? && may_cache_field?(headers['cache-control'])
|
69
69
|
end
|
70
70
|
|
71
71
|
# Returns whether this cache control field allows caching
|
@@ -86,16 +86,13 @@ module Restfulie::Client::HTTP::ResponseCacheCheck
|
|
86
86
|
max_age_header = value_for(field, /^max-age=(\d+)/)
|
87
87
|
return false if max_age_header.nil?
|
88
88
|
|
89
|
-
|
90
|
-
false
|
91
|
-
else
|
92
|
-
true
|
93
|
-
end
|
89
|
+
return !value_for(field, /^no-store/)
|
94
90
|
end
|
95
91
|
|
96
92
|
# extracts the header value for an specific expression, which can be located at the start or in the middle
|
97
93
|
# of the expression
|
98
94
|
def value_for(value, expression)
|
95
|
+
value = value[0] if value.kind_of? Array
|
99
96
|
value.split(",").find { |obj| obj.strip =~ expression }
|
100
97
|
end
|
101
98
|
|
@@ -113,9 +110,14 @@ module Restfulie::Client::HTTP::ResponseCacheCheck
|
|
113
110
|
end
|
114
111
|
end
|
115
112
|
|
113
|
+
private
|
114
|
+
def may_cache_method?
|
115
|
+
verb == :get || verb == :post
|
116
|
+
end
|
116
117
|
end
|
117
118
|
|
118
|
-
class
|
119
|
+
class Net::HTTPResponse
|
119
120
|
include Restfulie::Client::HTTP::ResponseStatus
|
120
121
|
include Restfulie::Client::HTTP::ResponseCacheCheck
|
122
|
+
|
121
123
|
end
|
@@ -4,12 +4,7 @@ module Restfulie::Client::Cache
|
|
4
4
|
|
5
5
|
# checks whether this request verb and its cache headers allow caching
|
6
6
|
def may_cache?(response)
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
# only Post and Get requests are cacheable so far
|
11
|
-
def may_cache_method?(verb)
|
12
|
-
verb == :get || verb == :post
|
7
|
+
response && response.may_cache?
|
13
8
|
end
|
14
9
|
|
15
10
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Restfulie::Client
|
2
|
+
class Dsl
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@requests = []
|
6
|
+
trait :base
|
7
|
+
trait :verb
|
8
|
+
request :base_request
|
9
|
+
request :setup_header
|
10
|
+
request :serialize_body
|
11
|
+
request :enhance_response
|
12
|
+
# request :cache
|
13
|
+
request :follow_request
|
14
|
+
end
|
15
|
+
|
16
|
+
def request(what)
|
17
|
+
req = "Restfulie::Client::Feature::#{what.to_s.classify}".constantize
|
18
|
+
@requests << req
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def trait(sym)
|
23
|
+
t = "Restfulie::Client::Feature::#{sym.to_s.classify}".constantize
|
24
|
+
self.extend t
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(sym, *args)
|
29
|
+
if Restfulie::Client::Feature.const_defined? sym.to_s.classify
|
30
|
+
loaded = true
|
31
|
+
trait sym
|
32
|
+
end
|
33
|
+
if Restfulie::Client::Feature.const_defined? "#{sym.to_s.classify}Request"
|
34
|
+
loaded = true
|
35
|
+
request "#{sym.to_s}Request"
|
36
|
+
end
|
37
|
+
if loaded
|
38
|
+
self
|
39
|
+
else
|
40
|
+
super sym, *args
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def request_flow(env = {})
|
45
|
+
Parser.new(@requests).continue(self, nil, env)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class Parser
|
51
|
+
|
52
|
+
def initialize(stack)
|
53
|
+
@stack = stack.dup
|
54
|
+
end
|
55
|
+
|
56
|
+
def continue(request, response, env)
|
57
|
+
current = @stack.pop
|
58
|
+
if current.nil?
|
59
|
+
return response
|
60
|
+
end
|
61
|
+
filter = current.new
|
62
|
+
filter.execute(self, request, response, env)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -1,14 +1,8 @@
|
|
1
|
+
|
1
2
|
module Restfulie
|
2
3
|
module Client#:nodoc
|
3
|
-
|
4
|
-
|
5
|
-
include HTTP::RequestFollow
|
6
|
-
extend self
|
7
|
-
|
8
|
-
def self.at(uri)
|
9
|
-
Object.new.send(:extend, EntryPoint).at(uri)
|
10
|
-
end
|
11
|
-
|
4
|
+
|
5
|
+
module HTTP::RecipeModule
|
12
6
|
def recipe(converter_sym, options={}, &block)
|
13
7
|
raise 'Undefined block' unless block_given?
|
14
8
|
converter = "Restfulie::Common::Converter::#{converter_sym.to_s.camelize}".constantize
|
@@ -32,5 +26,36 @@ module Restfulie
|
|
32
26
|
end
|
33
27
|
end
|
34
28
|
end
|
29
|
+
|
30
|
+
class HTTP::Recipe < MasterDelegator
|
31
|
+
|
32
|
+
def initialize(requester)
|
33
|
+
@requester = requester
|
34
|
+
@resources_configurations = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
include Restfulie::Client::HTTP::RecipeModule
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class EntryPoint
|
42
|
+
|
43
|
+
@resources_configurations = {}
|
44
|
+
extend Restfulie::Client::HTTP::RecipeModule
|
45
|
+
|
46
|
+
def initialize(requester)
|
47
|
+
@requester = requester
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.at(uri)
|
51
|
+
Restfulie.using {
|
52
|
+
recipe
|
53
|
+
follow_link
|
54
|
+
request_marshaller
|
55
|
+
verb_request
|
56
|
+
}.at(uri)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
35
60
|
end
|
36
61
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# Atom links now can be followed
|
2
2
|
module Restfulie
|
3
3
|
module Common
|
4
4
|
module Representation
|
5
5
|
module Atom
|
6
6
|
class Link
|
7
|
-
|
7
|
+
def follow
|
8
|
+
Restfulie.at(href).as(type)
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Restfulie::Client::Feature
|
2
|
+
module Base
|
3
|
+
|
4
|
+
attr_reader :default_headers, :cookies, :verb, :host
|
5
|
+
attr_writer :headers
|
6
|
+
|
7
|
+
#Set host
|
8
|
+
def at(url)
|
9
|
+
if self.host.nil?
|
10
|
+
self.host= url
|
11
|
+
else
|
12
|
+
self.host= self.host + url
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
#Set Content-Type and Accept headers
|
18
|
+
def as(content_type)
|
19
|
+
headers['Content-Type'] = content_type
|
20
|
+
accepts(content_type)
|
21
|
+
end
|
22
|
+
|
23
|
+
#Set Accept headers
|
24
|
+
def accepts(content_type)
|
25
|
+
headers['Accept'] = content_type
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Merge internal header
|
30
|
+
#
|
31
|
+
# * <tt>headers (e.g. {'Cache-control' => 'no-cache'})</tt>
|
32
|
+
#
|
33
|
+
def with(headers)
|
34
|
+
headers.merge!(headers)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Path (e.g. http://restfulie.com/posts => /posts)
|
39
|
+
def path
|
40
|
+
host.path + (host.query.nil? ? "" : "?#{host.query}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def host=(host)
|
44
|
+
if host.is_a?(::URI)
|
45
|
+
@host = host
|
46
|
+
else
|
47
|
+
@host = ::URI.parse(host)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_headers
|
52
|
+
@default_headers ||= {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def headers
|
56
|
+
@headers ||= {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def http_to_s(method, path, *args)
|
60
|
+
result = ["#{method.to_s.upcase} #{path}"]
|
61
|
+
|
62
|
+
arguments = args.dup
|
63
|
+
headers = arguments.extract_options!
|
64
|
+
|
65
|
+
if [:post, :put].include?(method)
|
66
|
+
body = arguments.shift
|
67
|
+
end
|
68
|
+
|
69
|
+
result << headers.collect { |key, value| "#{key}: #{value}" }.join("\n")
|
70
|
+
|
71
|
+
(result + [body ? (body.inspect + "\n") : nil]).compact.join("\n") << "\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Restfulie::Client::Feature::BaseRequest
|
2
|
+
|
3
|
+
def execute(flow, request, response, env)
|
4
|
+
request!(request.verb, request.host, request.path, request, flow, env)
|
5
|
+
end
|
6
|
+
|
7
|
+
# Executes a request against your server and return a response instance.
|
8
|
+
# * <tt>method: :get,:post,:delete,:head,:put</tt>
|
9
|
+
# * <tt>path: '/posts'</tt>
|
10
|
+
# * <tt>args: payload: 'some text' and/or headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
|
11
|
+
def request!(method, host, path, request, flow, env)
|
12
|
+
|
13
|
+
::Restfulie::Common::Logger.logger.info(request.http_to_s(method, path, [request.headers])) if ::Restfulie::Common::Logger.logger
|
14
|
+
begin
|
15
|
+
http_request = get_connection_provider(host)
|
16
|
+
|
17
|
+
if env[:body]
|
18
|
+
response = http_request.send(method, path, env[:body], request.headers)
|
19
|
+
else
|
20
|
+
response = http_request.send(method, path, request.headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
rescue Exception => e
|
24
|
+
response = e
|
25
|
+
end
|
26
|
+
|
27
|
+
flow.continue(request, response, env)
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_connection_provider(host)
|
32
|
+
@connection ||= ::Net::HTTP.new(host.host, host.port)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Restfulie::Client::Feature::Cache
|
2
|
+
|
3
|
+
def execute(flow, request, response, env)
|
4
|
+
found = Restfulie::Client.cache_provider.get([request.host, request.path], request)
|
5
|
+
return found if found
|
6
|
+
|
7
|
+
resp = flow.continue(request, response, env)
|
8
|
+
if resp.kind_of?(Exception)
|
9
|
+
resp
|
10
|
+
else
|
11
|
+
Restfulie::Client.cache_provider.put([request.host, request.path], request, resp)
|
12
|
+
resp
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Restfulie::Client::Feature
|
2
|
+
class EnhanceResponse
|
3
|
+
def execute(flow, request, response, env)
|
4
|
+
resp = flow.continue(request, response, env)
|
5
|
+
unless resp.kind_of? ::Restfulie::Client::HTTP::ResponseHolder
|
6
|
+
resp.extend(::Restfulie::Client::HTTP::ResponseHolder)
|
7
|
+
resp.results_from request, resp
|
8
|
+
end
|
9
|
+
resp
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# ==== FollowLink follow new location of a document usually with response codes 201,301,302,303 and 307. You can also configure other codes.
|
2
|
+
#
|
3
|
+
# ==== Example:
|
4
|
+
# @executor = ::Restfulie::Client::HTTP::FollowLinkExecutor.new("http://restfulie.com") #this class includes FollowLink module.
|
5
|
+
# @executor.at('/custom/songs').accepts('application/atom+xml').follow(201).post!("custom").code
|
6
|
+
class Restfulie::Client::Feature::FollowRequest
|
7
|
+
|
8
|
+
def follow(code = nil)
|
9
|
+
unless code.nil? or follow_codes.include?(code)
|
10
|
+
follow_codes << code
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(flow, request, response, env)
|
16
|
+
resp = flow.continue(request, response, env)
|
17
|
+
if !resp.respond_to?(:code)
|
18
|
+
return resp
|
19
|
+
end
|
20
|
+
code = resp.code.to_i
|
21
|
+
if follow_codes.include?(code)
|
22
|
+
if code==201 && !resp.body.empty?
|
23
|
+
resp
|
24
|
+
else
|
25
|
+
location = resp.response.headers['location'] || resp.response.headers['Location']
|
26
|
+
raise Error::AutoFollowWithoutLocationError.new(request, resp) unless location
|
27
|
+
# use the first location available
|
28
|
+
location = location[0]
|
29
|
+
Restfulie.at(location).accepts(request.headers['Accept']).get
|
30
|
+
end
|
31
|
+
else
|
32
|
+
resp
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def follow_codes
|
39
|
+
@follow_codes ||= [201,301,302,303,307]
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Restfulie::Client::Feature::History
|
2
|
+
|
3
|
+
def snapshots
|
4
|
+
@snapshots ||= []
|
5
|
+
end
|
6
|
+
|
7
|
+
def max_to_remind
|
8
|
+
10
|
9
|
+
end
|
10
|
+
|
11
|
+
def history(number)
|
12
|
+
snapshots[snapshots.size + number] || raise("Undefined snapshot for #{number}, only containing #{@snapshots.size} snapshots.")
|
13
|
+
end
|
14
|
+
|
15
|
+
def make_snapshot(request)
|
16
|
+
snapshots.shift if snapshot_full?
|
17
|
+
snapshots << request
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def snapshot_full?
|
23
|
+
snapshots.size >= max_to_remind
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# ==== RequestHistory
|
2
|
+
# Uses RequestBuilder and remind previous requests
|
3
|
+
#
|
4
|
+
# ==== Example:
|
5
|
+
#
|
6
|
+
# @executor = ::Restfulie::Client::HTTP::RequestHistoryExecutor.new("http://restfulie.com") #this class includes RequestHistory module.
|
7
|
+
# @executor.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #first request
|
8
|
+
# @executor.at('/blogs').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #second request
|
9
|
+
# @executor.request_history!(0) #doing first request
|
10
|
+
#
|
11
|
+
class Restfulie::Client::Feature::HistoryRequest
|
12
|
+
|
13
|
+
def execute(flow, request, response, env)
|
14
|
+
resp = flow.continue(request, response, env)
|
15
|
+
request.make_snapshot(request)
|
16
|
+
resp
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Restfulie::Client::Feature::OpenSearch
|
2
|
+
|
3
|
+
class PatternMatcher
|
4
|
+
|
5
|
+
def match(params, pattern)
|
6
|
+
params = params.collect do |key, value|
|
7
|
+
[key, value]
|
8
|
+
end
|
9
|
+
pattern = params.inject(pattern) do |pattern, p|
|
10
|
+
what = "{#{p[0]}}"
|
11
|
+
if pattern[what]
|
12
|
+
pattern[what]= "#{p[1]}"
|
13
|
+
end
|
14
|
+
what = "{#{p[0]}?}"
|
15
|
+
if pattern[what]
|
16
|
+
pattern[what]= "#{p[1]}"
|
17
|
+
end
|
18
|
+
pattern
|
19
|
+
end
|
20
|
+
pattern.gsub(/\{[^\?]*\?\}/,"")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Restfulie::Client::Feature
|
2
|
+
module OpenSearch
|
3
|
+
autoload :PatternMatcher, 'restfulie/client/feature/open_search/pattern_matcher'
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
module Restfulie::Client::Feature::OpenSearch
|
8
|
+
|
9
|
+
def search(params)
|
10
|
+
at ("?" + PatternMatcher.new.match(params, params_pattern))
|
11
|
+
get
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :params_pattern
|
15
|
+
|
16
|
+
def with_pattern(params_pattern)
|
17
|
+
@params_pattern = params_pattern
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Restfulie::Client::Feature::SerializeBody
|
2
|
+
|
3
|
+
def execute(flow, request, response, env)
|
4
|
+
|
5
|
+
if should_have_payload?(request.verb)
|
6
|
+
|
7
|
+
payload = env[:body]
|
8
|
+
if payload && !(payload.kind_of?(String) && payload.empty?)
|
9
|
+
type = request.headers['Content-Type']
|
10
|
+
raise Restfulie::Common::Error::RestfulieError, "Missing content type related to the data to be submitted" unless type
|
11
|
+
|
12
|
+
marshaller = Restfulie::Common::Converter.content_type_for(type)
|
13
|
+
raise Restfulie::Common::Error::RestfulieError, "Missing content type for #{type} related to the data to be submitted" unless marshaller
|
14
|
+
|
15
|
+
rel = request.respond_to?(:rel) ? request.rel : ""
|
16
|
+
env[:body] = marshaller.marshal(payload, { :rel => rel, :recipe => env[:recipe] })
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
flow.continue(request, response, env)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
PAYLOAD_METHODS = {:put=>true,:post=>true,:patch=>true}
|
27
|
+
|
28
|
+
def should_have_payload?(method)
|
29
|
+
PAYLOAD_METHODS[method]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|