cryx-cacheability 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 0
4
+ :major: 0
@@ -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
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
@@ -0,0 +1,9 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+
5
+ require File.dirname(__FILE__) + '/../lib/cacheability'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
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
+