cacheability 1.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.
@@ -0,0 +1,3 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 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 ADDED
@@ -0,0 +1,41 @@
1
+ Cacheability
2
+ ============
3
+ A gem that makes client-side caching of HTTP requests a no-brainer. It is built upon the Rack:Cache gem from Ryan Tomayko.
4
+
5
+ Cached data can be stored in heap, file or memcached. See the Rack::Cache documentation (http://tomayko.com/src/rack-cache/) for more information.
6
+
7
+ Installation
8
+ ============
9
+ gem install cryx-cacheability
10
+
11
+ Usage
12
+ =====
13
+ require 'cacheability/restclient'
14
+ resource = RestClient::CacheableResource.new( 'http://some/cacheable/resource',
15
+ :cache => { :metastore => 'file:/tmp/cache/meta',
16
+ :entitystore => 'file:/tmp/cache/body' }
17
+ )
18
+ resource.get # get from remote server, and cache if possible
19
+ # ...
20
+ resource.get # get from cache, if still fresh.
21
+ # ...
22
+ resource.get(:cache_control => 'no-cache') # explicitly tells to bypass the cache, requires rack-cache >= 0.4
23
+ # ...
24
+
25
+ Do yourself a favor and read:
26
+ * the HTTP specification related to HTTP caching - http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
27
+ * Things Caches Do - http://tomayko.com/writings/things-caches-do
28
+
29
+
30
+ Supported libraries
31
+ ===================
32
+ * rest-client > 0.9
33
+
34
+ Dependencies
35
+ ============
36
+ * rack-cache
37
+
38
+ COPYRIGHT
39
+ =========
40
+
41
+ Copyright (c) 2008 Cyril Rohr. See LICENSE for details.
@@ -0,0 +1,79 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "cacheability"
7
+ s.summary = %Q{A gem that makes client-side caching of HTTP requests a no-brainer. Replace your calls to RestClient::Resource.new with RestClient::CacheableResource.new and the caching is taken care of for you ! Supports heap, file and memcache storage.}
8
+ s.email = "cyril.rohr@irisa.fr"
9
+ s.homepage = "http://github.com/cryx/cacheability"
10
+ s.description = "Transparent caching for your HTTP requests (heap, file, memcache). Built-in support for RestClient. Built upon Rack::Cache."
11
+ s.authors = ["Cyril Rohr"]
12
+ s.add_dependency "rack-cache"
13
+ s.rubyforge_project = 'cacheability'
14
+ end
15
+
16
+ Jeweler::RubyforgeTasks.new do |rubyforge|
17
+ rubyforge.doc_task = "rdoc"
18
+ end
19
+ rescue LoadError
20
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
21
+ end
22
+
23
+
24
+ begin
25
+ require 'rake/contrib/sshpublisher'
26
+ namespace :rubyforge do
27
+
28
+ desc "Release gem and RDoc documentation to RubyForge"
29
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
30
+
31
+ namespace :release do
32
+ desc "Publish RDoc to RubyForge."
33
+ task :docs => [:rdoc] do
34
+ config = YAML.load(
35
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
36
+ )
37
+
38
+ host = "#{config['username']}@rubyforge.org"
39
+ remote_dir = "/var/www/gforge-projects/cacheability/"
40
+ local_dir = 'rdoc'
41
+
42
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
43
+ end
44
+ end
45
+ end
46
+ rescue LoadError
47
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
48
+ end
49
+
50
+ require 'rake/rdoctask'
51
+ Rake::RDocTask.new do |rdoc|
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = 'cacheability'
54
+ rdoc.options << '--line-numbers' << '--inline-source'
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
58
+
59
+ require 'spec/rake/spectask'
60
+ Spec::Rake::SpecTask.new(:spec) do |t|
61
+ t.libs << 'lib' << 'spec'
62
+ t.spec_files = FileList['spec/**/*_spec.rb']
63
+ end
64
+
65
+ Spec::Rake::SpecTask.new(:rcov) do |t|
66
+ t.libs << 'lib' << 'spec'
67
+ t.spec_files = FileList['spec/**/*_spec.rb']
68
+ t.rcov = true
69
+ end
70
+
71
+ begin
72
+ require 'cucumber/rake/task'
73
+ Cucumber::Rake::Task.new(:features)
74
+ rescue LoadError
75
+ puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
76
+ end
77
+
78
+
79
+ task :default => :spec
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 1
4
+ :patch: 0
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{cacheability}
8
+ s.version = "1.1.0"
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{2009-08-07}
13
+ s.description = %q{Transparent caching for your HTTP requests (heap, file, memcache). Built-in support for RestClient. Built upon Rack::Cache.}
14
+ s.email = %q{cyril.rohr@irisa.fr}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README",
23
+ "Rakefile",
24
+ "VERSION.yml",
25
+ "cacheability.gemspec",
26
+ "lib/cacheability.rb",
27
+ "lib/cacheability/restclient.rb",
28
+ "spec/cacheability_spec.rb",
29
+ "spec/spec_helper.rb"
30
+ ]
31
+ s.has_rdoc = true
32
+ s.homepage = %q{http://github.com/cryx/cacheability}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubyforge_project = %q{cacheability}
36
+ s.rubygems_version = %q{1.3.2}
37
+ s.summary = %q{A gem that makes client-side caching of HTTP requests a no-brainer. Replace your calls to RestClient::Resource.new with RestClient::CacheableResource.new and the caching is taken care of for you ! Supports heap, file and memcache storage.}
38
+ s.test_files = [
39
+ "spec/cacheability_spec.rb",
40
+ "spec/spec_helper.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<rack-cache>, [">= 0"])
49
+ else
50
+ s.add_dependency(%q<rack-cache>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<rack-cache>, [">= 0"])
54
+ end
55
+ end
File without changes
@@ -0,0 +1,92 @@
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, io = rack_response
11
+ @body = ""
12
+ io.each{|block| @body << block}
13
+ io.close if io.respond_to?(:close)
14
+ end
15
+
16
+ def to_hash
17
+ @headers.inject({}) {|out, (key, value)|
18
+ # In Net::HTTP, headers values are arrays
19
+ out[key] = value.split(", ")
20
+ out
21
+ }
22
+ end
23
+ end
24
+
25
+ class CacheableResource < Resource
26
+ attr_reader :cache
27
+ CACHE_DEFAULT_OPTIONS = {:verbose => true}.freeze
28
+
29
+ def initialize(*args)
30
+ super(*args)
31
+ # rack-cache creates a singleton, so that there is only one instance of the cache at any time
32
+ @cache = Rack::Cache.new(self, options[:cache] || CACHE_DEFAULT_OPTIONS)
33
+ end
34
+
35
+ def get(additional_headers = {}, pass_through_cache = true)
36
+ if pass_through_cache && cache
37
+ uri = URI.parse(url)
38
+ uri_path_split = uri.path.split("/")
39
+ path_info = (last_part = uri_path_split.pop) ? "/"+last_part : ""
40
+ script_name = uri_path_split.join("/")
41
+ # minimal rack spec
42
+ env = {
43
+ "REQUEST_METHOD" => 'GET',
44
+ "SCRIPT_NAME" => script_name,
45
+ "PATH_INFO" => path_info,
46
+ "QUERY_STRING" => uri.query,
47
+ "SERVER_NAME" => uri.host,
48
+ "SERVER_PORT" => uri.port.to_s,
49
+ "rack.version" => Rack::VERSION,
50
+ "rack.run_once" => false,
51
+ "rack.multithread" => true,
52
+ "rack.multiprocess" => true,
53
+ "rack.url_scheme" => uri.scheme,
54
+ "rack.input" => StringIO.new,
55
+ "rack.errors" => StringIO.new # Rack-Cache writes errors into this field
56
+ }
57
+ debeautify_headers(additional_headers).each do |key, value|
58
+ env.merge!("HTTP_"+key.to_s.gsub("-", "_").upcase => value)
59
+ end
60
+ response = MockHTTPResponse.new(cache.call(env))
61
+ RestClient::Response.new(response.body, response)
62
+ else
63
+ super(additional_headers)
64
+ end
65
+ end
66
+
67
+ def debeautify_headers(headers = {})
68
+ headers.inject({}) do |out, (key, value)|
69
+ out[key.to_s.gsub(/_/, '-')] = value
70
+ out
71
+ end
72
+ end
73
+
74
+ # Follows the SPEC of Rack 1.0: the returned body is always an array of string
75
+ #
76
+ def call(env)
77
+ http_headers = env.inject({}) do |out, (header, value)|
78
+ if header =~ /HTTP_/
79
+ out[header.gsub("HTTP_", '')] = value unless value.nil? || value.empty?
80
+ end
81
+ out
82
+ end
83
+ response = get(debeautify_headers(http_headers), pass_through_cache=false)
84
+ response.headers.delete(:x_content_digest) # don't know why, but it seems to make the validation fail if kept...
85
+ [response.code, debeautify_headers( response.headers ), [response.to_s]]
86
+ rescue RestClient::NotModified => e
87
+ # e is a Net::HTTPResponse
88
+ response = RestClient::Response.new("", e.response)
89
+ [304, debeautify_headers( response.headers ), [response.to_s]]
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'restclient'
3
+ require File.dirname(__FILE__) + '/../lib/cacheability/restclient'
4
+
5
+
6
+ describe RestClient::CacheableResource do
7
+ require File.dirname(__FILE__) + '/../lib/cacheability/restclient'
8
+ before do
9
+ @cache_options = {:metastore => 'file:/tmp/cache/meta', :entitystore => 'file:/tmp/cache/body'}
10
+ end
11
+
12
+ it "should instantiate the cache at init" do
13
+ mock_cache = mock('rack-cache instance')
14
+ Rack::Cache.should_receive(:new).with(
15
+ RestClient::CacheableResource.new('http://domain.tld:8888/some/cacheable/resource', :cache => @cache_options),
16
+ @cache_options
17
+ ).and_return(mock_cache)
18
+ end
19
+
20
+ describe "correctly instantiated" do
21
+ before do
22
+ @mock_cache = mock('rack-cache singleton')
23
+ @mock_rack_errors = mock('string io')
24
+ @mock_304_net_http_response = mock('http response', :code => 304, :body => "", :to_hash => {'header1' => ['value1', 'value2']})
25
+ Rack::Cache.stub!(:new).and_return(@mock_cache)
26
+ @env = {
27
+ 'REQUEST_METHOD' => 'GET',
28
+ "SCRIPT_NAME" => '/some/cacheable',
29
+ "PATH_INFO" => '/resource',
30
+ "QUERY_STRING" => 'q1=a&q2=b',
31
+ "SERVER_NAME" => 'domain.tld',
32
+ "SERVER_PORT" => '8888'
33
+ }
34
+ @resource = RestClient::CacheableResource.new('http://domain.tld:8888/some/cacheable/resource', :cache => @cache_options)
35
+ end
36
+
37
+ it "should pass through the cache" do
38
+ @resource.cache.should_receive(:call).with( hash_including( @env.merge({'HTTP_ADDITIONAL_HEADER' => 'whatever'}) ) ).and_return([200, {}, "response body"])
39
+ @resource['/?q1=a&q2=b'].get(:additional_header => 'whatever')
40
+ end
41
+
42
+ it "should bypass the cache if pass_through_cache argument is false" do
43
+ @resource = RestClient::CacheableResource.new('http://domain.tld:8888/some/cacheable/resource', :cache => @cache_options)
44
+ @resource.cache.should_not_receive(:call)
45
+ @resource.get({:additional_header => 'whatever'}, false) rescue nil # don't know how to spec the call to super()
46
+ end
47
+
48
+ it "should call the backend (bypassing the cache) if the requested resource is not in the cache" do
49
+ @resource.should_receive(:get).with({'ADDITIONAL-HEADER' => 'whatever'}, false).and_return(mock('rest-client response', :headers => {}, :code => 200, :to_s => 'body'))
50
+ response = @resource.call(@env.merge({'HTTP_ADDITIONAL_HEADER' => 'whatever'}))
51
+ response.should == [200, {}, ["body"]]
52
+ end
53
+
54
+ it "should return a 304 not modified response if the call to the backend returned a 304 not modified response" do
55
+ @resource.should_receive(:get).with({'ADDITIONAL-HEADER' => 'whatever'}, false).and_raise(RestClient::NotModified.new(@mock_304_net_http_response))
56
+ response = @resource.call(@env.merge({'HTTP_ADDITIONAL_HEADER' => 'whatever'}))
57
+ response.should == [304, {"header1"=>"value1"}, [""]]
58
+ end
59
+
60
+ it "should render a RestClient::Response even when the data is coming from the cache" do
61
+ @resource.cache.should_receive(:call).and_return([200, {'ADDITIONAL_HEADER' => 'whatever'}, "body"])
62
+ response = @resource.get({'HTTP_ADDITIONAL_HEADER' => 'whatever'}, true)
63
+ response.should be_is_a(RestClient::Response)
64
+ response.code.should == 200
65
+ response.headers.should == {:ADDITIONAL_HEADER => 'whatever'}
66
+ response.to_s.should == "body"
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cacheability
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Cyril Rohr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-07 00:00:00 +02: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: Transparent caching for your HTTP requests (heap, file, memcache). Built-in support for RestClient. Built upon Rack::Cache.
26
+ email: cyril.rohr@irisa.fr
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README
34
+ files:
35
+ - .gitignore
36
+ - LICENSE
37
+ - README
38
+ - Rakefile
39
+ - VERSION.yml
40
+ - cacheability.gemspec
41
+ - lib/cacheability.rb
42
+ - lib/cacheability/restclient.rb
43
+ - spec/cacheability_spec.rb
44
+ - spec/spec_helper.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/cryx/cacheability
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project: cacheability
69
+ rubygems_version: 1.3.2
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: A gem that makes client-side caching of HTTP requests a no-brainer. Replace your calls to RestClient::Resource.new with RestClient::CacheableResource.new and the caching is taken care of for you ! Supports heap, file and memcache storage.
73
+ test_files:
74
+ - spec/cacheability_spec.rb
75
+ - spec/spec_helper.rb