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 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
+