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.
Potentially problematic release.
This version of rack-cache might be problematic. Click here for more details.
- data/CHANGES +58 -0
- data/README +14 -9
- data/Rakefile +11 -5
- data/TODO +11 -19
- data/doc/configuration.markdown +9 -1
- 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 +37 -9
- data/lib/rack/cache/entitystore.rb +29 -6
- data/lib/rack/cache/headers.rb +118 -30
- data/lib/rack/cache/metastore.rb +22 -39
- data/lib/rack/cache/options.rb +11 -1
- data/lib/rack/cache/response.rb +2 -2
- data/rack-cache.gemspec +5 -4
- data/test/cache_test.rb +3 -3
- data/test/context_test.rb +255 -75
- 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 +106 -23
- data/test/metastore_test.rb +28 -18
- data/test/options_test.rb +13 -10
- data/test/spec_setup.rb +13 -7
- metadata +8 -4
data/CHANGES
CHANGED
@@ -1,3 +1,61 @@
|
|
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
|
+
|
43
|
+
* BUG: The Age response header was not being set properly when a stale
|
44
|
+
entry was validated. This would result in Age values that exceeded
|
45
|
+
the freshness lifetime in responses.
|
46
|
+
|
47
|
+
* BUG: A cached entry in a heap meta store could be unintentionally
|
48
|
+
modified by request processing since the cached objects were being
|
49
|
+
returned directly. The result was typically missing/incorrect header
|
50
|
+
values (e.g., missing Content-Type header). [dkubb]
|
51
|
+
|
52
|
+
* BUG: 304 responses should not include entity headers (especially
|
53
|
+
Content-Length). This is causing Safari/WebKit weirdness on 304
|
54
|
+
responses.
|
55
|
+
|
56
|
+
* BUG: The If-None-Match header was being ignored, causing the cache
|
57
|
+
to send 200 responses to matching conditional GET requests.
|
58
|
+
|
1
59
|
## 0.2.0 / 2008-10-24 / Initial Release
|
2
60
|
|
3
61
|
* Document events and transitions in `rack/cache/config/default.rb`
|
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,12 +72,12 @@ 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
|
-
content = Markdown.new(ERB.new(template, 0, "%<>").result(binding)).to_html
|
80
|
+
content = Markdown.new(ERB.new(template, 0, "%<>").result(binding), :smart).to_html
|
81
81
|
title = content.match("<h1>(.*)</h1>")[1] rescue ''
|
82
82
|
layout = ERB.new(File.read("doc/layout.html.erb"), 0, "%<>")
|
83
83
|
output = layout.result(binding)
|
@@ -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,40 +1,32 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.4
|
2
2
|
|
3
|
-
- BUG: meta store hits but entity misses
|
4
|
-
- BUG: HEAD request on invalid entry caches zero-length response
|
5
|
-
- BUG: Response body written to cache each time validation succeeds
|
6
|
-
- Are we doing HEAD properly?
|
7
3
|
- liberal, conservative, sane caching configs
|
8
|
-
- Sample
|
9
|
-
- busters.rb doc and tests
|
10
|
-
- no-cache.rb doc and tests
|
4
|
+
- Sample apps: Rack, Rails, Sinatra, Merb, etc.
|
5
|
+
- busters.rb and no-cache.rb doc and tests
|
11
6
|
- Canonicalized URL for cache key:
|
12
7
|
- sorts params by key, then value
|
13
8
|
- urlencodes /[^ A-Za-z0-9_.-]/ host, path, and param key/value
|
14
|
-
-
|
15
|
-
bodies (file entity stores only).
|
16
|
-
- Sqlite3 (meta store)
|
9
|
+
- Custom cache keys
|
17
10
|
- Cache invalidation on PUT, POST, DELETE.
|
18
11
|
- Invalidate at the request URI; or, anything that's "near" the request URI.
|
19
12
|
- Invalidate at the URI of the Location or Content-Location response header.
|
20
13
|
|
21
14
|
## Backlog
|
22
15
|
|
16
|
+
- Add missing Expires header if we have a max-age.
|
23
17
|
- Purge/invalidate specific cache entries
|
24
18
|
- Purge/invalidate everything
|
25
19
|
- Maximum size of cached entity
|
26
20
|
- Last-Modified factor: requests that have a Last-Modified header but no Expires
|
27
21
|
header have a TTL assigned based on the last modified age of the response:
|
28
22
|
TTL = (Age * Factor), or, 1h = (10h * 0.1)
|
29
|
-
-
|
30
|
-
|
31
|
-
|
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
|
32
28
|
probably be implemented as a separate middleware component.
|
29
|
+
- Sqlite3 (meta store)
|
33
30
|
- stale-while-revalidate
|
34
31
|
- Serve cached copies when down (see: stale-if-error) - e.g., database
|
35
32
|
connection drops and the cache takes over what it can.
|
36
|
-
- When a cache misses due to Vary, try to validate using the best match. Note
|
37
|
-
that you can't do this with a weak validator, so only strong etags can be
|
38
|
-
used.
|
39
|
-
- Consider implementing ESI (http://www.w3.org/TR/esi-lang). This should
|
40
|
-
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
|
@@ -184,7 +192,7 @@ cache policy into files for organization and reuse.
|
|
184
192
|
# more stuff here
|
185
193
|
end
|
186
194
|
|
187
|
-
The `
|
195
|
+
The `busters` and `mycacheconfig` configuration files are normal Ruby source
|
188
196
|
files (i.e., they have a `.rb` extension) situated on the `$LOAD_PATH` - the
|
189
197
|
`import` statement works like Ruby's `require` statement but the contents of the
|
190
198
|
files are evaluated in the context of the configuration machinery, as if
|
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,10 +101,18 @@ module Rack::Cache
|
|
101
101
|
end
|
102
102
|
|
103
103
|
private
|
104
|
-
#
|
105
|
-
#
|
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
|
+
|
111
|
+
# Determine if the #response validators (ETag, Last-Modified) matches
|
112
|
+
# a conditional value specified in #original_request.
|
106
113
|
def not_modified?
|
107
|
-
response.
|
114
|
+
response.etag_matches?(original_request.if_none_match) ||
|
115
|
+
response.last_modified_at?(original_request.if_modified_since)
|
108
116
|
end
|
109
117
|
|
110
118
|
# Delegate the request to the backend and create the response.
|
@@ -118,6 +126,7 @@ module Rack::Cache
|
|
118
126
|
private
|
119
127
|
def perform_receive
|
120
128
|
@original_request = Request.new(@env.dup.freeze)
|
129
|
+
@env['REQUEST_METHOD'] = 'GET' if @original_request.head?
|
121
130
|
@request = Request.new(@env)
|
122
131
|
info "%s %s", @original_request.request_method, @original_request.fullpath
|
123
132
|
transition(from=:receive, to=[:pass, :lookup, :error])
|
@@ -125,6 +134,7 @@ module Rack::Cache
|
|
125
134
|
|
126
135
|
def perform_pass
|
127
136
|
trace 'passing'
|
137
|
+
request.env['REQUEST_METHOD'] = @original_request.request_method
|
128
138
|
fetch_from_backend
|
129
139
|
transition(from=:pass, to=[:pass, :finish, :error]) do |event|
|
130
140
|
if event == :pass
|
@@ -171,6 +181,7 @@ module Rack::Cache
|
|
171
181
|
trace "cache entry valid"
|
172
182
|
@response = entry.dup
|
173
183
|
@response.headers.delete('Age')
|
184
|
+
@response.headers.delete('Date')
|
174
185
|
@response.headers['X-Origin-Status'] = '304'
|
175
186
|
%w[Date Expires Cache-Control Etag Last-Modified].each do |name|
|
176
187
|
next unless value = original_response.headers[name]
|
@@ -189,6 +200,20 @@ module Rack::Cache
|
|
189
200
|
request.env.delete('HTTP_IF_MODIFIED_SINCE')
|
190
201
|
request.env.delete('HTTP_IF_NONE_MATCH')
|
191
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
|
192
217
|
transition(from=:fetch, to=[:store, :deliver, :error])
|
193
218
|
end
|
194
219
|
|
@@ -196,7 +221,11 @@ module Rack::Cache
|
|
196
221
|
@entry = @response
|
197
222
|
transition(from=:store, to=[:persist, :deliver, :error]) do |event|
|
198
223
|
if event == :persist
|
199
|
-
|
224
|
+
if @response.private?
|
225
|
+
warn 'forced to store response marked as private.'
|
226
|
+
else
|
227
|
+
trace "storing response in cache"
|
228
|
+
end
|
200
229
|
metastore.store(original_request, @entry, entitystore)
|
201
230
|
@response = @entry
|
202
231
|
:deliver
|
@@ -208,14 +237,13 @@ module Rack::Cache
|
|
208
237
|
|
209
238
|
def perform_deliver
|
210
239
|
trace "delivering response ..."
|
211
|
-
if not_modified?
|
212
|
-
|
213
|
-
response.body = []
|
214
|
-
end
|
240
|
+
response.not_modified! if not_modified?
|
241
|
+
response.body = [] if @original_request.head?
|
215
242
|
transition(from=:deliver, to=[:finish, :error])
|
216
243
|
end
|
217
244
|
|
218
245
|
def perform_finish
|
246
|
+
response.headers.delete 'X-Status'
|
219
247
|
response.to_a
|
220
248
|
end
|
221
249
|
|