cryx-cacheability 0.1.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/VERSION.yml +4 -0
- data/lib/cacheability/restclient.rb +74 -0
- data/lib/cacheability.rb +0 -0
- data/spec/cacheability_spec.rb +65 -0
- data/spec/spec_helper.rb +9 -0
- metadata +68 -0
data/VERSION.yml
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
require 'rack/cache'
|
3
|
+
|
4
|
+
module RestClient
|
5
|
+
# A class that mocks the behaviour of a Net::HTTPResponse class.
|
6
|
+
# It is required since RestClient::Response must be initialized with a class that responds to :code and :to_hash.
|
7
|
+
class MockHTTPResponse
|
8
|
+
attr_reader :code, :headers, :body
|
9
|
+
def initialize(rack_response)
|
10
|
+
@code, @headers, @body = rack_response
|
11
|
+
end
|
12
|
+
def to_hash
|
13
|
+
@headers
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class CacheableResource < Resource
|
18
|
+
attr_reader :cache
|
19
|
+
CACHE_DEFAULT_OPTIONS = {}.freeze
|
20
|
+
|
21
|
+
def initialize(*args)
|
22
|
+
super(*args)
|
23
|
+
# rack-cache creates a singleton, so that there is only one instance of the cache at any time
|
24
|
+
@cache = Rack::Cache.new(self, options[:cache] || CACHE_DEFAULT_OPTIONS)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(additional_headers = {}, pass_through_cache = true)
|
28
|
+
if pass_through_cache && cache
|
29
|
+
uri = URI.parse(url)
|
30
|
+
uri_path_split = uri.path.split("/")
|
31
|
+
path_info = (last_part = uri_path_split.pop) ? "/"+last_part : ""
|
32
|
+
script_name = uri_path_split.join("/")
|
33
|
+
# minimal rack spec
|
34
|
+
env = {
|
35
|
+
"REQUEST_METHOD" => 'GET',
|
36
|
+
"SCRIPT_NAME" => script_name,
|
37
|
+
"PATH_INFO" => path_info,
|
38
|
+
"QUERY_STRING" => uri.query,
|
39
|
+
"SERVER_NAME" => uri.host,
|
40
|
+
"SERVER_PORT" => uri.port.to_s
|
41
|
+
}
|
42
|
+
debeautify_headers(additional_headers).each do |key, value|
|
43
|
+
env.merge!("HTTP_"+key.to_s.gsub("-", "_").upcase => value)
|
44
|
+
end
|
45
|
+
response = MockHTTPResponse.new(cache.call(env))
|
46
|
+
RestClient::Response.new(response.body.to_s, response)
|
47
|
+
else
|
48
|
+
super(additional_headers)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def debeautify_headers(headers = {})
|
53
|
+
headers.inject({}) do |out, (key, value)|
|
54
|
+
out[key.to_s.gsub(/_/, '-')] = value
|
55
|
+
out
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def call(env)
|
60
|
+
http_headers = env.inject({}) do |out, (header, value)|
|
61
|
+
if header =~ /HTTP_/
|
62
|
+
out[header.gsub("HTTP_", '')] = value unless value.nil? || value.empty?
|
63
|
+
end
|
64
|
+
out
|
65
|
+
end
|
66
|
+
response = get(debeautify_headers(http_headers), pass_through_cache=false)
|
67
|
+
response.headers.delete(:x_content_digest) # don't know why, but it seems to make the validation fail if kept...
|
68
|
+
[response.code, debeautify_headers( response.headers ), response.to_s]
|
69
|
+
rescue RestClient::NotModified
|
70
|
+
# should be modified to include response headers when the rest-client gem is up to date
|
71
|
+
[304, {}, ""]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/cacheability.rb
ADDED
File without changes
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'restclient'
|
3
|
+
|
4
|
+
describe RestClient::CacheableResource do
|
5
|
+
require File.dirname(__FILE__) + '/../lib/cacheability/restclient'
|
6
|
+
before do
|
7
|
+
@cache_options = {:metastore => 'file:/tmp/cache/meta', :entitystore => 'file:/tmp/cache/body'}
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should instantiate the cache at init" do
|
11
|
+
mock_cache = mock('rack-cache instance')
|
12
|
+
Rack::Cache.should_receive(:new).with(
|
13
|
+
RestClient::CacheableResource.new('http://domain.tld:8888/some/cacheable/resource', :cache => @cache_options),
|
14
|
+
@cache_options
|
15
|
+
).and_return(mock_cache)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "correctly instantiated" do
|
19
|
+
before do
|
20
|
+
@mock_cache = mock('rack-cache singleton')
|
21
|
+
Rack::Cache.stub!(:new).and_return(@mock_cache)
|
22
|
+
@env = {
|
23
|
+
'REQUEST_METHOD' => 'GET',
|
24
|
+
"SCRIPT_NAME" => '/some/cacheable',
|
25
|
+
"PATH_INFO" => '/resource',
|
26
|
+
"QUERY_STRING" => 'q1=a&q2=b',
|
27
|
+
"SERVER_NAME" => 'domain.tld',
|
28
|
+
"SERVER_PORT" => '8888'
|
29
|
+
}
|
30
|
+
@resource = RestClient::CacheableResource.new('http://domain.tld:8888/some/cacheable/resource', :cache => @cache_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should pass through the cache" do
|
34
|
+
@resource.cache.should_receive(:call).with( hash_including( @env.merge({'HTTP_ADDITIONAL_HEADER' => 'whatever'}) ) )
|
35
|
+
@resource['/?q1=a&q2=b'].get(:additional_header => 'whatever')
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should bypass the cache if pass_through_cache argument is false" do
|
39
|
+
@resource = RestClient::CacheableResource.new('http://domain.tld:8888/some/cacheable/resource', :cache => @cache_options)
|
40
|
+
@resource.cache.should_not_receive(:call)
|
41
|
+
@resource.get({:additional_header => 'whatever'}, false) rescue nil # don't know how to spec the call to super()
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should call the backend (bypassing the cache) if the requested resource is not in the cache" do
|
45
|
+
@resource.should_receive(:get).with({'ADDITIONAL-HEADER' => 'whatever'}, false).and_return(mock('rest-client response', :headers => {}, :code => 200, :to_s => 'body'))
|
46
|
+
response = @resource.call(@env.merge({'HTTP_ADDITIONAL_HEADER' => 'whatever'}))
|
47
|
+
response.should == [200, {}, "body"]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return a 304 not modified response if the call to the backend returned a 304 not modified response" do
|
51
|
+
@resource.should_receive(:get).with({'ADDITIONAL-HEADER' => 'whatever'}, false).and_raise(RestClient::NotModified)
|
52
|
+
response = @resource.call(@env.merge({'HTTP_ADDITIONAL_HEADER' => 'whatever'}))
|
53
|
+
response.should == [304, {}, ""]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should render a RestClient::Response even when the data is coming from the cache" do
|
57
|
+
@resource.cache.should_receive(:call).and_return([200, {'ADDITIONAL_HEADER' => 'whatever'}, "body"])
|
58
|
+
response = @resource.get({'HTTP_ADDITIONAL_HEADER' => 'whatever'}, true)
|
59
|
+
response.should be_is_a(RestClient::Response)
|
60
|
+
response.code.should == 200
|
61
|
+
response.headers.should == {:ADDITIONAL_HEADER => 'whatever'}
|
62
|
+
response.to_s.should == "body"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cryx-cacheability
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cyril Rohr
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-02 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack-cache
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: TODO
|
26
|
+
email: cyril.rohr@irisa.fr
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- VERSION.yml
|
35
|
+
- lib/cacheability
|
36
|
+
- lib/cacheability/restclient.rb
|
37
|
+
- lib/cacheability.rb
|
38
|
+
- spec/cacheability_spec.rb
|
39
|
+
- spec/spec_helper.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/cryx/cacheability
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --inline-source
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: TODO
|
67
|
+
test_files: []
|
68
|
+
|