rtomayko-rack-cache 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +45 -10
- data/README +14 -9
- data/Rakefile +10 -4
- data/TODO +11 -21
- data/doc/configuration.markdown +8 -0
- data/doc/index.markdown +17 -10
- data/doc/layout.html.erb +1 -0
- data/doc/server.ru +34 -0
- data/lib/rack/cache/config/default.rb +1 -2
- data/lib/rack/cache/core.rb +30 -2
- data/lib/rack/cache/entitystore.rb +29 -6
- data/lib/rack/cache/headers.rb +68 -20
- data/lib/rack/cache/metastore.rb +11 -11
- data/lib/rack/cache/options.rb +11 -1
- data/lib/rack/cache/response.rb +2 -2
- data/rack-cache.gemspec +3 -2
- data/test/cache_test.rb +3 -3
- data/test/context_test.rb +205 -65
- data/test/core_test.rb +8 -8
- data/test/entitystore_test.rb +23 -11
- data/test/environment_headers_test.rb +10 -12
- data/test/headers_test.rb +99 -23
- data/test/metastore_test.rb +27 -17
- data/test/options_test.rb +13 -10
- data/test/spec_setup.rb +13 -7
- metadata +3 -2
data/CHANGES
CHANGED
@@ -1,23 +1,58 @@
|
|
1
|
-
## 0.3.0 /
|
2
|
-
|
3
|
-
*
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
## 0.3.0 / December 2008
|
2
|
+
|
3
|
+
* Add support for public and private cache control directives. Responses
|
4
|
+
marked as explicitly public are cached even when the request includes
|
5
|
+
an Authorization or Cookie header. Responses marked as explicitly private
|
6
|
+
are considered uncacheable.
|
7
|
+
|
8
|
+
* Added a "private_headers" option that dictates which request headers
|
9
|
+
trigger default "private" cache control processing. By default, the
|
10
|
+
Cookie and Authorization headers are included. Headers may be added or
|
11
|
+
removed as necessary to change the default private logic.
|
12
|
+
|
13
|
+
* Adhere to must-revalidate/proxy-revalidate cache control directives by
|
14
|
+
not assigning the default_ttl to responses that don't include freshness
|
15
|
+
information. This should let us begin using default_ttl more liberally
|
16
|
+
since we can control it using the must-revalidate/proxy-revalidate directives.
|
17
|
+
|
18
|
+
* Use the s-maxage Cache-Control value in preference to max-age when
|
19
|
+
present. The ttl= method now sets the s-maxage value instead of max-age.
|
20
|
+
Code that used ttl= to control freshness at the client needs to change
|
21
|
+
to set the max-age directive explicitly.
|
22
|
+
|
23
|
+
* Enable support for X-Sendfile middleware by responding to #to_path on
|
24
|
+
bodies served from disk storage. Adding the Rack::Sendfile component
|
25
|
+
upstream from Rack::Cache will result in cached bodies being served
|
26
|
+
directly by the web server (instead of being read in Ruby).
|
27
|
+
|
28
|
+
* BUG: MetaStore hits but EntityStore misses. This would 500 previously; now
|
29
|
+
we detect it and act as if the MetaStore missed as well.
|
30
|
+
|
31
|
+
* Implement low level #purge method on all concrete entity store
|
32
|
+
classes -- removes the entity body corresponding to the SHA1 key
|
33
|
+
provided and returns nil.
|
34
|
+
|
35
|
+
* Basically sane handling of HEAD requests. A HEAD request is never passed
|
36
|
+
through to the backend except when transitioning with pass!. This means
|
37
|
+
that the cache responds to HEAD requests without invoking the backend at
|
38
|
+
all when the cached entry is fresh. When no cache entry exists, or the
|
39
|
+
cached entry is stale and can be validated, the backend is invoked with
|
40
|
+
a GET request and the HEAD is handled right before the response
|
41
|
+
is delivered upstream.
|
42
|
+
|
11
43
|
* BUG: The Age response header was not being set properly when a stale
|
12
44
|
entry was validated. This would result in Age values that exceeded
|
13
45
|
the freshness lifetime in responses.
|
46
|
+
|
14
47
|
* BUG: A cached entry in a heap meta store could be unintentionally
|
15
48
|
modified by request processing since the cached objects were being
|
16
49
|
returned directly. The result was typically missing/incorrect header
|
17
50
|
values (e.g., missing Content-Type header). [dkubb]
|
51
|
+
|
18
52
|
* BUG: 304 responses should not include entity headers (especially
|
19
53
|
Content-Length). This is causing Safari/WebKit weirdness on 304
|
20
54
|
responses.
|
55
|
+
|
21
56
|
* BUG: The If-None-Match header was being ignored, causing the cache
|
22
57
|
to send 200 responses to matching conditional GET requests.
|
23
58
|
|
data/README
CHANGED
@@ -3,16 +3,14 @@ Rack::Cache
|
|
3
3
|
|
4
4
|
Rack::Cache is suitable as a quick drop-in component to enable HTTP caching for
|
5
5
|
Rack-based applications that produce freshness (Expires, Cache-Control) and/or
|
6
|
-
validation (Last-Modified, ETag) information
|
7
|
-
|
8
|
-
We strive to implement those portions of RFC 2616 Section 13 relevant to gateway
|
9
|
-
(i.e., "reverse proxy") cache scenarios with a system for specifying cache
|
10
|
-
policy:
|
6
|
+
validation (Last-Modified, ETag) information:
|
11
7
|
|
12
8
|
* Standards-based (RFC 2616)
|
13
9
|
* Freshness/expiration based caching
|
14
|
-
* Validation
|
10
|
+
* Validation (If-Modified-Since / If-None-Match)
|
15
11
|
* Vary support
|
12
|
+
* Cache-Control: public, private, max-age, s-maxage, must-revalidate,
|
13
|
+
and proxy-revalidate.
|
16
14
|
* Portable: 100% Ruby / works with any Rack-enabled framework
|
17
15
|
* Configuration language for advanced caching policies
|
18
16
|
* Disk, memcached, and heap memory storage backends
|
@@ -68,10 +66,17 @@ Assuming you've designed your backend application to take advantage of HTTP's
|
|
68
66
|
caching features, no further code or configuration is required for basic
|
69
67
|
caching.
|
70
68
|
|
71
|
-
|
72
|
-
|
69
|
+
Links
|
70
|
+
-----
|
73
71
|
|
74
|
-
|
72
|
+
Documentation:
|
73
|
+
http://tomayko.com/src/rack-cache/
|
74
|
+
|
75
|
+
Mailing List:
|
76
|
+
http://groups.google.com/group/rack-cache
|
77
|
+
|
78
|
+
GitHub:
|
79
|
+
http://github.com/rtomayko/rack-cache/
|
75
80
|
|
76
81
|
License
|
77
82
|
-------
|
data/Rakefile
CHANGED
@@ -21,7 +21,7 @@ end
|
|
21
21
|
desc 'Run specs with unit test style output'
|
22
22
|
task :test => FileList['test/*_test.rb'] do |t|
|
23
23
|
suite = t.prerequisites
|
24
|
-
sh "
|
24
|
+
sh "specrb -Ilib:test #{suite.join(' ')}", :verbose => false
|
25
25
|
end
|
26
26
|
|
27
27
|
desc 'Generate test coverage report'
|
@@ -72,10 +72,10 @@ desc 'Build markdown documentation files'
|
|
72
72
|
task 'doc:markdown'
|
73
73
|
FileList['doc/*.markdown'].each do |source|
|
74
74
|
dest = "doc/#{File.basename(source, '.markdown')}.html"
|
75
|
-
file dest => source do |f|
|
75
|
+
file dest => [source, 'doc/layout.html.erb'] do |f|
|
76
76
|
puts "markdown: #{source} -> #{dest}" if verbose
|
77
|
-
require 'erb'
|
78
|
-
require 'rdiscount'
|
77
|
+
require 'erb' unless defined? ERB
|
78
|
+
require 'rdiscount' unless defined? RDiscount
|
79
79
|
template = File.read(source)
|
80
80
|
content = Markdown.new(ERB.new(template, 0, "%<>").result(binding), :smart).to_html
|
81
81
|
title = content.match("<h1>(.*)</h1>")[1] rescue ''
|
@@ -87,10 +87,16 @@ FileList['doc/*.markdown'].each do |source|
|
|
87
87
|
CLEAN.include dest
|
88
88
|
end
|
89
89
|
|
90
|
+
desc 'Publish documentation'
|
90
91
|
task 'doc:publish' => :doc do
|
91
92
|
sh 'rsync -avz doc/ gus@tomayko.com:/src/rack-cache'
|
92
93
|
end
|
93
94
|
|
95
|
+
desc 'Start the documentation development server (requires thin)'
|
96
|
+
task 'doc:server' do
|
97
|
+
sh 'cd doc && thin --rackup server.ru --port 3035 start'
|
98
|
+
end
|
99
|
+
|
94
100
|
# PACKAGING =================================================================
|
95
101
|
|
96
102
|
def package(ext='')
|
data/TODO
CHANGED
@@ -1,42 +1,32 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.4
|
2
2
|
|
3
|
-
- BUG: HEAD request on invalid entry caches zero-length response
|
4
|
-
- BUG: meta store hits but entity misses
|
5
|
-
- BUG: Response body written to cache each time validation succeeds
|
6
|
-
(actually, I'm not positive whether this is happening or not but
|
7
|
-
it looks like it is).
|
8
|
-
- Are we doing HEAD properly?
|
9
3
|
- liberal, conservative, sane caching configs
|
10
|
-
- Sample
|
11
|
-
- busters.rb doc and tests
|
12
|
-
- no-cache.rb doc and tests
|
4
|
+
- Sample apps: Rack, Rails, Sinatra, Merb, etc.
|
5
|
+
- busters.rb and no-cache.rb doc and tests
|
13
6
|
- Canonicalized URL for cache key:
|
14
7
|
- sorts params by key, then value
|
15
8
|
- urlencodes /[^ A-Za-z0-9_.-]/ host, path, and param key/value
|
16
|
-
-
|
17
|
-
bodies (file entity stores only).
|
18
|
-
- Sqlite3 (meta store)
|
9
|
+
- Custom cache keys
|
19
10
|
- Cache invalidation on PUT, POST, DELETE.
|
20
11
|
- Invalidate at the request URI; or, anything that's "near" the request URI.
|
21
12
|
- Invalidate at the URI of the Location or Content-Location response header.
|
22
13
|
|
23
14
|
## Backlog
|
24
15
|
|
16
|
+
- Add missing Expires header if we have a max-age.
|
25
17
|
- Purge/invalidate specific cache entries
|
26
18
|
- Purge/invalidate everything
|
27
19
|
- Maximum size of cached entity
|
28
20
|
- Last-Modified factor: requests that have a Last-Modified header but no Expires
|
29
21
|
header have a TTL assigned based on the last modified age of the response:
|
30
22
|
TTL = (Age * Factor), or, 1h = (10h * 0.1)
|
31
|
-
-
|
32
|
-
|
33
|
-
|
23
|
+
- Run under multiple-threads with an option to lock before making requests
|
24
|
+
to the backend. The idea is to be able to serve requests from cache in
|
25
|
+
separate threads. This should probably be implemented as a separate
|
26
|
+
middleware component.
|
27
|
+
- Consider implementing ESI (http://www.w3.org/TR/esi-lang). This should
|
34
28
|
probably be implemented as a separate middleware component.
|
29
|
+
- Sqlite3 (meta store)
|
35
30
|
- stale-while-revalidate
|
36
31
|
- Serve cached copies when down (see: stale-if-error) - e.g., database
|
37
32
|
connection drops and the cache takes over what it can.
|
38
|
-
- When a cache misses due to Vary, try to validate using the best match. Note
|
39
|
-
that you can't do this with a weak validator, so only strong etags can be
|
40
|
-
used.
|
41
|
-
- Consider implementing ESI (http://www.w3.org/TR/esi-lang). This should
|
42
|
-
probably be implemented as a separate middleware component.
|
data/doc/configuration.markdown
CHANGED
@@ -115,6 +115,14 @@ If no entitystore is specified, the `heap:/` store is assumed. This
|
|
115
115
|
implementation has significant draw-backs so explicit configuration is
|
116
116
|
recommended.
|
117
117
|
|
118
|
+
### `private_headers`
|
119
|
+
|
120
|
+
An array of request header names that cause the response to be treated with
|
121
|
+
private cache control semantics. The default value is `['Authorization', 'Cookie']`.
|
122
|
+
If any of these headers are present in the request, the response is considered
|
123
|
+
private and will not be cached _unless_ the response is explicitly marked public
|
124
|
+
(e.g., `Cache-Control: public`).
|
125
|
+
|
118
126
|
<a id='machinery'></a>
|
119
127
|
|
120
128
|
Configuration Machinery - Events and Transitions
|
data/doc/index.markdown
CHANGED
@@ -2,10 +2,10 @@ __Rack::Cache__ is suitable as a quick drop-in component to enable HTTP caching
|
|
2
2
|
for [Rack][]-based applications that produce freshness (`Expires`,
|
3
3
|
`Cache-Control`) and/or validation (`Last-Modified`, `ETag`) information.
|
4
4
|
|
5
|
-
* Standards-based ([RFC 2616][rfc] / [Section 13][s13]).
|
5
|
+
* Standards-based (see [RFC 2616][rfc] / [Section 13][s13]).
|
6
6
|
* Freshness/expiration based caching
|
7
7
|
* Validation
|
8
|
-
* Vary
|
8
|
+
* Vary support
|
9
9
|
* Portable: 100% Ruby / works with any [Rack][]-enabled framework.
|
10
10
|
* [Configuration language][config] for advanced caching policies.
|
11
11
|
* Disk, memcached, and heap memory [storage backends][storage].
|
@@ -47,29 +47,35 @@ simply `require` and `use` as follows:
|
|
47
47
|
|
48
48
|
Assuming you've designed your backend application to take advantage of HTTP's
|
49
49
|
caching features, no further code or configuration is required for basic
|
50
|
-
caching.
|
51
|
-
Language][config].
|
50
|
+
caching.
|
52
51
|
|
53
|
-
|
54
|
-
|
52
|
+
More
|
53
|
+
----
|
55
54
|
|
56
|
-
* [Configuration Language Documentation][config] -
|
55
|
+
* [Configuration Language Documentation][config] - how to customize cache
|
57
56
|
policy using the simple event-based configuration system.
|
58
57
|
|
59
|
-
* [Cache Storage Documentation][storage] -
|
58
|
+
* [Cache Storage Documentation][storage] - detailed information on the various
|
60
59
|
storage implementations available in __Rack::Cache__ and how to choose the one
|
61
60
|
that's best for your application.
|
62
61
|
|
63
|
-
* [
|
62
|
+
* [Things Caches Do][things] - an illustrated guide to how HTTP gateway
|
63
|
+
caches work with pointers to other useful resources on HTTP caching.
|
64
64
|
|
65
|
-
* [GitHub Repository](http://github.com/rtomayko/rack-cache/) -
|
65
|
+
* [GitHub Repository](http://github.com/rtomayko/rack-cache/) - get your
|
66
66
|
fork on.
|
67
67
|
|
68
|
+
* [Mailing List](http://groups.google.com/group/rack-cache) - for hackers
|
69
|
+
and users (`rack-cache@groups.google.com`).
|
70
|
+
|
71
|
+
* [FAQ](./faq) - Frequently Asked Questions about __Rack::Cache__.
|
72
|
+
|
68
73
|
* [RDoc API Documentation](./api/) - Mostly worthless if you just want to use
|
69
74
|
__Rack::Cache__ in your application but mildly insightful if you'd like to
|
70
75
|
get a feel for how the system has been put together; I recommend
|
71
76
|
[reading the source](http://github.com/rtomayko/rack-cache/master/lib/rack/cache).
|
72
77
|
|
78
|
+
|
73
79
|
See Also
|
74
80
|
--------
|
75
81
|
|
@@ -99,6 +105,7 @@ and is provided under [the MIT license](./license)
|
|
99
105
|
|
100
106
|
[config]: ./configuration "Rack::Cache Configuration Language Documentation"
|
101
107
|
[storage]: ./storage "Rack::Cache Storage Documentation"
|
108
|
+
[things]: http://tomayko.com/writings/things-caches-do
|
102
109
|
|
103
110
|
[rfc]: http://tools.ietf.org/html/rfc2616
|
104
111
|
"RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 [ietf.org]"
|
data/doc/layout.html.erb
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
<p>
|
15
15
|
<a href="./configuration" title='Configuration Language Documentation'>Config</a> |
|
16
16
|
<a href="./storage" title='Cache Storage Documentation'>Storage</a> |
|
17
|
+
<a href="http://tomayko.com/writings/things-caches-do" title="Things Caches Do">Things</a> |
|
17
18
|
<a href="./faq" title='Frequently Asked Questions'>FAQ</a> |
|
18
19
|
<a href="./api/" title='Fucking Sucks.'>RDoc</a>
|
19
20
|
</p>
|
data/doc/server.ru
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Rackup config that serves the contents of Rack::Cache's
|
2
|
+
# doc directory. The documentation is rebuilt on each request.
|
3
|
+
|
4
|
+
# Rewrites URLs like conventional web server configs.
|
5
|
+
class Rewriter < Struct.new(:app)
|
6
|
+
def call(env)
|
7
|
+
if env['PATH_INFO'] =~ /\/$/
|
8
|
+
env['PATH_INFO'] += 'index.html'
|
9
|
+
elsif env['PATH_INFO'] !~ /\.\w+$/
|
10
|
+
env['PATH_INFO'] += '.html'
|
11
|
+
end
|
12
|
+
app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Rebuilds documentation on each request.
|
17
|
+
class DocBuilder < Struct.new(:app)
|
18
|
+
def call(env)
|
19
|
+
if env['PATH_INFO'] !~ /\.(css|js|gif|jpg|png|ico)$/
|
20
|
+
env['rack.errors'] << "*** rebuilding documentation (rake -s doc)\n"
|
21
|
+
system "rake -s doc"
|
22
|
+
end
|
23
|
+
app.call(env)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
use Rack::CommonLogger
|
28
|
+
use DocBuilder
|
29
|
+
use Rewriter
|
30
|
+
use Rack::Static, :root => File.dirname(__FILE__), :urls => ["/"]
|
31
|
+
|
32
|
+
run(lambda{|env| [404,{},'<h1>Not Found</h1>']})
|
33
|
+
|
34
|
+
# vim: ft=ruby
|
@@ -17,7 +17,7 @@
|
|
17
17
|
#
|
18
18
|
on :receive do
|
19
19
|
pass! unless request.method? 'GET', 'HEAD'
|
20
|
-
pass! if request.header? '
|
20
|
+
pass! if request.header? 'Expect'
|
21
21
|
lookup!
|
22
22
|
end
|
23
23
|
|
@@ -109,7 +109,6 @@ end
|
|
109
109
|
# * error! - return the error code specified and abandon request.
|
110
110
|
#
|
111
111
|
on :store do
|
112
|
-
entry.ttl = default_ttl if entry.ttl.nil?
|
113
112
|
trace 'store backend response in cache (ttl: %ds)', entry.ttl
|
114
113
|
persist!
|
115
114
|
end
|
data/lib/rack/cache/core.rb
CHANGED
@@ -74,9 +74,9 @@ module Rack::Cache
|
|
74
74
|
@triggered.include?(event)
|
75
75
|
end
|
76
76
|
|
77
|
-
private
|
78
77
|
# Event handlers.
|
79
78
|
attr_reader :events
|
79
|
+
private :events
|
80
80
|
|
81
81
|
public
|
82
82
|
# Attach custom logic to one or more events.
|
@@ -101,6 +101,13 @@ module Rack::Cache
|
|
101
101
|
end
|
102
102
|
|
103
103
|
private
|
104
|
+
# Does the request include authorization or other sensitive information
|
105
|
+
# that should cause the response to be considered private by default?
|
106
|
+
# Private responses are not stored in the cache.
|
107
|
+
def private_request?
|
108
|
+
request.header?(*private_headers)
|
109
|
+
end
|
110
|
+
|
104
111
|
# Determine if the #response validators (ETag, Last-Modified) matches
|
105
112
|
# a conditional value specified in #original_request.
|
106
113
|
def not_modified?
|
@@ -119,6 +126,7 @@ module Rack::Cache
|
|
119
126
|
private
|
120
127
|
def perform_receive
|
121
128
|
@original_request = Request.new(@env.dup.freeze)
|
129
|
+
@env['REQUEST_METHOD'] = 'GET' if @original_request.head?
|
122
130
|
@request = Request.new(@env)
|
123
131
|
info "%s %s", @original_request.request_method, @original_request.fullpath
|
124
132
|
transition(from=:receive, to=[:pass, :lookup, :error])
|
@@ -126,6 +134,7 @@ module Rack::Cache
|
|
126
134
|
|
127
135
|
def perform_pass
|
128
136
|
trace 'passing'
|
137
|
+
request.env['REQUEST_METHOD'] = @original_request.request_method
|
129
138
|
fetch_from_backend
|
130
139
|
transition(from=:pass, to=[:pass, :finish, :error]) do |event|
|
131
140
|
if event == :pass
|
@@ -191,6 +200,20 @@ module Rack::Cache
|
|
191
200
|
request.env.delete('HTTP_IF_MODIFIED_SINCE')
|
192
201
|
request.env.delete('HTTP_IF_NONE_MATCH')
|
193
202
|
fetch_from_backend
|
203
|
+
|
204
|
+
# mark the response as explicitly private if any of the private
|
205
|
+
# request headers are present and the response was not explicitly
|
206
|
+
# declared public.
|
207
|
+
if private_request? && !@response.public?
|
208
|
+
@response.private = true
|
209
|
+
else
|
210
|
+
# assign a default TTL for the cache entry if none was specified in
|
211
|
+
# the response; the must-revalidate cache control directive disables
|
212
|
+
# default ttl assigment.
|
213
|
+
if default_ttl > 0 && @response.ttl.nil? && !@response.must_revalidate?
|
214
|
+
@response.ttl = default_ttl
|
215
|
+
end
|
216
|
+
end
|
194
217
|
transition(from=:fetch, to=[:store, :deliver, :error])
|
195
218
|
end
|
196
219
|
|
@@ -198,7 +221,11 @@ module Rack::Cache
|
|
198
221
|
@entry = @response
|
199
222
|
transition(from=:store, to=[:persist, :deliver, :error]) do |event|
|
200
223
|
if event == :persist
|
201
|
-
|
224
|
+
if @response.private?
|
225
|
+
warn 'forced to store response marked as private.'
|
226
|
+
else
|
227
|
+
trace "storing response in cache"
|
228
|
+
end
|
202
229
|
metastore.store(original_request, @entry, entitystore)
|
203
230
|
@response = @entry
|
204
231
|
:deliver
|
@@ -211,6 +238,7 @@ module Rack::Cache
|
|
211
238
|
def perform_deliver
|
212
239
|
trace "delivering response ..."
|
213
240
|
response.not_modified! if not_modified?
|
241
|
+
response.body = [] if @original_request.head?
|
214
242
|
transition(from=:deliver, to=[:finish, :error])
|
215
243
|
end
|
216
244
|
|
@@ -58,6 +58,12 @@ module Rack::Cache
|
|
58
58
|
[key, size]
|
59
59
|
end
|
60
60
|
|
61
|
+
# Remove the body corresponding to key; return nil.
|
62
|
+
def purge(key)
|
63
|
+
@hash.delete(key)
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
61
67
|
def self.resolve(uri)
|
62
68
|
new
|
63
69
|
end
|
@@ -89,16 +95,19 @@ module Rack::Cache
|
|
89
95
|
nil
|
90
96
|
end
|
91
97
|
|
92
|
-
|
93
|
-
|
94
|
-
def open(key)
|
95
|
-
io = File.open(body_path(key), 'rb')
|
96
|
-
def io.each
|
98
|
+
class Body < ::File #:nodoc:
|
99
|
+
def each
|
97
100
|
while part = read(8192)
|
98
101
|
yield part
|
99
102
|
end
|
100
103
|
end
|
101
|
-
|
104
|
+
alias_method :to_path, :path
|
105
|
+
end
|
106
|
+
|
107
|
+
# Open the entity body and return an IO object. The IO object's
|
108
|
+
# each method is overridden to read 8K chunks instead of lines.
|
109
|
+
def open(key)
|
110
|
+
Body.open(body_path(key), 'rb')
|
102
111
|
rescue Errno::ENOENT
|
103
112
|
nil
|
104
113
|
end
|
@@ -121,6 +130,13 @@ module Rack::Cache
|
|
121
130
|
[key, size]
|
122
131
|
end
|
123
132
|
|
133
|
+
def purge(key)
|
134
|
+
File.unlink body_path(key)
|
135
|
+
nil
|
136
|
+
rescue Errno::ENOENT
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
|
124
140
|
protected
|
125
141
|
def storage_path(stem)
|
126
142
|
File.join root, stem
|
@@ -190,6 +206,13 @@ module Rack::Cache
|
|
190
206
|
[key, size]
|
191
207
|
end
|
192
208
|
|
209
|
+
def purge(key)
|
210
|
+
cache.delete(key)
|
211
|
+
nil
|
212
|
+
rescue Memcached::NotFound
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
|
193
216
|
extend Rack::Utils
|
194
217
|
|
195
218
|
# Create MemCache store for the given URI. The URI must specify
|
data/lib/rack/cache/headers.rb
CHANGED
@@ -21,7 +21,7 @@ module Rack::Cache
|
|
21
21
|
# header is present.
|
22
22
|
def cache_control
|
23
23
|
@cache_control ||=
|
24
|
-
|
24
|
+
headers['Cache-Control'].to_s.split(/\s*[,;]\s*/).inject({}) {|hash,token|
|
25
25
|
name, value = token.split(/\s*=\s*/, 2)
|
26
26
|
hash[name.downcase] = (value || true) unless name.empty?
|
27
27
|
hash
|
@@ -107,16 +107,15 @@ module Rack::Cache
|
|
107
107
|
!fresh?
|
108
108
|
end
|
109
109
|
|
110
|
-
# Determine if the response is worth caching under any circumstance.
|
111
|
-
#
|
112
|
-
#
|
110
|
+
# Determine if the response is worth caching under any circumstance. Responses
|
111
|
+
# marked "private" with an explicit Cache-Control directive are considered
|
112
|
+
# uncacheable
|
113
113
|
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
# cache that only serves fresh or valid objects.
|
114
|
+
# Responses with neither a freshness lifetime (Expires, max-age) nor cache
|
115
|
+
# validator (Last-Modified, Etag) are considered uncacheable.
|
117
116
|
def cacheable?
|
118
117
|
return false unless CACHEABLE_RESPONSE_CODES.include?(status)
|
119
|
-
return false if no_store?
|
118
|
+
return false if no_store? || private?
|
120
119
|
validateable? || fresh?
|
121
120
|
end
|
122
121
|
|
@@ -124,7 +123,8 @@ module Rack::Cache
|
|
124
123
|
# a +Cache-Control+ header with +max-age+ value is present or when the
|
125
124
|
# +Expires+ header is set.
|
126
125
|
def freshness_information?
|
127
|
-
header?('Expires') ||
|
126
|
+
header?('Expires') ||
|
127
|
+
!!(cache_control['s-maxage'] || cache_control['max-age'])
|
128
128
|
end
|
129
129
|
|
130
130
|
# Determine if the response includes headers that can be used to validate
|
@@ -137,7 +137,7 @@ module Rack::Cache
|
|
137
137
|
# revalidating with the origin. Note that this does not necessary imply that
|
138
138
|
# a caching agent ought not store the response in its cache.
|
139
139
|
def no_cache?
|
140
|
-
|
140
|
+
cache_control['no-cache']
|
141
141
|
end
|
142
142
|
|
143
143
|
# Indicates that the response should not be stored under any circumstances.
|
@@ -145,6 +145,42 @@ module Rack::Cache
|
|
145
145
|
cache_control['no-store']
|
146
146
|
end
|
147
147
|
|
148
|
+
# True when the response has been explicitly marked "public".
|
149
|
+
def public?
|
150
|
+
cache_control['public']
|
151
|
+
end
|
152
|
+
|
153
|
+
# Mark the response "public", making it eligible for other clients. Note
|
154
|
+
# that responses are considered "public" by default unless the request
|
155
|
+
# includes private headers (Authorization, Cookie).
|
156
|
+
def public=(value)
|
157
|
+
value = value ? true : nil
|
158
|
+
self.cache_control = cache_control.
|
159
|
+
merge('public' => value, 'private' => !value)
|
160
|
+
end
|
161
|
+
|
162
|
+
# True when the response has been marked "private" explicitly.
|
163
|
+
def private?
|
164
|
+
cache_control['private']
|
165
|
+
end
|
166
|
+
|
167
|
+
# Mark the response "private", making it ineligible for serving other
|
168
|
+
# clients.
|
169
|
+
def private=(value)
|
170
|
+
value = value ? true : nil
|
171
|
+
self.cache_control = cache_control.
|
172
|
+
merge('public' => !value, 'private' => value)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Indicates that the cache must not serve a stale response in any
|
176
|
+
# circumstance without first revalidating with the origin. When present,
|
177
|
+
# the TTL of the response should not be overriden to be greater than the
|
178
|
+
# value provided by the origin.
|
179
|
+
def must_revalidate?
|
180
|
+
cache_control['must-revalidate'] ||
|
181
|
+
cache_control['proxy-revalidate']
|
182
|
+
end
|
183
|
+
|
148
184
|
# The date, as specified by the Date header. When no Date header is present,
|
149
185
|
# set the Date header to Time.now and return.
|
150
186
|
def date
|
@@ -163,29 +199,36 @@ module Rack::Cache
|
|
163
199
|
|
164
200
|
# The number of seconds after the time specified in the response's Date
|
165
201
|
# header when the the response should no longer be considered fresh. First
|
166
|
-
# check for a
|
167
|
-
# header; return nil when no maximum age can be
|
202
|
+
# check for a s-maxage directive, then a max-age directive, and then fall
|
203
|
+
# back on an expires header; return nil when no maximum age can be
|
204
|
+
# established.
|
168
205
|
def max_age
|
169
|
-
if age = cache_control['max-age']
|
206
|
+
if age = (cache_control['s-maxage'] || cache_control['max-age'])
|
170
207
|
age.to_i
|
171
208
|
elsif headers['Expires']
|
172
209
|
Time.httpdate(headers['Expires']) - date
|
173
210
|
end
|
174
211
|
end
|
175
212
|
|
176
|
-
#
|
177
|
-
# be considered fresh.
|
213
|
+
# The number of seconds after which the response should no longer
|
214
|
+
# be considered fresh. Sets the Cache-Control max-age directive.
|
178
215
|
def max_age=(value)
|
179
216
|
self.cache_control = cache_control.merge('max-age' => value.to_s)
|
180
217
|
end
|
181
218
|
|
219
|
+
# Like #max_age= but sets the s-maxage directive, which applies only
|
220
|
+
# to shared caches.
|
221
|
+
def shared_max_age=(value)
|
222
|
+
self.cache_control = cache_control.merge('s-maxage' => value.to_s)
|
223
|
+
end
|
224
|
+
|
182
225
|
# The Time when the response should be considered stale. With a
|
183
226
|
# Cache-Control/max-age value is present, this is calculated by adding the
|
184
227
|
# number of seconds specified to the responses #date value. Falls back to
|
185
228
|
# the time specified in the Expires header or returns nil if neither is
|
186
229
|
# present.
|
187
230
|
def expires_at
|
188
|
-
if max_age = cache_control['max-age']
|
231
|
+
if max_age = (cache_control['s-maxage'] || cache_control['max-age'])
|
189
232
|
date + max_age.to_i
|
190
233
|
elsif time = headers['Expires']
|
191
234
|
Time.httpdate(time)
|
@@ -200,9 +243,15 @@ module Rack::Cache
|
|
200
243
|
max_age - age if max_age
|
201
244
|
end
|
202
245
|
|
203
|
-
# Set the response's time-to-live to the specified number
|
204
|
-
# adjusts the Cache-Control/
|
246
|
+
# Set the response's time-to-live for shared caches to the specified number
|
247
|
+
# of seconds. This adjusts the Cache-Control/s-maxage directive.
|
205
248
|
def ttl=(seconds)
|
249
|
+
self.shared_max_age = age + seconds
|
250
|
+
end
|
251
|
+
|
252
|
+
# Set the response's time-to-live for private/client caches. This adjusts
|
253
|
+
# the Cache-Control/max-age directive.
|
254
|
+
def client_ttl=(seconds)
|
206
255
|
self.max_age = age + seconds
|
207
256
|
end
|
208
257
|
|
@@ -250,8 +299,7 @@ module Rack::Cache
|
|
250
299
|
nil
|
251
300
|
end
|
252
301
|
|
253
|
-
# The literal value of the Vary header, or nil when no
|
254
|
-
# present.
|
302
|
+
# The literal value of the Vary header, or nil when no header is present.
|
255
303
|
def vary
|
256
304
|
headers['Vary']
|
257
305
|
end
|