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.
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
|
|