rest-client-components 0.2.1
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/LICENSE +20 -0
- data/README.rdoc +79 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/examples/caching.rb +53 -0
- data/examples/parsing.rb +24 -0
- data/lib/restclient/components.rb +152 -0
- data/rest-client-components.gemspec +59 -0
- data/spec/components_spec.rb +178 -0
- data/spec/spec_helper.rb +9 -0
- metadata +87 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Cyril Rohr
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
= rest-client-components
|
2
|
+
|
3
|
+
RestClient on steroids !
|
4
|
+
|
5
|
+
Want to add transparent HTTP caching to the rest-client[http://github.com/archiloque/rest-client] gem ? It's as simple as:
|
6
|
+
require 'restclient/components'
|
7
|
+
require 'rack/cache'
|
8
|
+
RestClient.enable Rack::Cache
|
9
|
+
|
10
|
+
Want to log the requests in the commonlog format ?
|
11
|
+
require 'restclient/components'
|
12
|
+
RestClient.enable Rack::CommonLogger, STDOUT
|
13
|
+
|
14
|
+
Want to enable both ?
|
15
|
+
require 'restclient/components'
|
16
|
+
require 'rack/cache'
|
17
|
+
RestClient.enable Rack::CommonLogger, STDOUT
|
18
|
+
RestClient.enable Rack::Cache
|
19
|
+
|
20
|
+
This works with any Rack middleware, thus you can reuse the wide range of existing Rack middleware to add functionalities to RestClient with very little effort.
|
21
|
+
The order in which you enable components will be respected.
|
22
|
+
|
23
|
+
Note that the rest-client behaviour is also respected: you'll get back a RestClient::Response or RestClient exceptions as a result of your requests. If you prefer a more rackesque approach, just disable the Compatibility component, and you will get back a response conform to the Rack SPEC:
|
24
|
+
require 'restclient/components'
|
25
|
+
RestClient.disable RestClient::Rack::Compatibility
|
26
|
+
status, header, body = RestClient.get('http://some/url')
|
27
|
+
In that case, you will only get exceptions for connection or timeout errors (RestClient::ServerBrokeConnection or RestClient::RequestTimeout)
|
28
|
+
|
29
|
+
= Installation
|
30
|
+
|
31
|
+
gem install rest-client-components
|
32
|
+
|
33
|
+
= Usage
|
34
|
+
Example with Rack::Cache, and Rack::CommonLogger
|
35
|
+
|
36
|
+
require 'restclient/components'
|
37
|
+
require 'rack/cache'
|
38
|
+
|
39
|
+
RestClient.enable Rack::CommonLogger
|
40
|
+
# Enable the cache Rack middleware, and store both meta and entity data in files:
|
41
|
+
# See http://tomayko.com/src/rack-cache/configuration for the list of available options
|
42
|
+
RestClient.enable Rack::Cache,
|
43
|
+
:metastore => 'file:/tmp/cache/meta',
|
44
|
+
:entitystore => 'file:/tmp/cache/body'
|
45
|
+
|
46
|
+
|
47
|
+
# ... done !
|
48
|
+
# Then you can make your requests as usual:
|
49
|
+
# You'll get a log for each call, and the resources will be automatically and transparently cached for you according to their HTTP headers.
|
50
|
+
# Cache invalidation on requests other than GET is also transparently supported, thanks to Rack::Cache. Enjoy !
|
51
|
+
|
52
|
+
RestClient.get 'http://some/cacheable/resource'
|
53
|
+
# or
|
54
|
+
resource = RestClient::Resource.new('http://some/cacheable/resource')
|
55
|
+
|
56
|
+
# obviously, caching is only interesting if you request the same resource multiple times, e.g. :
|
57
|
+
resource.get # get from origin server, and cache if possible
|
58
|
+
resource.get # get from cache, if still fresh.
|
59
|
+
resource.put(...) # will automatically invalidate the cache, so that a subsequent GET request on the same resource does not return the cached resource
|
60
|
+
resource.get # get from origin server, and cache if possible
|
61
|
+
# ...
|
62
|
+
resource.get(:cache_control => 'no-cache') # explicitly tells to bypass the cache, requires rack-cache >= 0.5 and :allow_reload => true option
|
63
|
+
# ...
|
64
|
+
resource.delete(...) # will invalidate the cache
|
65
|
+
resource.get # should raise a RestClient::ResourceNotFound exception
|
66
|
+
|
67
|
+
|
68
|
+
Now, you just need to make your resources cacheable, so unless you've already taken care of that, do yourself a favor and read:
|
69
|
+
* the HTTP specification related to HTTP caching - http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
70
|
+
* Things Caches Do - http://tomayko.com/writings/things-caches-do
|
71
|
+
|
72
|
+
= Dependencies
|
73
|
+
|
74
|
+
* rest-client >= 1.2.0
|
75
|
+
* rack >= 1.0.1
|
76
|
+
|
77
|
+
= COPYRIGHT
|
78
|
+
|
79
|
+
Copyright (c) 2009 Cyril Rohr. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |s|
|
6
|
+
s.name = "rest-client-components"
|
7
|
+
s.summary = %Q{RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.}
|
8
|
+
s.email = "cyril.rohr@gmail.com"
|
9
|
+
s.homepage = "http://github.com/crohr/rest-client-components"
|
10
|
+
s.description = "RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc."
|
11
|
+
s.authors = ["Cyril Rohr"]
|
12
|
+
s.add_dependency "rest-client", ">= 1.2.0"
|
13
|
+
s.add_dependency "rack", ">= 1.0.1"
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake/rdoctask'
|
20
|
+
Rake::RDocTask.new do |rdoc|
|
21
|
+
rdoc.rdoc_dir = 'rdoc'
|
22
|
+
rdoc.title = 'rest-client-components'
|
23
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
24
|
+
rdoc.rdoc_files.include('README*')
|
25
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'spec/rake/spectask'
|
29
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
30
|
+
t.libs << 'lib' << 'spec'
|
31
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.1
|
data/examples/caching.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# In this example, https://localhost:3443/sid/grid5000/sites/grenoble/jobs is a resource having an Expires header, that makes it cacheable.
|
2
|
+
# Note how POSTing a payload to this resource automatically invalidates the previously cached entry.
|
3
|
+
require File.dirname(__FILE__) + '/../lib/restclient/components'
|
4
|
+
require 'rack/cache'
|
5
|
+
require 'json'
|
6
|
+
require 'zlib'
|
7
|
+
RestClient.log = 'stdout'
|
8
|
+
RestClient.enable Rack::Cache, :allow_reload => true, :allow_revalidate => true
|
9
|
+
|
10
|
+
api = RestClient::Resource.new('https://localhost:3443')
|
11
|
+
def get_jobs(api, headers={})
|
12
|
+
puts "*** GETting jobs..."
|
13
|
+
jobs = JSON.parse api['/sid/grid5000/sites/grenoble/jobs'].get({:accept => :json}.merge(headers))
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
puts "Number of jobs=#{get_jobs(api)['items'].length}"
|
18
|
+
puts "Number of jobs=#{get_jobs(api)['items'].length}"
|
19
|
+
puts "*** POSTing new job"
|
20
|
+
job = {
|
21
|
+
:resources => "nodes=1",
|
22
|
+
:command => "sleep 120"
|
23
|
+
}
|
24
|
+
api['/sid/grid5000/sites/grenoble/jobs'].post(job.to_json, :content_type => :json, :accept => :json)
|
25
|
+
puts "Number of jobs=#{get_jobs(api)['items'].length}"
|
26
|
+
rescue RestClient::Exception => e
|
27
|
+
if e.respond_to?(:response)
|
28
|
+
p e.response.to_hash
|
29
|
+
p Zlib::GzipReader.new(StringIO.new(e.response.body)).read
|
30
|
+
else
|
31
|
+
p e.message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
__END__
|
36
|
+
This example displays:
|
37
|
+
*** GETting jobs...
|
38
|
+
RestClient.get "https://localhost:3443/sid/grid5000/sites/grenoble/jobs", headers: {"Accept-encoding"=>"gzip, deflate", "Accept"=>"application/json"}
|
39
|
+
# => 200 OK | application/json 295 bytes
|
40
|
+
cache: [GET /sid/grid5000/sites/grenoble/jobs] miss
|
41
|
+
Number of jobs=1
|
42
|
+
*** GETting jobs...
|
43
|
+
cache: [GET /sid/grid5000/sites/grenoble/jobs] fresh
|
44
|
+
Number of jobs=1
|
45
|
+
*** POSTing new job
|
46
|
+
RestClient.post "https://localhost:3443/sid/grid5000/sites/grenoble/jobs", headers: {"Accept-encoding"=>"gzip, deflate", "Content-type"=>"application/json", "Content-Length"=>"45", "Accept"=>"application/json"}, paylod: "{\"resources\":\"nodes=1\",\"command\":\"sleep 120\"}"
|
47
|
+
# => 201 Created | application/json 181 bytes
|
48
|
+
cache: [POST /sid/grid5000/sites/grenoble/jobs] invalidate, pass
|
49
|
+
*** GETting jobs...
|
50
|
+
RestClient.get "https://localhost:3443/sid/grid5000/sites/grenoble/jobs", headers: {"Accept-encoding"=>"gzip, deflate", "Accept"=>"application/json"}
|
51
|
+
# => 200 OK | application/json 323 bytes
|
52
|
+
cache: [GET /sid/grid5000/sites/grenoble/jobs] miss
|
53
|
+
Number of jobs=2
|
data/examples/parsing.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# In this example, we automatically parse the response body if the Content-Type looks like JSON
|
2
|
+
require File.dirname(__FILE__) + '/../lib/restclient/components'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
class JSON
|
7
|
+
def initialize app
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
status, header, body = @app.call env
|
13
|
+
parsed_body = ::JSON.parse body.join if header['Content-Type'] =~ /^application\/.*json/i
|
14
|
+
[status, header, parsed_body]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
RestClient.disable RestClient::Rack::Compatibility
|
20
|
+
# this breaks the Rack spec, but it should be the last component to be enabled.
|
21
|
+
RestClient.enable Rack::JSON
|
22
|
+
|
23
|
+
status, header, parsed_body = RestClient.get "http://twitter.com/statuses/user_timeline/20191563.json"
|
24
|
+
p parsed_body.map{|tweet| tweet['text']}
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module RestClient
|
5
|
+
module Rack
|
6
|
+
class Compatibility
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
status, header, body = @app.call(env)
|
13
|
+
if e = env['restclient.error']
|
14
|
+
raise e
|
15
|
+
else
|
16
|
+
response = RestClient::MockNetHTTPResponse.new(body, status, header)
|
17
|
+
RestClient::Response.new(response.body.join, response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class <<self
|
24
|
+
attr_reader :components
|
25
|
+
end
|
26
|
+
|
27
|
+
# Enable a Rack component. You may enable as many components as you want.
|
28
|
+
# e.g.
|
29
|
+
# Transparent HTTP caching:
|
30
|
+
# RestClient.enable Rack::Cache,
|
31
|
+
# :verbose => true,
|
32
|
+
# :metastore => 'file:/var/cache/rack/meta'
|
33
|
+
# :entitystore => 'file:/var/cache/rack/body'
|
34
|
+
#
|
35
|
+
# Transparent logging of HTTP requests (commonlog format):
|
36
|
+
# RestClient.enable Rack::CommonLogger, STDOUT
|
37
|
+
#
|
38
|
+
# Please refer to the documentation of each rack component for the list of available options.
|
39
|
+
#
|
40
|
+
def self.enable(component, *args)
|
41
|
+
# remove any existing component of the same class
|
42
|
+
disable(component)
|
43
|
+
@components.unshift [component, args]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Disable a component
|
47
|
+
# RestClient.disable Rack::Cache
|
48
|
+
# => array of remaining components
|
49
|
+
def self.disable(component)
|
50
|
+
@components.delete_if{|(existing_component, options)| component == existing_component}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns true if the given component is enabled, false otherwise
|
54
|
+
# RestClient.enable Rack::Cache
|
55
|
+
# RestClient.enabled?(Rack::Cache)
|
56
|
+
# => true
|
57
|
+
def self.enabled?(component)
|
58
|
+
!@components.detect{|(existing_component, options)| component == existing_component}.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.reset
|
62
|
+
# hash of the enabled components
|
63
|
+
@components = [[RestClient::Rack::Compatibility]]
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.debeautify_headers(headers = {}) # :nodoc:
|
67
|
+
headers.inject({}) do |out, (key, value)|
|
68
|
+
out[key.to_s.gsub(/_/, '-').split("-").map{|w| w.capitalize}.join("-")] = value.to_s
|
69
|
+
out
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
reset
|
74
|
+
|
75
|
+
# Reopen the RestClient::Request class to add a level of indirection in order to create the stack of Rack middleware.
|
76
|
+
#
|
77
|
+
class Request
|
78
|
+
alias_method :original_execute, :execute
|
79
|
+
def execute
|
80
|
+
uri = URI.parse(@url)
|
81
|
+
uri_path_split = uri.path.split("/")
|
82
|
+
path_info = (last_part = uri_path_split.pop) ? "/"+last_part : ""
|
83
|
+
script_name = uri_path_split.join("/")
|
84
|
+
# minimal rack spec
|
85
|
+
env = {
|
86
|
+
"restclient.request" => self,
|
87
|
+
"REQUEST_METHOD" => @method.to_s.upcase,
|
88
|
+
"SCRIPT_NAME" => script_name,
|
89
|
+
"PATH_INFO" => path_info,
|
90
|
+
"QUERY_STRING" => uri.query || "",
|
91
|
+
"SERVER_NAME" => uri.host,
|
92
|
+
"SERVER_PORT" => uri.port.to_s,
|
93
|
+
"rack.version" => ::Rack::VERSION,
|
94
|
+
"rack.run_once" => false,
|
95
|
+
"rack.multithread" => true,
|
96
|
+
"rack.multiprocess" => true,
|
97
|
+
"rack.url_scheme" => uri.scheme,
|
98
|
+
"rack.input" => StringIO.new,
|
99
|
+
"rack.errors" => $stderr # Rack-Cache writes errors into this field
|
100
|
+
}
|
101
|
+
@processed_headers.each do |key, value|
|
102
|
+
env.merge!("HTTP_"+key.to_s.gsub("-", "_").upcase => value)
|
103
|
+
end
|
104
|
+
stack = RestClient::RACK_APP
|
105
|
+
RestClient.components.each do |(component, args)|
|
106
|
+
if (args || []).empty?
|
107
|
+
stack = component.new(stack)
|
108
|
+
else
|
109
|
+
stack = component.new(stack, *args)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
stack.call(env)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# A class that mocks the behaviour of a Net::HTTPResponse class.
|
117
|
+
# It is required since RestClient::Response must be initialized with a class that responds to :code and :to_hash.
|
118
|
+
class MockNetHTTPResponse
|
119
|
+
attr_reader :body, :header, :status
|
120
|
+
alias_method :code, :status
|
121
|
+
|
122
|
+
def initialize(body, status, header)
|
123
|
+
@body = body
|
124
|
+
@status = status
|
125
|
+
@header = header
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_hash
|
129
|
+
@header.inject({}) {|out, (key, value)|
|
130
|
+
# In Net::HTTP, header values are arrays
|
131
|
+
out[key] = [value]
|
132
|
+
out
|
133
|
+
}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
RACK_APP = Proc.new { |env|
|
138
|
+
begin
|
139
|
+
# get the original request and execute it
|
140
|
+
response = env['restclient.request'].original_execute
|
141
|
+
# to satisfy Rack::Lint
|
142
|
+
response.headers.delete(:status)
|
143
|
+
[response.code, RestClient.debeautify_headers( response.headers ), [response.to_s]]
|
144
|
+
rescue RestClient::ExceptionWithResponse => e
|
145
|
+
env['restclient.error'] = e
|
146
|
+
# e is a Net::HTTPResponse
|
147
|
+
response = RestClient::Response.new(e.response.body, e.response)
|
148
|
+
[response.code, RestClient.debeautify_headers( response.headers ), [response.to_s]]
|
149
|
+
end
|
150
|
+
}
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rest-client-components}
|
8
|
+
s.version = "0.2.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Cyril Rohr"]
|
12
|
+
s.date = %q{2010-01-10}
|
13
|
+
s.description = %q{RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.}
|
14
|
+
s.email = %q{cyril.rohr@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"examples/caching.rb",
|
25
|
+
"examples/parsing.rb",
|
26
|
+
"lib/restclient/components.rb",
|
27
|
+
"rest-client-components.gemspec",
|
28
|
+
"spec/components_spec.rb",
|
29
|
+
"spec/spec_helper.rb"
|
30
|
+
]
|
31
|
+
s.homepage = %q{http://github.com/crohr/rest-client-components}
|
32
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.rubygems_version = %q{1.3.5}
|
35
|
+
s.summary = %q{RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.}
|
36
|
+
s.test_files = [
|
37
|
+
"spec/components_spec.rb",
|
38
|
+
"spec/spec_helper.rb",
|
39
|
+
"examples/caching.rb",
|
40
|
+
"examples/parsing.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_runtime_dependency(%q<rest-client>, [">= 1.2.0"])
|
49
|
+
s.add_runtime_dependency(%q<rack>, [">= 1.0.1"])
|
50
|
+
else
|
51
|
+
s.add_dependency(%q<rest-client>, [">= 1.2.0"])
|
52
|
+
s.add_dependency(%q<rack>, [">= 1.0.1"])
|
53
|
+
end
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rest-client>, [">= 1.2.0"])
|
56
|
+
s.add_dependency(%q<rack>, [">= 1.0.1"])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/restclient/components'
|
3
|
+
require 'logger'
|
4
|
+
require 'rack/cache'
|
5
|
+
describe "Components for RestClient" do
|
6
|
+
before(:each) do
|
7
|
+
RestClient.reset
|
8
|
+
@mock_304_net_http_response = mock('http response', :code => 304, :body => "body", :to_hash => {"Date"=>["Mon, 04 Jan 2010 13:42:43 GMT"], 'header1' => ['value1', 'value2']})
|
9
|
+
@env = {
|
10
|
+
'REQUEST_METHOD' => 'GET',
|
11
|
+
"SCRIPT_NAME" => '/some/cacheable',
|
12
|
+
"PATH_INFO" => '/resource',
|
13
|
+
"QUERY_STRING" => 'q1=a&q2=b',
|
14
|
+
"SERVER_NAME" => 'domain.tld',
|
15
|
+
"SERVER_PORT" => '8888',
|
16
|
+
"rack.version" => Rack::VERSION,
|
17
|
+
"rack.run_once" => false,
|
18
|
+
"rack.multithread" => true,
|
19
|
+
"rack.multiprocess" => true,
|
20
|
+
"rack.url_scheme" => "http",
|
21
|
+
"rack.input" => StringIO.new,
|
22
|
+
"rack.errors" => $stderr
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should automatically have the Compatibility component enabled" do
|
27
|
+
RestClient.components.first.should == [RestClient::Rack::Compatibility]
|
28
|
+
end
|
29
|
+
it "should enable components" do
|
30
|
+
RestClient.enable Rack::Cache, :key => "value"
|
31
|
+
RestClient.enabled?(Rack::Cache).should be_true
|
32
|
+
RestClient.components.first.should == [Rack::Cache, [{:key => "value"}]]
|
33
|
+
RestClient.enable Rack::CommonLogger
|
34
|
+
RestClient.components.length.should == 3
|
35
|
+
RestClient.components.first.should == [Rack::CommonLogger, []]
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "usage" do
|
39
|
+
before do
|
40
|
+
|
41
|
+
@expected_args = {:url=>"http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b", :method=>:get, :headers=>{:additional_header=>"whatever"}}
|
42
|
+
@expected_request = RestClient::Request.new(@expected_args)
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "internal" do
|
46
|
+
it "should call the backend (bypassing the cache) if the requested resource is not in the cache" do
|
47
|
+
@expected_request.should_receive(:original_execute).and_return(
|
48
|
+
mock('rest-client response',
|
49
|
+
:headers => {:content_type => "text/plain, */*", :date => "Mon, 04 Jan 2010 13:37:18 GMT"},
|
50
|
+
:code => 200,
|
51
|
+
:to_s => 'body'))
|
52
|
+
status, header, body = Rack::Lint.new(Rack::CommonLogger.new(Rack::Cache.new(RestClient::RACK_APP))).call(@env.merge(
|
53
|
+
'restclient.request' => @expected_request
|
54
|
+
))
|
55
|
+
status.should == 200
|
56
|
+
header.should == {"Content-Type"=>"text/plain, */*", "X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:37:18 GMT"}
|
57
|
+
content = ""
|
58
|
+
body.each{|part| content << part}
|
59
|
+
content.should == "body"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return a 304 not modified response if the call to the backend returned a 304 not modified response" do
|
63
|
+
@expected_request.should_receive(:original_execute).and_raise(RestClient::NotModified.new(@mock_304_net_http_response))
|
64
|
+
status, header, body = Rack::Lint.new(Rack::Cache.new(RestClient::RACK_APP)).call(@env.merge(
|
65
|
+
'restclient.request' => @expected_request
|
66
|
+
))
|
67
|
+
status.should == 304
|
68
|
+
header.should == {"X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:42:43 GMT", "Header1"=>"value1"} # restclient only returns the first member of each header
|
69
|
+
content = ""
|
70
|
+
body.each{|part| content<<part}
|
71
|
+
content.should == "body"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "external" do
|
76
|
+
before do
|
77
|
+
RestClient.enable Rack::Cache, :key => "value"
|
78
|
+
@rack_app = RestClient::RACK_APP
|
79
|
+
@rack_app_after_cache = Rack::Cache.new(@rack_app)
|
80
|
+
@rack_app_after_composition = RestClient::Rack::Compatibility.new(@rack_app_after_cache)
|
81
|
+
RestClient::Request.should_receive(:new).once.with(@expected_args).and_return(@expected_request)
|
82
|
+
Rack::Cache.should_receive(:new).with(@rack_app, :key => "value").and_return(@rack_app_after_cache)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should pass through the components [using RestClient::Resource instance methods]" do
|
86
|
+
RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
|
87
|
+
resource = RestClient::Resource.new('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b')
|
88
|
+
@rack_app_after_composition.should_receive(:call).with(
|
89
|
+
hash_including( {'HTTP_ADDITIONAL_HEADER' => 'whatever', "restclient.request" => @expected_request })
|
90
|
+
)
|
91
|
+
resource.get(:additional_header=>"whatever")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should pass through the components [using RestClient class methods]" do
|
95
|
+
RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
|
96
|
+
@rack_app_after_composition.should_receive(:call).with(
|
97
|
+
hash_including( {'HTTP_ADDITIONAL_HEADER' => 'whatever', "restclient.request" => @expected_request })
|
98
|
+
)
|
99
|
+
RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return a RestClient response" do
|
103
|
+
RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
|
104
|
+
@rack_app.should_receive(:call).and_return(
|
105
|
+
[200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
|
106
|
+
)
|
107
|
+
response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
|
108
|
+
response.should be_a(RestClient::Response)
|
109
|
+
response.code.should == 200
|
110
|
+
response.headers.should == {:content_type=>"text/plain", :x_rack_cache=>"miss", :content_length=>"13", :allow => "GET, POST", :date => "Mon, 04 Jan 2010 13:37:18 GMT"}
|
111
|
+
response.to_s.should == "response body"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should return a response following the rack spec, if the compatibility component is disabled" do
|
115
|
+
RestClient.disable RestClient::Rack::Compatibility
|
116
|
+
@rack_app.should_receive(:call).and_return(
|
117
|
+
[200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
|
118
|
+
)
|
119
|
+
response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
|
120
|
+
response.should be_a(Array)
|
121
|
+
code, header, body = response
|
122
|
+
code.should == 200
|
123
|
+
header.should == {"X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:37:18 GMT", "Content-Type"=>"text/plain", "Content-Length"=>"13", "Allow"=>"GET, POST"}
|
124
|
+
body.should == ["response body"]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "RestClient Exceptions" do
|
129
|
+
before do
|
130
|
+
RestClient::Request.should_receive(:new).once.with(@expected_args).and_return(@expected_request)
|
131
|
+
@mock_resource_not_found_net_http_response = mock("net http response", :code => 404, :to_hash => {'header1' => ['value1']}, :body => "Not Found")
|
132
|
+
end
|
133
|
+
describe "with compatibility component" do
|
134
|
+
it "should still raise the RestClient exceptions" do
|
135
|
+
@expected_request.should_receive(:original_execute).and_raise(RestClient::Exception.new("error"))
|
136
|
+
lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::Exception)
|
137
|
+
end
|
138
|
+
it "should still raise the RestClient exceptions with message" do
|
139
|
+
@expected_request.should_receive(:original_execute).and_raise(RestClient::ResourceNotFound.new(@mock_resource_not_found_net_http_response))
|
140
|
+
lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::ResourceNotFound)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
describe "without compatibility component" do
|
144
|
+
it "should still raise the RestClient high-level exceptions" do
|
145
|
+
RestClient.components.clear
|
146
|
+
@expected_request.should_receive(:original_execute).and_raise(RestClient::Exception.new("error"))
|
147
|
+
lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::Exception)
|
148
|
+
end
|
149
|
+
it "should not raise the RestClient exceptions with response" do
|
150
|
+
RestClient.components.clear
|
151
|
+
@expected_request.should_receive(:original_execute).and_raise(RestClient::ResourceNotFound.new(@mock_resource_not_found_net_http_response))
|
152
|
+
response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
|
153
|
+
response.should be_a(Array)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
describe RestClient::Rack::Compatibility do
|
161
|
+
it "should transform a Rack response into a RestClient Response" do
|
162
|
+
fake_app = Proc.new{|env|
|
163
|
+
[200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
|
164
|
+
}
|
165
|
+
response = RestClient::Rack::Compatibility.new(fake_app).call(@env)
|
166
|
+
response.should be_a(RestClient::Response)
|
167
|
+
response.code.should == 200
|
168
|
+
response.headers.should == {:content_type=>"text/plain", :content_length=>"13", :allow => "GET, POST", :date => "Mon, 04 Jan 2010 13:37:18 GMT"}
|
169
|
+
response.to_s.should == "response body"
|
170
|
+
end
|
171
|
+
it "should raise RestClient Exceptions if restclient.error exists" do
|
172
|
+
fake_app = Proc.new{|env|
|
173
|
+
raise RestClient::Exception, "error"
|
174
|
+
}
|
175
|
+
lambda{RestClient::Rack::Compatibility.new(fake_app).call(@env)}.should raise_error(RestClient::Exception)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rest-client-components
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cyril Rohr
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-10 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rest-client
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rack
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.1
|
34
|
+
version:
|
35
|
+
description: RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.
|
36
|
+
email: cyril.rohr@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- LICENSE
|
46
|
+
- README.rdoc
|
47
|
+
- Rakefile
|
48
|
+
- VERSION
|
49
|
+
- examples/caching.rb
|
50
|
+
- examples/parsing.rb
|
51
|
+
- lib/restclient/components.rb
|
52
|
+
- rest-client-components.gemspec
|
53
|
+
- spec/components_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/crohr/rest-client-components
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options:
|
61
|
+
- --charset=UTF-8
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.5
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.
|
83
|
+
test_files:
|
84
|
+
- spec/components_spec.rb
|
85
|
+
- spec/spec_helper.rb
|
86
|
+
- examples/caching.rb
|
87
|
+
- examples/parsing.rb
|