rtomayko-rack-cache 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|