redis-rack-cache 0.0.0 → 1.1.rc
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/.gitignore +1 -0
- data/Gemfile +2 -0
- data/MIT-LICENSE +20 -0
- data/README.md +54 -0
- data/Rakefile +13 -1
- data/lib/rack/cache/redis_entitystore.rb +55 -0
- data/lib/rack/cache/redis_metastore.rb +45 -0
- data/lib/redis-rack-cache/version.rb +2 -2
- data/lib/redis-rack-cache.rb +5 -9
- data/redis-rack-cache.gemspec +8 -3
- data/test/rack/.DS_Store +0 -0
- data/test/rack/cache/.DS_Store +0 -0
- data/test/rack/cache/entitystore/pony.jpg +0 -0
- data/test/rack/cache/entitystore/redis_test.rb +121 -0
- data/test/rack/cache/metastore/redis_test.rb +266 -0
- data/test/redis-rack-cache/version_test.rb +7 -0
- data/test/test_helper.rb +6 -0
- metadata +147 -15
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 - 2011 Luca Guidi
|
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.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Redis stores for Rack::Cache
|
2
|
+
|
3
|
+
__`redis-rack-cache`__ provides a Redis backed store for __Rack::Cache__. It natively supports object marshalling, timeouts, single or multiple nodes and namespaces.
|
4
|
+
|
5
|
+
## Redis Installation
|
6
|
+
|
7
|
+
### Option 1: Homebrew
|
8
|
+
|
9
|
+
MacOS X users should use [Homebrew](https://github.com/mxcl/homebrew) to install Redis:
|
10
|
+
|
11
|
+
brew install redis
|
12
|
+
|
13
|
+
### Option 2: From Source
|
14
|
+
|
15
|
+
Download and install Redis from [http://redis.io](http://redis.io/)
|
16
|
+
|
17
|
+
wget http://redis.googlecode.com/files/redis-2.4.5.tar.gz
|
18
|
+
tar -zxf redis-2.4.5.tar.gz
|
19
|
+
mv redis-2.4.5 redis
|
20
|
+
cd redis
|
21
|
+
make
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
# Gemfile
|
26
|
+
gem 'redis-rack-cache'
|
27
|
+
|
28
|
+
### HTTP Cache Store:
|
29
|
+
|
30
|
+
# config.ru
|
31
|
+
require 'rack'
|
32
|
+
require 'rack/cache'
|
33
|
+
require 'redis-rack-cache'
|
34
|
+
|
35
|
+
use Rack::Cache,
|
36
|
+
:metastore => 'redis://localhost:6379/0/metastore',
|
37
|
+
:entitystore => 'redis://localhost:6380/0/entitystore'
|
38
|
+
|
39
|
+
#### Configuration
|
40
|
+
|
41
|
+
For advanced configuration options, please check the [Redis Store Wiki](https://github.com/jodosha/redis-store/wiki).
|
42
|
+
|
43
|
+
## Running tests
|
44
|
+
|
45
|
+
git clone git://github.com/jodosha/redis-store.git
|
46
|
+
cd redis-store/redis-rack-cache
|
47
|
+
gem install bundler --pre # required version: 1.1.rc
|
48
|
+
bundle exec rake
|
49
|
+
|
50
|
+
If you are on **Snow Leopard** you have to run `env ARCHFLAGS="-arch x86_64" bundle exec rake`
|
51
|
+
|
52
|
+
## Copyright
|
53
|
+
|
54
|
+
(c) 2009 - 2011 Luca Guidi - [http://lucaguidi.com](http://lucaguidi.com), released under the MIT license
|
data/Rakefile
CHANGED
@@ -1 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
require 'rake'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'rdoc/task'
|
8
|
+
rescue LoadError
|
9
|
+
require 'rake/rdoctask'
|
10
|
+
end
|
11
|
+
|
12
|
+
load 'tasks/redis.tasks.rb'
|
13
|
+
task :default => 'redis:test:suite'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rack/cache/entitystore'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Cache
|
5
|
+
class EntityStore
|
6
|
+
class RedisBase < self
|
7
|
+
# The underlying ::Redis instance used to communicate with the Redis daemon.
|
8
|
+
attr_reader :cache
|
9
|
+
|
10
|
+
extend Rack::Utils
|
11
|
+
|
12
|
+
def open(key)
|
13
|
+
data = read(key)
|
14
|
+
data && [data]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.resolve(uri)
|
18
|
+
new ::Redis::Factory.resolve(uri.to_s)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Redis < RedisBase
|
23
|
+
def initialize(server, options = {})
|
24
|
+
@cache = ::Redis.new server
|
25
|
+
end
|
26
|
+
|
27
|
+
def exist?(key)
|
28
|
+
cache.exists key
|
29
|
+
end
|
30
|
+
|
31
|
+
def read(key)
|
32
|
+
cache.get key
|
33
|
+
end
|
34
|
+
|
35
|
+
def write(body, ttl=0)
|
36
|
+
buf = StringIO.new
|
37
|
+
key, size = slurp(body){|part| buf.write(part) }
|
38
|
+
|
39
|
+
if ttl.zero?
|
40
|
+
[key, size] if cache.set(key, buf.string)
|
41
|
+
else
|
42
|
+
[key, size] if cache.setex(key, ttl, buf.string)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def purge(key)
|
47
|
+
cache.del key
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
REDIS = Redis
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'rack/cache/key'
|
4
|
+
require 'rack/cache/metastore'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module Cache
|
8
|
+
class MetaStore
|
9
|
+
class RedisBase < self
|
10
|
+
extend Rack::Utils
|
11
|
+
|
12
|
+
# The Redis::Store object used to communicate with the Redis daemon.
|
13
|
+
attr_reader :cache
|
14
|
+
|
15
|
+
def self.resolve(uri)
|
16
|
+
new ::Redis::Factory.resolve(uri.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Redis < RedisBase
|
21
|
+
# The Redis instance used to communicated with the Redis daemon.
|
22
|
+
attr_reader :cache
|
23
|
+
|
24
|
+
def initialize(server, options = {})
|
25
|
+
@cache = ::Redis::Factory.create(server)
|
26
|
+
end
|
27
|
+
|
28
|
+
def read(key)
|
29
|
+
cache.get(hexdigest(key)) || []
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(key, entries)
|
33
|
+
cache.set(hexdigest(key), entries)
|
34
|
+
end
|
35
|
+
|
36
|
+
def purge(key)
|
37
|
+
cache.del(hexdigest(key))
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
REDIS = Redis
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/redis-rack-cache.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# Your code goes here...
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
1
|
+
require 'redis-store'
|
2
|
+
require 'rack/cache'
|
3
|
+
require 'rack/cache/redis_entitystore'
|
4
|
+
require 'rack/cache/redis_metastore'
|
5
|
+
require 'redis-rack-cache/version'
|
data/redis-rack-cache.gemspec
CHANGED
@@ -18,7 +18,12 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
s.add_dependency 'redis-store', '1.1.0.rc'
|
22
|
+
s.add_dependency 'rack-cache', '1.1'
|
23
|
+
|
24
|
+
s.add_development_dependency 'rake', '~> 0.9.2.2'
|
25
|
+
s.add_development_dependency 'bundler', '~> 1.1.rc'
|
26
|
+
s.add_development_dependency 'mocha', '~> 0.10.0'
|
27
|
+
s.add_development_dependency 'minitest', '~> 2.8.0'
|
28
|
+
s.add_development_dependency 'purdytest', '~> 1.0.0'
|
24
29
|
end
|
data/test/rack/.DS_Store
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Object
|
4
|
+
def sha_like?
|
5
|
+
length == 40 && self =~ /^[0-9a-z]+$/
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Rack::Cache::EntityStore::Redis do
|
10
|
+
before do
|
11
|
+
@store = ::Rack::Cache::EntityStore::Redis.new :host => 'localhost'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has the class referenced by homonym constant' do
|
15
|
+
::Rack::Cache::EntityStore::REDIS.must_equal(::Rack::Cache::EntityStore::Redis)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'resolves the connection uri' do
|
19
|
+
cache = ::Rack::Cache::EntityStore::Redis.resolve(uri("redis://127.0.0.1")).cache
|
20
|
+
cache.must_be_kind_of(::Redis)
|
21
|
+
cache.id.must_equal("redis://127.0.0.1:6379/0")
|
22
|
+
|
23
|
+
cache = ::Rack::Cache::EntityStore::Redis.resolve(uri("redis://127.0.0.1:6380")).cache
|
24
|
+
cache.id.must_equal("redis://127.0.0.1:6380/0")
|
25
|
+
|
26
|
+
cache = ::Rack::Cache::EntityStore::Redis.resolve(uri("redis://127.0.0.1/13")).cache
|
27
|
+
cache.id.must_equal("redis://127.0.0.1:6379/13")
|
28
|
+
|
29
|
+
cache = ::Rack::Cache::EntityStore::Redis.resolve(uri("redis://:secret@127.0.0.1")).cache
|
30
|
+
cache.id.must_equal("redis://127.0.0.1:6379/0")
|
31
|
+
cache.client.password.must_equal('secret')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'responds to all required messages' do
|
35
|
+
%w[read open write exist?].each do |message|
|
36
|
+
@store.must_respond_to message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'stores bodies with #write' do
|
41
|
+
key, size = @store.write(['My wild love went riding,'])
|
42
|
+
key.wont_be_nil
|
43
|
+
key.must_be :sha_like?
|
44
|
+
|
45
|
+
data = @store.read(key)
|
46
|
+
data.must_equal('My wild love went riding,')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'takes a ttl parameter for #write' do
|
50
|
+
key, size = @store.write(['My wild love went riding,'], 0)
|
51
|
+
key.wont_be_nil
|
52
|
+
key.must_be :sha_like?
|
53
|
+
|
54
|
+
data = @store.read(key)
|
55
|
+
data.must_equal('My wild love went riding,')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'correctly determines whether cached body exists for key with #exist?' do
|
59
|
+
key, size = @store.write(['She rode to the devil,'])
|
60
|
+
assert @store.exist?(key)
|
61
|
+
assert ! @store.exist?('938jasddj83jasdh4438021ksdfjsdfjsdsf')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'can read data written with #write' do
|
65
|
+
key, size = @store.write(['And asked him to pay.'])
|
66
|
+
data = @store.read(key)
|
67
|
+
data.must_equal('And asked him to pay.')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'gives a 40 character SHA1 hex digest from #write' do
|
71
|
+
key, size = @store.write(['she rode to the sea;'])
|
72
|
+
key.wont_be_nil
|
73
|
+
key.length.must_equal(40)
|
74
|
+
key.must_match(/^[0-9a-z]+$/)
|
75
|
+
key.must_equal('90a4c84d51a277f3dafc34693ca264531b9f51b6')
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'returns the entire body as a String from #read' do
|
79
|
+
key, size = @store.write(['She gathered together'])
|
80
|
+
@store.read(key).must_equal('She gathered together')
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'returns nil from #read when key does not exist' do
|
84
|
+
@store.read('87fe0a1ae82a518592f6b12b0183e950b4541c62').must_be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns a Rack compatible body from #open' do
|
88
|
+
key, size = @store.write(['Some shells for her hair.'])
|
89
|
+
body = @store.open(key)
|
90
|
+
body.must_respond_to :each
|
91
|
+
buf = ''
|
92
|
+
body.each { |part| buf << part }
|
93
|
+
buf.must_equal('Some shells for her hair.')
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns nil from #open when key does not exist' do
|
97
|
+
@store.open('87fe0a1ae82a518592f6b12b0183e950b4541c62').must_be_nil
|
98
|
+
end
|
99
|
+
|
100
|
+
if RUBY_VERSION < '1.9'
|
101
|
+
it 'can store largish bodies with binary data' do
|
102
|
+
pony = File.open(File.dirname(__FILE__) + '/pony.jpg', 'rb') { |f| f.read }
|
103
|
+
key, size = @store.write([pony])
|
104
|
+
key.must_equal('d0f30d8659b4d268c5c64385d9790024c2d78deb')
|
105
|
+
data = @store.read(key)
|
106
|
+
data.length.must_equal(pony.length)
|
107
|
+
data.hash.must_equal(pony.hash)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'deletes stored entries with #purge' do
|
112
|
+
key, size = @store.write(['My wild love went riding,'])
|
113
|
+
@store.purge(key).must_be_nil
|
114
|
+
@store.read(key).must_be_nil
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
define_method :uri do |uri|
|
119
|
+
URI.parse uri
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Rack::Cache::MetaStore::Redis do
|
4
|
+
before do
|
5
|
+
@store = ::Rack::Cache::MetaStore::Redis.resolve uri('redis://127.0.0.1')
|
6
|
+
@entity_store = ::Rack::Cache::EntityStore::Redis.resolve uri('redis://127.0.0.1:6380')
|
7
|
+
@request = mock_request('/', {})
|
8
|
+
@response = mock_response(200, {}, ['hello world'])
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
@store.cache.flushall
|
13
|
+
@entity_store.cache.flushall
|
14
|
+
end
|
15
|
+
|
16
|
+
it "has the class referenced by homonym constant" do
|
17
|
+
::Rack::Cache::MetaStore::REDIS.must_equal(::Rack::Cache::MetaStore::Redis)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "instantiates the store" do
|
21
|
+
@store.must_be_kind_of(::Rack::Cache::MetaStore::Redis)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "resolves the connection uri" do
|
25
|
+
cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1")).cache
|
26
|
+
cache.must_be_kind_of(::Redis::Store)
|
27
|
+
cache.to_s.must_equal("Redis Client connected to 127.0.0.1:6379 against DB 0")
|
28
|
+
|
29
|
+
cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1:6380")).cache
|
30
|
+
cache.to_s.must_equal("Redis Client connected to 127.0.0.1:6380 against DB 0")
|
31
|
+
|
32
|
+
cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1/13")).cache
|
33
|
+
cache.to_s.must_equal("Redis Client connected to 127.0.0.1:6379 against DB 13")
|
34
|
+
|
35
|
+
cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://:secret@127.0.0.1")).cache
|
36
|
+
cache.id.must_equal("redis://127.0.0.1:6379/0")
|
37
|
+
cache.client.password.must_equal('secret')
|
38
|
+
end
|
39
|
+
|
40
|
+
# Low-level implementation methods ===========================================
|
41
|
+
|
42
|
+
it 'writes a list of negotation tuples with #write' do
|
43
|
+
# lambda {
|
44
|
+
@store.write('/test', [[{}, {}]])
|
45
|
+
# }.wont_raise Exception
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'reads a list of negotation tuples with #read' do
|
49
|
+
@store.write('/test', [[{},{}],[{},{}]])
|
50
|
+
tuples = @store.read('/test')
|
51
|
+
tuples.must_equal([ [{},{}], [{},{}] ])
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'reads an empty list with #read when nothing cached at key' do
|
55
|
+
@store.read('/nothing').must_be_empty
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'removes entries for key with #purge' do
|
59
|
+
@store.write('/test', [[{},{}]])
|
60
|
+
@store.read('/test').wont_be_empty
|
61
|
+
|
62
|
+
@store.purge('/test')
|
63
|
+
@store.read('/test').must_be_empty
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'succeeds when purging non-existing entries' do
|
67
|
+
@store.read('/test').must_be_empty
|
68
|
+
@store.purge('/test')
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns nil from #purge' do
|
72
|
+
@store.write('/test', [[{},{}]])
|
73
|
+
@store.purge('/test').must_be_nil
|
74
|
+
@store.read('/test').must_equal([])
|
75
|
+
end
|
76
|
+
|
77
|
+
%w[/test http://example.com:8080/ /test?x=y /test?x=y&p=q].each do |key|
|
78
|
+
it "can read and write key: '#{key}'" do
|
79
|
+
# lambda {
|
80
|
+
@store.write(key, [[{},{}]])
|
81
|
+
# }.wont_raise Exception
|
82
|
+
@store.read(key).must_equal([[{},{}]])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "can read and write fairly large keys" do
|
87
|
+
key = "b" * 4096
|
88
|
+
# lambda {
|
89
|
+
@store.write(key, [[{},{}]])
|
90
|
+
# }.wont_raise Exception
|
91
|
+
@store.read(key).must_equal([[{},{}]])
|
92
|
+
end
|
93
|
+
|
94
|
+
it "allows custom cache keys from block" do
|
95
|
+
request = mock_request('/test', {})
|
96
|
+
request.env['rack-cache.cache_key'] =
|
97
|
+
lambda { |request| request.path_info.reverse }
|
98
|
+
@store.cache_key(request).must_equal('tset/')
|
99
|
+
end
|
100
|
+
|
101
|
+
it "allows custom cache keys from class" do
|
102
|
+
request = mock_request('/test', {})
|
103
|
+
request.env['rack-cache.cache_key'] = Class.new do
|
104
|
+
def self.call(request); request.path_info.reverse end
|
105
|
+
end
|
106
|
+
@store.cache_key(request).must_equal('tset/')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'does not blow up when given a non-marhsalable object with an ALL_CAPS key' do
|
110
|
+
store_simple_entry('/bad', { 'SOME_THING' => Proc.new {} })
|
111
|
+
end
|
112
|
+
|
113
|
+
# Abstract methods ===========================================================
|
114
|
+
|
115
|
+
it 'stores a cache entry' do
|
116
|
+
cache_key = store_simple_entry
|
117
|
+
@store.read(cache_key).wont_be_empty
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'sets the X-Content-Digest response header before storing' do
|
121
|
+
cache_key = store_simple_entry
|
122
|
+
req, res = @store.read(cache_key).first
|
123
|
+
res['X-Content-Digest'].must_equal('a94a8fe5ccb19ba61c4c0873d391e987982fbbd3')
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'finds a stored entry with #lookup' do
|
127
|
+
store_simple_entry
|
128
|
+
response = @store.lookup(@request, @entity_store)
|
129
|
+
response.wont_be_nil
|
130
|
+
response.must_be_kind_of(Rack::Cache::Response)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'does not find an entry with #lookup when none exists' do
|
134
|
+
req = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
135
|
+
@store.lookup(req, @entity_store).must_be_nil
|
136
|
+
end
|
137
|
+
|
138
|
+
it "canonizes urls for cache keys" do
|
139
|
+
store_simple_entry(path='/test?x=y&p=q')
|
140
|
+
|
141
|
+
hits_req = mock_request(path, {})
|
142
|
+
miss_req = mock_request('/test?p=x', {})
|
143
|
+
|
144
|
+
@store.lookup(hits_req, @entity_store).wont_be_nil
|
145
|
+
@store.lookup(miss_req, @entity_store).must_be_nil
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'does not find an entry with #lookup when the body does not exist' do
|
149
|
+
store_simple_entry
|
150
|
+
@response.headers['X-Content-Digest'].wont_be_nil
|
151
|
+
@entity_store.purge(@response.headers['X-Content-Digest'])
|
152
|
+
@store.lookup(@request, @entity_store).must_be_nil
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'restores response headers properly with #lookup' do
|
156
|
+
store_simple_entry
|
157
|
+
response = @store.lookup(@request, @entity_store)
|
158
|
+
response.headers.
|
159
|
+
must_equal(@response.headers.merge('Content-Length' => '4'))
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'restores response body from entity store with #lookup' do
|
163
|
+
store_simple_entry
|
164
|
+
response = @store.lookup(@request, @entity_store)
|
165
|
+
body = '' ; response.body.each {|p| body << p}
|
166
|
+
body.must_equal('test')
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'invalidates meta and entity store entries with #invalidate' do
|
170
|
+
store_simple_entry
|
171
|
+
@store.invalidate(@request, @entity_store)
|
172
|
+
response = @store.lookup(@request, @entity_store)
|
173
|
+
response.must_be_kind_of(Rack::Cache::Response)
|
174
|
+
response.wont_be :fresh?
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'succeeds quietly when #invalidate called with no matching entries' do
|
178
|
+
req = mock_request('/test', {})
|
179
|
+
@store.invalidate(req, @entity_store)
|
180
|
+
@store.lookup(@request, @entity_store).must_be_nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# Vary =======================================================================
|
184
|
+
|
185
|
+
it 'does not return entries that Vary with #lookup' do
|
186
|
+
req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
187
|
+
req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
|
188
|
+
res = mock_response(200, {'Vary' => 'Foo Bar'}, ['test'])
|
189
|
+
@store.store(req1, res, @entity_store)
|
190
|
+
|
191
|
+
@store.lookup(req2, @entity_store).must_be_nil
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'stores multiple responses for each Vary combination' do
|
195
|
+
req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
196
|
+
res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
|
197
|
+
key = @store.store(req1, res1, @entity_store)
|
198
|
+
|
199
|
+
req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
|
200
|
+
res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
|
201
|
+
@store.store(req2, res2, @entity_store)
|
202
|
+
|
203
|
+
req3 = mock_request('/test', {'HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom'})
|
204
|
+
res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
|
205
|
+
@store.store(req3, res3, @entity_store)
|
206
|
+
|
207
|
+
slurp(@store.lookup(req3, @entity_store).body).must_equal('test 3')
|
208
|
+
slurp(@store.lookup(req1, @entity_store).body).must_equal('test 1')
|
209
|
+
slurp(@store.lookup(req2, @entity_store).body).must_equal('test 2')
|
210
|
+
|
211
|
+
@store.read(key).length.must_equal(3)
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'overwrites non-varying responses with #store' do
|
215
|
+
req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
216
|
+
res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
|
217
|
+
key = @store.store(req1, res1, @entity_store)
|
218
|
+
slurp(@store.lookup(req1, @entity_store).body).must_equal('test 1')
|
219
|
+
|
220
|
+
req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
|
221
|
+
res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
|
222
|
+
@store.store(req2, res2, @entity_store)
|
223
|
+
slurp(@store.lookup(req2, @entity_store).body).must_equal('test 2')
|
224
|
+
|
225
|
+
req3 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
|
226
|
+
res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
|
227
|
+
@store.store(req3, res3, @entity_store)
|
228
|
+
slurp(@store.lookup(req1, @entity_store).body).must_equal('test 3')
|
229
|
+
|
230
|
+
@store.read(key).length.must_equal(2)
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
define_method :mock_request do |uri, opts|
|
235
|
+
env = Rack::MockRequest.env_for(uri, opts || {})
|
236
|
+
Rack::Cache::Request.new(env)
|
237
|
+
end
|
238
|
+
|
239
|
+
define_method :mock_response do |status, headers, body|
|
240
|
+
headers ||= {}
|
241
|
+
body = Array(body).compact
|
242
|
+
Rack::Cache::Response.new(status, headers, body)
|
243
|
+
end
|
244
|
+
|
245
|
+
define_method :slurp do |body|
|
246
|
+
buf = ''
|
247
|
+
body.each { |part| buf << part }
|
248
|
+
buf
|
249
|
+
end
|
250
|
+
|
251
|
+
# Stores an entry for the given request args, returns a url encoded cache key
|
252
|
+
# for the request.
|
253
|
+
define_method :store_simple_entry do |*request_args|
|
254
|
+
path, headers = request_args
|
255
|
+
@request = mock_request(path || '/test', headers || {})
|
256
|
+
@response = mock_response(200, {'Cache-Control' => 'max-age=420'}, ['test'])
|
257
|
+
body = @response.body
|
258
|
+
cache_key = @store.store(@request, @response, @entity_store)
|
259
|
+
@response.body.must_equal(body)
|
260
|
+
cache_key
|
261
|
+
end
|
262
|
+
|
263
|
+
define_method :uri do |uri|
|
264
|
+
URI.parse uri
|
265
|
+
end
|
266
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-rack-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 7712070
|
5
|
+
prerelease: 4
|
6
6
|
segments:
|
7
|
-
-
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version:
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
- rc
|
10
|
+
version: 1.1.rc
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Luca Guidi
|
@@ -15,9 +15,121 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
19
|
-
dependencies:
|
20
|
-
|
18
|
+
date: 2011-12-30 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: redis-store
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - "="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 7712002
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- rc
|
34
|
+
version: 1.1.0.rc
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rack-cache
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - "="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 13
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 1
|
49
|
+
version: "1.1"
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rake
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 11
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
- 9
|
64
|
+
- 2
|
65
|
+
- 2
|
66
|
+
version: 0.9.2.2
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 7712070
|
78
|
+
segments:
|
79
|
+
- 1
|
80
|
+
- 1
|
81
|
+
- rc
|
82
|
+
version: 1.1.rc
|
83
|
+
type: :development
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: mocha
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 55
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
- 10
|
97
|
+
- 0
|
98
|
+
version: 0.10.0
|
99
|
+
type: :development
|
100
|
+
version_requirements: *id005
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: minitest
|
103
|
+
prerelease: false
|
104
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 47
|
110
|
+
segments:
|
111
|
+
- 2
|
112
|
+
- 8
|
113
|
+
- 0
|
114
|
+
version: 2.8.0
|
115
|
+
type: :development
|
116
|
+
version_requirements: *id006
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: purdytest
|
119
|
+
prerelease: false
|
120
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 23
|
126
|
+
segments:
|
127
|
+
- 1
|
128
|
+
- 0
|
129
|
+
- 0
|
130
|
+
version: 1.0.0
|
131
|
+
type: :development
|
132
|
+
version_requirements: *id007
|
21
133
|
description: Redis for Rack::Cache
|
22
134
|
email:
|
23
135
|
- guidi.luca@gmail.com
|
@@ -30,10 +142,21 @@ extra_rdoc_files: []
|
|
30
142
|
files:
|
31
143
|
- .gitignore
|
32
144
|
- Gemfile
|
145
|
+
- MIT-LICENSE
|
146
|
+
- README.md
|
33
147
|
- Rakefile
|
148
|
+
- lib/rack/cache/redis_entitystore.rb
|
149
|
+
- lib/rack/cache/redis_metastore.rb
|
34
150
|
- lib/redis-rack-cache.rb
|
35
151
|
- lib/redis-rack-cache/version.rb
|
36
152
|
- redis-rack-cache.gemspec
|
153
|
+
- test/rack/.DS_Store
|
154
|
+
- test/rack/cache/.DS_Store
|
155
|
+
- test/rack/cache/entitystore/pony.jpg
|
156
|
+
- test/rack/cache/entitystore/redis_test.rb
|
157
|
+
- test/rack/cache/metastore/redis_test.rb
|
158
|
+
- test/redis-rack-cache/version_test.rb
|
159
|
+
- test/test_helper.rb
|
37
160
|
homepage: http://jodosha.github.com/redis-store
|
38
161
|
licenses: []
|
39
162
|
|
@@ -54,12 +177,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
178
|
none: false
|
56
179
|
requirements:
|
57
|
-
- - "
|
180
|
+
- - ">"
|
58
181
|
- !ruby/object:Gem::Version
|
59
|
-
hash:
|
182
|
+
hash: 25
|
60
183
|
segments:
|
61
|
-
-
|
62
|
-
|
184
|
+
- 1
|
185
|
+
- 3
|
186
|
+
- 1
|
187
|
+
version: 1.3.1
|
63
188
|
requirements: []
|
64
189
|
|
65
190
|
rubyforge_project: redis-rack-cache
|
@@ -67,5 +192,12 @@ rubygems_version: 1.8.6
|
|
67
192
|
signing_key:
|
68
193
|
specification_version: 3
|
69
194
|
summary: Redis for Rack::Cache
|
70
|
-
test_files:
|
71
|
-
|
195
|
+
test_files:
|
196
|
+
- test/rack/.DS_Store
|
197
|
+
- test/rack/cache/.DS_Store
|
198
|
+
- test/rack/cache/entitystore/pony.jpg
|
199
|
+
- test/rack/cache/entitystore/redis_test.rb
|
200
|
+
- test/rack/cache/metastore/redis_test.rb
|
201
|
+
- test/redis-rack-cache/version_test.rb
|
202
|
+
- test/test_helper.rb
|
203
|
+
has_rdoc:
|