rtomayko-rack-cache 0.3.9 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +5 -3
- data/README +18 -8
- data/Rakefile +1 -14
- data/TODO +1 -6
- data/doc/faq.markdown +8 -0
- data/doc/index.markdown +7 -7
- data/lib/rack/cache/context.rb +5 -4
- data/lib/rack/cache/options.rb +41 -36
- data/rack-cache.gemspec +2 -3
- data/test/cache_test.rb +6 -6
- data/test/context_test.rb +53 -0
- data/test/spec_setup.rb +1 -0
- metadata +2 -3
- data/doc/events.dot +0 -27
data/CHANGES
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
## 0.4.0 /
|
1
|
+
## 0.4.0 / March 2009
|
2
2
|
|
3
3
|
* Ruby 1.9.1 / Rack 1.0 compatible.
|
4
4
|
|
@@ -9,7 +9,8 @@
|
|
9
9
|
|
10
10
|
* Implement "Cache-Control: max-age=N" request directive by forcing
|
11
11
|
validation when the max-age provided exceeds the age of the cache
|
12
|
-
entry.
|
12
|
+
entry. This can be disabled by setting the "allow_revalidate" option to
|
13
|
+
false.
|
13
14
|
|
14
15
|
* Properly implement "Cache-Control: no-cache" request directive by
|
15
16
|
performing a full reload. RFC 2616 states that when "no-cache" is
|
@@ -19,7 +20,8 @@
|
|
19
20
|
first validate its entry with the origin. Previously, we implemented
|
20
21
|
"no-cache" on requests by passing so no new cache entry would be stored
|
21
22
|
based on the response. Now we treat it as a forced miss and enter the
|
22
|
-
response into the cache if it's cacheable.
|
23
|
+
response into the cache if it's cacheable. This can be disabled by
|
24
|
+
setting the "allow_reload" option to false.
|
23
25
|
|
24
26
|
* Assume identical semantics for the "Pragma: no-cache" request header
|
25
27
|
as the "Cache-Control: no-cache" directive described above.
|
data/README
CHANGED
@@ -24,14 +24,6 @@ caching solution for small to medium sized deployments. More sophisticated /
|
|
24
24
|
high-performance caching systems (e.g., Varnish, Squid, httpd/mod-cache) may be
|
25
25
|
more appropriate for large deployments with significant throughput requirements.
|
26
26
|
|
27
|
-
Status
|
28
|
-
------
|
29
|
-
|
30
|
-
Rack::Cache is a young and experimental project that is likely to change
|
31
|
-
substantially and may not be wholly functional, consistent, fast, or correct.
|
32
|
-
The current focus is on reaching basic compliance with RFC 2616 and providing
|
33
|
-
good documentation.
|
34
|
-
|
35
27
|
Installation
|
36
28
|
------------
|
37
29
|
|
@@ -65,6 +57,24 @@ Assuming you've designed your backend application to take advantage of HTTP's
|
|
65
57
|
caching features, no further code or configuration is required for basic
|
66
58
|
caching.
|
67
59
|
|
60
|
+
Using with Rails
|
61
|
+
----------------
|
62
|
+
|
63
|
+
Add this to your `config/environment.rb`:
|
64
|
+
|
65
|
+
config.middleware.use Rack::Cache,
|
66
|
+
:verbose => true,
|
67
|
+
:metastore => 'file:/var/cache/rack/meta',
|
68
|
+
:entitystore => 'file:/var/cache/rack/body'
|
69
|
+
|
70
|
+
You should now see `Rack::Cache` listed in the middleware pipeline:
|
71
|
+
|
72
|
+
rake middleware
|
73
|
+
|
74
|
+
See the following for more information:
|
75
|
+
|
76
|
+
http://snippets.aktagon.com/snippets/302
|
77
|
+
|
68
78
|
Links
|
69
79
|
-----
|
70
80
|
|
data/Rakefile
CHANGED
@@ -31,7 +31,7 @@ end
|
|
31
31
|
|
32
32
|
# DOC =======================================================================
|
33
33
|
desc 'Build all documentation'
|
34
|
-
task :doc => %w[doc:api doc:
|
34
|
+
task :doc => %w[doc:api doc:markdown]
|
35
35
|
|
36
36
|
# requires the hanna gem:
|
37
37
|
# gem install mislav-hanna --source=http://gems.github.com
|
@@ -55,19 +55,6 @@ file 'doc/api/index.html' => FileList['lib/**/*.rb'] do |f|
|
|
55
55
|
end
|
56
56
|
CLEAN.include 'doc/api'
|
57
57
|
|
58
|
-
desc 'Build graphviz graphs'
|
59
|
-
task 'doc:graphs'
|
60
|
-
%w[pdf png svg].each do |filetype|
|
61
|
-
FileList["doc/*.dot"].each do |source|
|
62
|
-
dest = source.sub(/dot$/, filetype)
|
63
|
-
file dest => source do |f|
|
64
|
-
sh "dot -T#{filetype} #{source} -o #{f.name}"
|
65
|
-
end
|
66
|
-
task 'doc:graphs' => dest
|
67
|
-
CLEAN.include dest
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
58
|
desc 'Build markdown documentation files'
|
72
59
|
task 'doc:markdown'
|
73
60
|
FileList['doc/*.markdown'].each do |source|
|
data/TODO
CHANGED
@@ -1,13 +1,8 @@
|
|
1
|
-
##
|
1
|
+
## Backlog
|
2
2
|
|
3
3
|
- Move breakers.rb configuration file into rack-contrib as a middleware
|
4
4
|
component.
|
5
|
-
- Add docs on using Rack::Cache with Rails 2.3 or link to one of the
|
6
|
-
existing tutorials on this.
|
7
5
|
- Sample apps: Rack, Rails, Sinatra, Merb, etc.
|
8
|
-
|
9
|
-
## Backlog
|
10
|
-
|
11
6
|
- Use Bacon instead of test/spec
|
12
7
|
- Work with both memcache and memcached gems (memcached hasn't built on MacOS
|
13
8
|
for some time now).
|
data/doc/faq.markdown
CHANGED
@@ -10,6 +10,14 @@ General
|
|
10
10
|
-------
|
11
11
|
|
12
12
|
|
13
|
+
<a class='hash' id='rails' href='#rails'>#</a>
|
14
|
+
|
15
|
+
### Q: Can I use Rack::Cache with Rails?
|
16
|
+
|
17
|
+
Rack::Cache can be used with Rails 2.3 or above. Documentation and a
|
18
|
+
sample application is forthcoming; in the mean time, see
|
19
|
+
[this example of using Rack::Cache with Rails 2.3](http://snippets.aktagon.com/snippets/302-How-to-setup-and-use-Rack-Cache-with-Rails-2-3-0-RC-1).
|
20
|
+
|
13
21
|
<a class='hash' id='why-not-squid' href='#why-not-squid'>#</a>
|
14
22
|
|
15
23
|
### Q: Why Rack::Cache? Why not Squid, Varnish, Perlbol, etc.?
|
data/doc/index.markdown
CHANGED
@@ -9,13 +9,13 @@ for [Rack][]-based applications that produce freshness (`Expires`,
|
|
9
9
|
* Portable: 100% Ruby / works with any [Rack][]-enabled framework.
|
10
10
|
* Disk, memcached, and heap memory [storage backends][storage].
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
News
|
13
|
+
----
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
* [How to use Rack::Cache with Rails 2.3](http://snippets.aktagon.com/snippets/302-How-to-setup-and-use-Rack-Cache-with-Rails-2-3-0-RC-1) - it's really easy.
|
16
|
+
* [RailsLab's Advanced HTTP Caching Screencast](http://railslab.newrelic.com/2009/02/26/episode-11-advanced-http-caching)
|
17
|
+
is a really great review of HTTP caching concepts and shows how to
|
18
|
+
use Rack::Cache with Rails.
|
19
19
|
|
20
20
|
Installation
|
21
21
|
------------
|
@@ -51,7 +51,7 @@ caching.
|
|
51
51
|
More
|
52
52
|
----
|
53
53
|
|
54
|
-
* [Configuration
|
54
|
+
* [Configuration Options][config] - how to set cache options.
|
55
55
|
|
56
56
|
* [Cache Storage Documentation][storage] - detailed information on the various
|
57
57
|
storage implementations available in __Rack::Cache__ and how to choose the one
|
data/lib/rack/cache/context.rb
CHANGED
@@ -15,11 +15,12 @@ module Rack::Cache
|
|
15
15
|
# The Rack application object immediately downstream.
|
16
16
|
attr_reader :backend
|
17
17
|
|
18
|
-
def initialize(backend, options={}
|
18
|
+
def initialize(backend, options={})
|
19
19
|
@backend = backend
|
20
20
|
@trace = []
|
21
|
+
|
21
22
|
initialize_options options
|
22
|
-
|
23
|
+
yield self if block_given?
|
23
24
|
|
24
25
|
@private_header_keys =
|
25
26
|
private_headers.map { |name| "HTTP_#{name.upcase.tr('-', '_')}" }
|
@@ -109,7 +110,7 @@ module Rack::Cache
|
|
109
110
|
# Whether the cache entry is "fresh enough" to satisfy the request.
|
110
111
|
def fresh_enough?(entry)
|
111
112
|
if entry.fresh?
|
112
|
-
if max_age = @request.cache_control.max_age
|
113
|
+
if allow_revalidate? && max_age = @request.cache_control.max_age
|
113
114
|
max_age > 0 && max_age >= entry.age
|
114
115
|
else
|
115
116
|
true
|
@@ -143,7 +144,7 @@ module Rack::Cache
|
|
143
144
|
# stale, attempt to #validate the entry with the backend using conditional
|
144
145
|
# GET. When no matching cache entry is found, trigger #miss processing.
|
145
146
|
def lookup
|
146
|
-
if @request.no_cache?
|
147
|
+
if @request.no_cache? && allow_reload?
|
147
148
|
record :reload
|
148
149
|
fetch
|
149
150
|
elsif entry = metastore.lookup(@request, entitystore)
|
data/lib/rack/cache/options.rb
CHANGED
@@ -7,20 +7,22 @@ module Rack::Cache
|
|
7
7
|
# uses the Rack Environment to store option values. All options documented
|
8
8
|
# below are stored in the Rack Environment as "rack-cache.<option>", where
|
9
9
|
# <option> is the option name.
|
10
|
-
#
|
11
|
-
# The #set method can be used to configure a option values. When #set is
|
12
|
-
# called outside of request scope, the value applies to all requests; when
|
13
|
-
# called from within a request context, applies only to the request being
|
14
|
-
# processed.
|
15
10
|
module Options
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
def self.option_accessor(key)
|
12
|
+
name = option_name(key)
|
13
|
+
define_method(key) { || options[name] }
|
14
|
+
define_method("#{key}=") { |value| options[name] = value }
|
15
|
+
define_method("#{key}?") { || !! options[name] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def option_name(key)
|
19
|
+
case key
|
20
|
+
when Symbol ; "rack-cache.#{key}"
|
21
|
+
when String ; key
|
22
|
+
else raise ArgumentError
|
22
23
|
end
|
23
24
|
end
|
25
|
+
module_function :option_name
|
24
26
|
|
25
27
|
# Enable verbose trace logging. This option is currently enabled by
|
26
28
|
# default but is likely to be disabled in a future release.
|
@@ -57,9 +59,9 @@ module Rack::Cache
|
|
57
59
|
# end
|
58
60
|
option_accessor :cache_key
|
59
61
|
|
60
|
-
# A URI specifying the entity-store
|
61
|
-
# response bodies. See the metastore option for information on
|
62
|
-
# schemes.
|
62
|
+
# A URI specifying the entity-store implementation that should be used to
|
63
|
+
# store response bodies. See the metastore option for information on
|
64
|
+
# supported URI schemes.
|
63
65
|
#
|
64
66
|
# If no entity store is specified the 'heap:/' store is assumed. This
|
65
67
|
# implementation has significant draw-backs so explicit configuration is
|
@@ -83,6 +85,16 @@ module Rack::Cache
|
|
83
85
|
# Default: ['Authorization', 'Cookie']
|
84
86
|
option_accessor :private_headers
|
85
87
|
|
88
|
+
# Specifies whether the client can force a cache reload by including a
|
89
|
+
# Cache-Control "no-cache" directive in the request. This is enabled by
|
90
|
+
# default for compliance with RFC 2616.
|
91
|
+
option_accessor :allow_reload
|
92
|
+
|
93
|
+
# Specifies whether the client can force a cache revalidate by including
|
94
|
+
# a Cache-Control "max-age=0" directive in the request. This is enabled by
|
95
|
+
# default for compliance with RFC 2616.
|
96
|
+
option_accessor :allow_revalidate
|
97
|
+
|
86
98
|
# The underlying options Hash. During initialization (or outside of a
|
87
99
|
# request), this is a default values Hash. During a request, this is the
|
88
100
|
# Rack environment Hash. The default values Hash is merged in underneath
|
@@ -112,6 +124,21 @@ module Rack::Cache
|
|
112
124
|
end
|
113
125
|
|
114
126
|
private
|
127
|
+
def initialize_options(options={})
|
128
|
+
@default_options = {
|
129
|
+
'rack-cache.cache_key' => Key,
|
130
|
+
'rack-cache.verbose' => true,
|
131
|
+
'rack-cache.storage' => Rack::Cache::Storage.instance,
|
132
|
+
'rack-cache.metastore' => 'heap:/',
|
133
|
+
'rack-cache.entitystore' => 'heap:/',
|
134
|
+
'rack-cache.default_ttl' => 0,
|
135
|
+
'rack-cache.private_headers' => ['Authorization', 'Cookie'],
|
136
|
+
'rack-cache.allow_reload' => true,
|
137
|
+
'rack-cache.allow_revalidate' => true
|
138
|
+
}
|
139
|
+
self.options = options
|
140
|
+
end
|
141
|
+
|
115
142
|
def read_option(key)
|
116
143
|
options[option_name(key)]
|
117
144
|
end
|
@@ -119,27 +146,5 @@ module Rack::Cache
|
|
119
146
|
def write_option(key, value)
|
120
147
|
options[option_name(key)] = value
|
121
148
|
end
|
122
|
-
|
123
|
-
def option_name(key)
|
124
|
-
case key
|
125
|
-
when Symbol ; "rack-cache.#{key}"
|
126
|
-
when String ; key
|
127
|
-
else raise ArgumentError
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
private
|
132
|
-
def initialize_options(options={})
|
133
|
-
@default_options = {
|
134
|
-
'rack-cache.cache_key' => Key,
|
135
|
-
'rack-cache.verbose' => true,
|
136
|
-
'rack-cache.storage' => Rack::Cache::Storage.instance,
|
137
|
-
'rack-cache.metastore' => 'heap:/',
|
138
|
-
'rack-cache.entitystore' => 'heap:/',
|
139
|
-
'rack-cache.default_ttl' => 0,
|
140
|
-
'rack-cache.private_headers' => ['Authorization', 'Cookie']
|
141
|
-
}
|
142
|
-
self.options = options
|
143
|
-
end
|
144
149
|
end
|
145
150
|
end
|
data/rack-cache.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'rack-cache'
|
6
|
-
s.version = '0.
|
7
|
-
s.date = '2009-03-
|
6
|
+
s.version = '0.4'
|
7
|
+
s.date = '2009-03-16'
|
8
8
|
|
9
9
|
s.description = "HTTP Caching for Rack"
|
10
10
|
s.summary = "HTTP Caching for Rack"
|
@@ -20,7 +20,6 @@ Gem::Specification.new do |s|
|
|
20
20
|
Rakefile
|
21
21
|
TODO
|
22
22
|
doc/configuration.markdown
|
23
|
-
doc/events.dot
|
24
23
|
doc/faq.markdown
|
25
24
|
doc/index.markdown
|
26
25
|
doc/layout.html.erb
|
data/test/cache_test.rb
CHANGED
@@ -25,14 +25,14 @@ describe 'Rack::Cache::new' do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'takes a block; executes it during initialization' do
|
28
|
-
state,
|
29
|
-
|
30
|
-
Rack::Cache.new @app do
|
31
|
-
|
28
|
+
state, object = 'not invoked', nil
|
29
|
+
instance =
|
30
|
+
Rack::Cache.new @app do |cache|
|
31
|
+
object = cache
|
32
32
|
state = 'invoked'
|
33
|
-
should.respond_to :set
|
33
|
+
cache.should.respond_to :set
|
34
34
|
end
|
35
35
|
state.should.equal 'invoked'
|
36
|
-
object.should.be
|
36
|
+
object.should.be instance
|
37
37
|
end
|
38
38
|
end
|
data/test/context_test.rb
CHANGED
@@ -148,6 +148,31 @@ describe 'Rack::Cache::Context' do
|
|
148
148
|
cache.trace.should.include :store
|
149
149
|
end
|
150
150
|
|
151
|
+
it 'does not reload responses when allow_reload is set false' do
|
152
|
+
count = 0
|
153
|
+
respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res|
|
154
|
+
count+= 1
|
155
|
+
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
156
|
+
end
|
157
|
+
|
158
|
+
get '/'
|
159
|
+
response.should.be.ok
|
160
|
+
response.body.should.equal 'Hello World'
|
161
|
+
cache.trace.should.include :store
|
162
|
+
|
163
|
+
get '/'
|
164
|
+
response.should.be.ok
|
165
|
+
response.body.should.equal 'Hello World'
|
166
|
+
cache.trace.should.include :fresh
|
167
|
+
|
168
|
+
get '/',
|
169
|
+
'rack-cache.allow_reload' => false,
|
170
|
+
'HTTP_CACHE_CONTROL' => 'no-cache'
|
171
|
+
response.should.be.ok
|
172
|
+
response.body.should.equal 'Hello World'
|
173
|
+
cache.trace.should.not.include :reload
|
174
|
+
end
|
175
|
+
|
151
176
|
it 'revalidates fresh cache entry when max-age request directive is exceeded' do
|
152
177
|
count = 0
|
153
178
|
respond_with do |req,res|
|
@@ -175,6 +200,34 @@ describe 'Rack::Cache::Context' do
|
|
175
200
|
cache.trace.should.include :store
|
176
201
|
end
|
177
202
|
|
203
|
+
it 'does not revalidate fresh cache entry when enable_revalidate option is set false' do
|
204
|
+
count = 0
|
205
|
+
respond_with do |req,res|
|
206
|
+
count+= 1
|
207
|
+
res['Cache-Control'] = 'max-age=10000'
|
208
|
+
res['ETag'] = count.to_s
|
209
|
+
res.body = (count == 1) ? ['Hello World'] : ['Goodbye World']
|
210
|
+
end
|
211
|
+
|
212
|
+
get '/'
|
213
|
+
response.should.be.ok
|
214
|
+
response.body.should.equal 'Hello World'
|
215
|
+
cache.trace.should.include :store
|
216
|
+
|
217
|
+
get '/'
|
218
|
+
response.should.be.ok
|
219
|
+
response.body.should.equal 'Hello World'
|
220
|
+
cache.trace.should.include :fresh
|
221
|
+
|
222
|
+
get '/',
|
223
|
+
'rack-cache.allow_revalidate' => false,
|
224
|
+
'HTTP_CACHE_CONTROL' => 'max-age=0'
|
225
|
+
response.should.be.ok
|
226
|
+
response.body.should.equal 'Hello World'
|
227
|
+
cache.trace.should.not.include :stale
|
228
|
+
cache.trace.should.not.include :invalid
|
229
|
+
cache.trace.should.include :fresh
|
230
|
+
end
|
178
231
|
it 'fetches response from backend when cache misses' do
|
179
232
|
respond_with 200, 'Expires' => (Time.now + 5).httpdate
|
180
233
|
get '/'
|
data/test/spec_setup.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rtomayko-rack-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: "0.4"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Tomayko
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-16 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -40,7 +40,6 @@ files:
|
|
40
40
|
- Rakefile
|
41
41
|
- TODO
|
42
42
|
- doc/configuration.markdown
|
43
|
-
- doc/events.dot
|
44
43
|
- doc/faq.markdown
|
45
44
|
- doc/index.markdown
|
46
45
|
- doc/layout.html.erb
|
data/doc/events.dot
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
digraph cache_logic {
|
2
|
-
nodesep=1.25;
|
3
|
-
center=true;
|
4
|
-
|
5
|
-
node[fontname="Lucida Sans Unicode",labelloc=c,margin=0.10,0.03]
|
6
|
-
edge[fontname="Lucida Sans Unicode",fontcolor="#444444",labeldistance=20];
|
7
|
-
|
8
|
-
receive -> pass[label="uncacheable request",color=grey];
|
9
|
-
receive -> lookup[label="cacheable request"];
|
10
|
-
|
11
|
-
pass -> deliver[label="",color=grey];
|
12
|
-
|
13
|
-
lookup -> hit[label="fresh"];
|
14
|
-
lookup -> fetch[label="stale (needs validation)"];
|
15
|
-
lookup -> miss[label="uncached"];
|
16
|
-
|
17
|
-
hit -> deliver[label="sizzling"];
|
18
|
-
hit -> pass[label="bailing...",color=grey];
|
19
|
-
|
20
|
-
miss -> fetch[label=""];
|
21
|
-
miss -> pass[color=grey];
|
22
|
-
|
23
|
-
fetch -> store[label="cacheable"];
|
24
|
-
fetch -> deliver[label="not cacheable",color=grey];
|
25
|
-
|
26
|
-
store -> deliver[label="KTHX"];
|
27
|
-
}
|