rack-cache 0.3.0 → 0.4
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 +43 -0
- data/README +18 -9
- data/Rakefile +1 -14
- data/TODO +13 -14
- data/doc/configuration.markdown +7 -153
- data/doc/faq.markdown +8 -0
- data/doc/index.markdown +7 -9
- data/example/sinatra/app.rb +25 -0
- data/example/sinatra/views/index.erb +44 -0
- data/lib/rack/cache.rb +5 -11
- data/lib/rack/cache/cachecontrol.rb +193 -0
- data/lib/rack/cache/context.rb +190 -52
- data/lib/rack/cache/entitystore.rb +10 -4
- data/lib/rack/cache/key.rb +52 -0
- data/lib/rack/cache/metastore.rb +52 -16
- data/lib/rack/cache/options.rb +60 -39
- data/lib/rack/cache/request.rb +11 -15
- data/lib/rack/cache/response.rb +221 -30
- data/lib/rack/cache/storage.rb +1 -2
- data/rack-cache.gemspec +9 -15
- data/test/cache_test.rb +9 -6
- data/test/cachecontrol_test.rb +139 -0
- data/test/context_test.rb +251 -169
- data/test/entitystore_test.rb +12 -11
- data/test/key_test.rb +50 -0
- data/test/metastore_test.rb +57 -14
- data/test/options_test.rb +11 -0
- data/test/request_test.rb +19 -0
- data/test/response_test.rb +164 -23
- data/test/spec_setup.rb +7 -0
- metadata +12 -20
- data/doc/events.dot +0 -27
- data/lib/rack/cache/config.rb +0 -65
- data/lib/rack/cache/config/busters.rb +0 -16
- data/lib/rack/cache/config/default.rb +0 -133
- data/lib/rack/cache/config/no-cache.rb +0 -13
- data/lib/rack/cache/core.rb +0 -299
- data/lib/rack/cache/headers.rb +0 -325
- data/lib/rack/utils/environment_headers.rb +0 -78
- data/test/config_test.rb +0 -66
- data/test/core_test.rb +0 -84
- data/test/environment_headers_test.rb +0 -69
- data/test/headers_test.rb +0 -298
- data/test/logging_test.rb +0 -45
data/lib/rack/cache.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'time'
|
3
1
|
require 'rack'
|
4
2
|
|
5
|
-
module Rack #:nodoc:
|
6
|
-
end
|
7
|
-
|
8
3
|
# = HTTP Caching For Rack
|
9
4
|
#
|
10
5
|
# Rack::Cache is suitable as a quick, drop-in component to enable HTTP caching
|
@@ -15,7 +10,6 @@ end
|
|
15
10
|
# * Freshness/expiration based caching and validation
|
16
11
|
# * Supports HTTP Vary
|
17
12
|
# * Portable: 100% Ruby / works with any Rack-enabled framework
|
18
|
-
# * VCL-like configuration language for advanced caching policies
|
19
13
|
# * Disk, memcached, and heap memory storage backends
|
20
14
|
#
|
21
15
|
# === Usage
|
@@ -32,12 +26,12 @@ end
|
|
32
26
|
# set :entitystore, 'file:/var/cache/rack'
|
33
27
|
# end
|
34
28
|
# run app
|
35
|
-
#
|
36
29
|
module Rack::Cache
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
30
|
+
autoload :Request, 'rack/cache/request'
|
31
|
+
autoload :Response, 'rack/cache/response'
|
32
|
+
autoload :Context, 'rack/cache/context'
|
33
|
+
autoload :Storage, 'rack/cache/storage'
|
34
|
+
autoload :CacheControl, 'rack/cache/cachecontrol'
|
41
35
|
|
42
36
|
# Create a new Rack::Cache middleware component that fetches resources from
|
43
37
|
# the specified backend application. The +options+ Hash can be used to
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Rack
|
2
|
+
module Cache
|
3
|
+
|
4
|
+
# Parses a Cache-Control header and exposes the directives as a Hash.
|
5
|
+
# Directives that do not have values are set to +true+.
|
6
|
+
class CacheControl < Hash
|
7
|
+
def initialize(value=nil)
|
8
|
+
parse(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Indicates that the response MAY be cached by any cache, even if it
|
12
|
+
# would normally be non-cacheable or cacheable only within a non-
|
13
|
+
# shared cache.
|
14
|
+
#
|
15
|
+
# A response may be considered public without this directive if the
|
16
|
+
# private directive is not set and the request does not include an
|
17
|
+
# Authorization header.
|
18
|
+
def public?
|
19
|
+
self['public']
|
20
|
+
end
|
21
|
+
|
22
|
+
# Indicates that all or part of the response message is intended for
|
23
|
+
# a single user and MUST NOT be cached by a shared cache. This
|
24
|
+
# allows an origin server to state that the specified parts of the
|
25
|
+
# response are intended for only one user and are not a valid
|
26
|
+
# response for requests by other users. A private (non-shared) cache
|
27
|
+
# MAY cache the response.
|
28
|
+
#
|
29
|
+
# Note: This usage of the word private only controls where the
|
30
|
+
# response may be cached, and cannot ensure the privacy of the
|
31
|
+
# message content.
|
32
|
+
def private?
|
33
|
+
self['private']
|
34
|
+
end
|
35
|
+
|
36
|
+
# When set in a response, a cache MUST NOT use the response to satisfy a
|
37
|
+
# subsequent request without successful revalidation with the origin
|
38
|
+
# server. This allows an origin server to prevent caching even by caches
|
39
|
+
# that have been configured to return stale responses to client requests.
|
40
|
+
#
|
41
|
+
# Note that this does not necessary imply that the response may not be
|
42
|
+
# stored by the cache, only that the cache cannot serve it without first
|
43
|
+
# making a conditional GET request with the origin server.
|
44
|
+
#
|
45
|
+
# When set in a request, the server MUST NOT use a cached copy for its
|
46
|
+
# response. This has quite different semantics compared to the no-cache
|
47
|
+
# directive on responses. When the client specifies no-cache, it causes
|
48
|
+
# an end-to-end reload, forcing each cache to update their cached copies.
|
49
|
+
def no_cache?
|
50
|
+
self['no-cache']
|
51
|
+
end
|
52
|
+
|
53
|
+
# Indicates that the response MUST NOT be stored under any circumstances.
|
54
|
+
#
|
55
|
+
# The purpose of the no-store directive is to prevent the
|
56
|
+
# inadvertent release or retention of sensitive information (for
|
57
|
+
# example, on backup tapes). The no-store directive applies to the
|
58
|
+
# entire message, and MAY be sent either in a response or in a
|
59
|
+
# request. If sent in a request, a cache MUST NOT store any part of
|
60
|
+
# either this request or any response to it. If sent in a response,
|
61
|
+
# a cache MUST NOT store any part of either this response or the
|
62
|
+
# request that elicited it. This directive applies to both non-
|
63
|
+
# shared and shared caches. "MUST NOT store" in this context means
|
64
|
+
# that the cache MUST NOT intentionally store the information in
|
65
|
+
# non-volatile storage, and MUST make a best-effort attempt to
|
66
|
+
# remove the information from volatile storage as promptly as
|
67
|
+
# possible after forwarding it.
|
68
|
+
#
|
69
|
+
# The purpose of this directive is to meet the stated requirements
|
70
|
+
# of certain users and service authors who are concerned about
|
71
|
+
# accidental releases of information via unanticipated accesses to
|
72
|
+
# cache data structures. While the use of this directive might
|
73
|
+
# improve privacy in some cases, we caution that it is NOT in any
|
74
|
+
# way a reliable or sufficient mechanism for ensuring privacy. In
|
75
|
+
# particular, malicious or compromised caches might not recognize or
|
76
|
+
# obey this directive, and communications networks might be
|
77
|
+
# vulnerable to eavesdropping.
|
78
|
+
def no_store?
|
79
|
+
self['no-store']
|
80
|
+
end
|
81
|
+
|
82
|
+
# The expiration time of an entity MAY be specified by the origin
|
83
|
+
# server using the Expires header (see section 14.21). Alternatively,
|
84
|
+
# it MAY be specified using the max-age directive in a response. When
|
85
|
+
# the max-age cache-control directive is present in a cached response,
|
86
|
+
# the response is stale if its current age is greater than the age
|
87
|
+
# value given (in seconds) at the time of a new request for that
|
88
|
+
# resource. The max-age directive on a response implies that the
|
89
|
+
# response is cacheable (i.e., "public") unless some other, more
|
90
|
+
# restrictive cache directive is also present.
|
91
|
+
#
|
92
|
+
# If a response includes both an Expires header and a max-age
|
93
|
+
# directive, the max-age directive overrides the Expires header, even
|
94
|
+
# if the Expires header is more restrictive. This rule allows an origin
|
95
|
+
# server to provide, for a given response, a longer expiration time to
|
96
|
+
# an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache. This might be
|
97
|
+
# useful if certain HTTP/1.0 caches improperly calculate ages or
|
98
|
+
# expiration times, perhaps due to desynchronized clocks.
|
99
|
+
#
|
100
|
+
# Many HTTP/1.0 cache implementations will treat an Expires value that
|
101
|
+
# is less than or equal to the response Date value as being equivalent
|
102
|
+
# to the Cache-Control response directive "no-cache". If an HTTP/1.1
|
103
|
+
# cache receives such a response, and the response does not include a
|
104
|
+
# Cache-Control header field, it SHOULD consider the response to be
|
105
|
+
# non-cacheable in order to retain compatibility with HTTP/1.0 servers.
|
106
|
+
#
|
107
|
+
# When the max-age directive is included in the request, it indicates
|
108
|
+
# that the client is willing to accept a response whose age is no
|
109
|
+
# greater than the specified time in seconds.
|
110
|
+
def max_age
|
111
|
+
self['max-age'].to_i if key?('max-age')
|
112
|
+
end
|
113
|
+
|
114
|
+
# If a response includes an s-maxage directive, then for a shared
|
115
|
+
# cache (but not for a private cache), the maximum age specified by
|
116
|
+
# this directive overrides the maximum age specified by either the
|
117
|
+
# max-age directive or the Expires header. The s-maxage directive
|
118
|
+
# also implies the semantics of the proxy-revalidate directive. i.e.,
|
119
|
+
# that the shared cache must not use the entry after it becomes stale
|
120
|
+
# to respond to a subsequent request without first revalidating it with
|
121
|
+
# the origin server. The s-maxage directive is always ignored by a
|
122
|
+
# private cache.
|
123
|
+
def shared_max_age
|
124
|
+
self['s-maxage'].to_i if key?('s-maxage')
|
125
|
+
end
|
126
|
+
alias_method :s_maxage, :shared_max_age
|
127
|
+
|
128
|
+
# Because a cache MAY be configured to ignore a server's specified
|
129
|
+
# expiration time, and because a client request MAY include a max-
|
130
|
+
# stale directive (which has a similar effect), the protocol also
|
131
|
+
# includes a mechanism for the origin server to require revalidation
|
132
|
+
# of a cache entry on any subsequent use. When the must-revalidate
|
133
|
+
# directive is present in a response received by a cache, that cache
|
134
|
+
# MUST NOT use the entry after it becomes stale to respond to a
|
135
|
+
# subsequent request without first revalidating it with the origin
|
136
|
+
# server. (I.e., the cache MUST do an end-to-end revalidation every
|
137
|
+
# time, if, based solely on the origin server's Expires or max-age
|
138
|
+
# value, the cached response is stale.)
|
139
|
+
#
|
140
|
+
# The must-revalidate directive is necessary to support reliable
|
141
|
+
# operation for certain protocol features. In all circumstances an
|
142
|
+
# HTTP/1.1 cache MUST obey the must-revalidate directive; in
|
143
|
+
# particular, if the cache cannot reach the origin server for any
|
144
|
+
# reason, it MUST generate a 504 (Gateway Timeout) response.
|
145
|
+
#
|
146
|
+
# Servers SHOULD send the must-revalidate directive if and only if
|
147
|
+
# failure to revalidate a request on the entity could result in
|
148
|
+
# incorrect operation, such as a silently unexecuted financial
|
149
|
+
# transaction. Recipients MUST NOT take any automated action that
|
150
|
+
# violates this directive, and MUST NOT automatically provide an
|
151
|
+
# unvalidated copy of the entity if revalidation fails.
|
152
|
+
def must_revalidate?
|
153
|
+
self['must-revalidate']
|
154
|
+
end
|
155
|
+
|
156
|
+
# The proxy-revalidate directive has the same meaning as the must-
|
157
|
+
# revalidate directive, except that it does not apply to non-shared
|
158
|
+
# user agent caches. It can be used on a response to an
|
159
|
+
# authenticated request to permit the user's cache to store and
|
160
|
+
# later return the response without needing to revalidate it (since
|
161
|
+
# it has already been authenticated once by that user), while still
|
162
|
+
# requiring proxies that service many users to revalidate each time
|
163
|
+
# (in order to make sure that each user has been authenticated).
|
164
|
+
# Note that such authenticated responses also need the public cache
|
165
|
+
# control directive in order to allow them to be cached at all.
|
166
|
+
def proxy_revalidate?
|
167
|
+
self['proxy-revalidate']
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_s
|
171
|
+
bools, vals = [], []
|
172
|
+
each do |key,value|
|
173
|
+
if value == true
|
174
|
+
bools << key
|
175
|
+
elsif value
|
176
|
+
vals << "#{key}=#{value}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
(bools.sort + vals.sort).join(', ')
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def parse(value)
|
184
|
+
return if value.nil? || value.empty?
|
185
|
+
value.delete(' ').split(',').inject(self) do |hash,part|
|
186
|
+
name, value = part.split('=', 2)
|
187
|
+
hash[name.downcase] = (value || true) unless name.empty?
|
188
|
+
hash
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/lib/rack/cache/context.rb
CHANGED
@@ -1,40 +1,48 @@
|
|
1
|
-
require 'rack/cache/config'
|
2
1
|
require 'rack/cache/options'
|
3
|
-
require 'rack/cache/core'
|
4
2
|
require 'rack/cache/request'
|
5
3
|
require 'rack/cache/response'
|
6
4
|
require 'rack/cache/storage'
|
7
5
|
|
8
6
|
module Rack::Cache
|
9
7
|
# Implements Rack's middleware interface and provides the context for all
|
10
|
-
# cache logic
|
11
|
-
# to provide much of its core functionality.
|
12
|
-
|
8
|
+
# cache logic, including the core logic engine.
|
13
9
|
class Context
|
14
10
|
include Rack::Cache::Options
|
15
|
-
|
16
|
-
|
11
|
+
|
12
|
+
# Array of trace Symbols
|
13
|
+
attr_reader :trace
|
17
14
|
|
18
15
|
# The Rack application object immediately downstream.
|
19
16
|
attr_reader :backend
|
20
17
|
|
21
|
-
def initialize(backend, options={}
|
22
|
-
@errors = nil
|
23
|
-
@env = nil
|
18
|
+
def initialize(backend, options={})
|
24
19
|
@backend = backend
|
20
|
+
@trace = []
|
21
|
+
|
25
22
|
initialize_options options
|
26
|
-
|
27
|
-
|
23
|
+
yield self if block_given?
|
24
|
+
|
25
|
+
@private_header_keys =
|
26
|
+
private_headers.map { |name| "HTTP_#{name.upcase.tr('-', '_')}" }
|
28
27
|
end
|
29
28
|
|
30
|
-
# The
|
31
|
-
#
|
32
|
-
|
33
|
-
|
29
|
+
# The configured MetaStore instance. Changing the rack-cache.metastore
|
30
|
+
# value effects the result of this method immediately.
|
31
|
+
def metastore
|
32
|
+
uri = options['rack-cache.metastore']
|
33
|
+
storage.resolve_metastore_uri(uri)
|
34
|
+
end
|
35
|
+
|
36
|
+
# The configured EntityStore instance. Changing the rack-cache.entitystore
|
37
|
+
# value effects the result of this method immediately.
|
38
|
+
def entitystore
|
39
|
+
uri = options['rack-cache.entitystore']
|
40
|
+
storage.resolve_entitystore_uri(uri)
|
41
|
+
end
|
34
42
|
|
35
|
-
# The Rack call interface. The receiver acts as a prototype and runs
|
36
|
-
# request in a
|
37
|
-
# in the environment.
|
43
|
+
# The Rack call interface. The receiver acts as a prototype and runs
|
44
|
+
# each request in a dup object unless the +rack.run_once+ variable is
|
45
|
+
# set in the environment.
|
38
46
|
def call(env)
|
39
47
|
if env['rack.run_once']
|
40
48
|
call! env
|
@@ -43,53 +51,183 @@ module Rack::Cache
|
|
43
51
|
end
|
44
52
|
end
|
45
53
|
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
@
|
54
|
+
# The real Rack call interface. The caching logic is performed within
|
55
|
+
# the context of the receiver.
|
56
|
+
def call!(env)
|
57
|
+
@trace = []
|
58
|
+
@env = @default_options.merge(env)
|
59
|
+
@request = Request.new(@env.dup.freeze)
|
60
|
+
|
61
|
+
response =
|
62
|
+
if @request.get? || @request.head?
|
63
|
+
if !@env['HTTP_EXPECT']
|
64
|
+
lookup
|
65
|
+
else
|
66
|
+
pass
|
67
|
+
end
|
68
|
+
else
|
69
|
+
invalidate
|
70
|
+
end
|
71
|
+
|
72
|
+
# log trace and set X-Rack-Cache tracing header
|
73
|
+
trace = @trace.join(', ')
|
74
|
+
response.headers['X-Rack-Cache'] = trace
|
75
|
+
|
76
|
+
# write log message to rack.errors
|
77
|
+
if verbose?
|
78
|
+
message = "cache: [%s %s] %s\n" %
|
79
|
+
[@request.request_method, @request.fullpath, trace]
|
80
|
+
@env['rack.errors'].write(message)
|
81
|
+
end
|
82
|
+
|
83
|
+
# tidy up response a bit
|
84
|
+
response.not_modified! if not_modified?(response)
|
85
|
+
response.body = [] if @request.head?
|
86
|
+
response.to_a
|
51
87
|
end
|
52
88
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
89
|
+
private
|
90
|
+
|
91
|
+
# Record that an event took place.
|
92
|
+
def record(event)
|
93
|
+
@trace << event
|
57
94
|
end
|
58
95
|
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
96
|
+
# Does the request include authorization or other sensitive information
|
97
|
+
# that should cause the response to be considered private by default?
|
98
|
+
# Private responses are not stored in the cache.
|
99
|
+
def private_request?
|
100
|
+
@private_header_keys.any? { |key| @env.key?(key) }
|
64
101
|
end
|
65
102
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
def
|
69
|
-
|
70
|
-
|
103
|
+
# Determine if the #response validators (ETag, Last-Modified) matches
|
104
|
+
# a conditional value specified in #request.
|
105
|
+
def not_modified?(response)
|
106
|
+
response.etag_matches?(@request.env['HTTP_IF_NONE_MATCH']) ||
|
107
|
+
response.last_modified_at?(@request.env['HTTP_IF_MODIFIED_SINCE'])
|
71
108
|
end
|
72
109
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
110
|
+
# Whether the cache entry is "fresh enough" to satisfy the request.
|
111
|
+
def fresh_enough?(entry)
|
112
|
+
if entry.fresh?
|
113
|
+
if allow_revalidate? && max_age = @request.cache_control.max_age
|
114
|
+
max_age > 0 && max_age >= entry.age
|
115
|
+
else
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
79
119
|
end
|
80
120
|
|
81
|
-
|
82
|
-
|
121
|
+
# Delegate the request to the backend and create the response.
|
122
|
+
def forward
|
123
|
+
Response.new(*backend.call(@env))
|
83
124
|
end
|
84
125
|
|
85
|
-
|
86
|
-
|
126
|
+
# The request is sent to the backend, and the backend's response is sent
|
127
|
+
# to the client, but is not entered into the cache.
|
128
|
+
def pass
|
129
|
+
record :pass
|
130
|
+
forward
|
87
131
|
end
|
88
132
|
|
89
|
-
|
90
|
-
|
91
|
-
|
133
|
+
# Invalidate POST, PUT, DELETE and all methods not understood by this cache
|
134
|
+
# See RFC2616 13.10
|
135
|
+
def invalidate
|
136
|
+
record :invalidate
|
137
|
+
metastore.invalidate(@request, entitystore)
|
138
|
+
pass
|
92
139
|
end
|
93
|
-
end
|
94
140
|
|
141
|
+
# Try to serve the response from cache. When a matching cache entry is
|
142
|
+
# found and is fresh, use it as the response without forwarding any
|
143
|
+
# request to the backend. When a matching cache entry is found but is
|
144
|
+
# stale, attempt to #validate the entry with the backend using conditional
|
145
|
+
# GET. When no matching cache entry is found, trigger #miss processing.
|
146
|
+
def lookup
|
147
|
+
if @request.no_cache? && allow_reload?
|
148
|
+
record :reload
|
149
|
+
fetch
|
150
|
+
elsif entry = metastore.lookup(@request, entitystore)
|
151
|
+
if fresh_enough?(entry)
|
152
|
+
record :fresh
|
153
|
+
entry.headers['Age'] = entry.age.to_s
|
154
|
+
entry
|
155
|
+
else
|
156
|
+
record :stale
|
157
|
+
validate(entry)
|
158
|
+
end
|
159
|
+
else
|
160
|
+
record :miss
|
161
|
+
fetch
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Validate that the cache entry is fresh. The original request is used
|
166
|
+
# as a template for a conditional GET request with the backend.
|
167
|
+
def validate(entry)
|
168
|
+
# send no head requests because we want content
|
169
|
+
@env['REQUEST_METHOD'] = 'GET'
|
170
|
+
|
171
|
+
# add our cached validators to the environment
|
172
|
+
@env['HTTP_IF_MODIFIED_SINCE'] = entry.last_modified
|
173
|
+
@env['HTTP_IF_NONE_MATCH'] = entry.etag
|
174
|
+
|
175
|
+
backend_response = forward
|
176
|
+
|
177
|
+
response =
|
178
|
+
if backend_response.status == 304
|
179
|
+
record :valid
|
180
|
+
entry = entry.dup
|
181
|
+
entry.headers.delete('Date')
|
182
|
+
%w[Date Expires Cache-Control ETag Last-Modified].each do |name|
|
183
|
+
next unless value = backend_response.headers[name]
|
184
|
+
entry.headers[name] = value
|
185
|
+
end
|
186
|
+
entry
|
187
|
+
else
|
188
|
+
record :invalid
|
189
|
+
backend_response
|
190
|
+
end
|
191
|
+
|
192
|
+
store(response) if response.cacheable?
|
193
|
+
|
194
|
+
response
|
195
|
+
end
|
196
|
+
|
197
|
+
# The cache missed or a reload is required. Forward the request to the
|
198
|
+
# backend and determine whether the response should be stored.
|
199
|
+
def fetch
|
200
|
+
# send no head requests because we want content
|
201
|
+
@env['REQUEST_METHOD'] = 'GET'
|
202
|
+
|
203
|
+
# avoid that the backend sends no content
|
204
|
+
@env.delete('HTTP_IF_MODIFIED_SINCE')
|
205
|
+
@env.delete('HTTP_IF_NONE_MATCH')
|
206
|
+
|
207
|
+
response = forward
|
208
|
+
|
209
|
+
# Mark the response as explicitly private if any of the private
|
210
|
+
# request headers are present and the response was not explicitly
|
211
|
+
# declared public.
|
212
|
+
if private_request? && !response.cache_control.public?
|
213
|
+
response.private = true
|
214
|
+
elsif default_ttl > 0 && response.ttl.nil? && !response.cache_control.must_revalidate?
|
215
|
+
# assign a default TTL for the cache entry if none was specified in
|
216
|
+
# the response; the must-revalidate cache control directive disables
|
217
|
+
# default ttl assigment.
|
218
|
+
response.ttl = default_ttl
|
219
|
+
end
|
220
|
+
|
221
|
+
store(response) if response.cacheable?
|
222
|
+
|
223
|
+
response
|
224
|
+
end
|
225
|
+
|
226
|
+
# Write the response to the cache.
|
227
|
+
def store(response)
|
228
|
+
record :store
|
229
|
+
metastore.store(@request, response, entitystore)
|
230
|
+
response.headers['Age'] = response.age.to_s
|
231
|
+
end
|
232
|
+
end
|
95
233
|
end
|