rtomayko-rack-cache 0.3.0 → 0.3.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +41 -0
- data/README +0 -1
- data/TODO +14 -10
- data/doc/configuration.markdown +7 -153
- data/doc/index.markdown +1 -3
- 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 +188 -51
- 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 +29 -13
- 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 -14
- data/test/cache_test.rb +4 -1
- data/test/cachecontrol_test.rb +139 -0
- data/test/context_test.rb +198 -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 +6 -0
- metadata +13 -19
- 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/options.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
require 'rack'
|
1
|
+
require 'rack/cache/key'
|
2
2
|
require 'rack/cache/storage'
|
3
3
|
|
4
4
|
module Rack::Cache
|
5
|
+
|
5
6
|
# Configuration options and utility methods for option access. Rack::Cache
|
6
7
|
# uses the Rack Environment to store option values. All options documented
|
7
8
|
# below are stored in the Rack Environment as "rack-cache.<option>", where
|
8
9
|
# <option> is the option name.
|
9
10
|
#
|
10
|
-
# The #set method can be used
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
|
11
|
+
# The #set method can be used to configure a option values. When #set is
|
12
|
+
# called outside of request scope, the value applies to all requests; when
|
13
|
+
# called from within a request context, applies only to the request being
|
14
|
+
# processed.
|
15
15
|
module Options
|
16
16
|
class << self
|
17
17
|
private
|
@@ -44,6 +44,19 @@ module Rack::Cache
|
|
44
44
|
# recommended.
|
45
45
|
option_accessor :metastore
|
46
46
|
|
47
|
+
# A custom cache key generator, which can be anything that responds to :call.
|
48
|
+
# By default, this is the Rack::Cache::Key class, but you can implement your
|
49
|
+
# own generator. A cache key generator gets passed a request and generates the
|
50
|
+
# appropriate cache key.
|
51
|
+
#
|
52
|
+
# In addition to setting the generator to an object, you can just pass a block
|
53
|
+
# instead, which will act as the cache key generator:
|
54
|
+
#
|
55
|
+
# set :cache_key do |request|
|
56
|
+
# request.fullpath.replace(/\//, '-')
|
57
|
+
# end
|
58
|
+
option_accessor :cache_key
|
59
|
+
|
47
60
|
# A URI specifying the entity-store implement that should be used to store
|
48
61
|
# response bodies. See the metastore option for information on supported URI
|
49
62
|
# schemes.
|
@@ -88,8 +101,10 @@ module Rack::Cache
|
|
88
101
|
# exactly as specified. The +option+ argument may also be a Hash in
|
89
102
|
# which case each key/value pair is merged into the environment as if
|
90
103
|
# the #set method were called on each.
|
91
|
-
def set(option, value=self)
|
92
|
-
if
|
104
|
+
def set(option, value=self, &block)
|
105
|
+
if block_given?
|
106
|
+
write_option option, block
|
107
|
+
elsif value == self
|
93
108
|
self.options = option.to_hash
|
94
109
|
else
|
95
110
|
write_option option, value
|
@@ -116,11 +131,12 @@ module Rack::Cache
|
|
116
131
|
private
|
117
132
|
def initialize_options(options={})
|
118
133
|
@default_options = {
|
119
|
-
'rack-cache.
|
120
|
-
'rack-cache.
|
121
|
-
'rack-cache.
|
122
|
-
'rack-cache.
|
123
|
-
'rack-cache.
|
134
|
+
'rack-cache.cache_key' => Key,
|
135
|
+
'rack-cache.verbose' => true,
|
136
|
+
'rack-cache.storage' => Rack::Cache::Storage.instance,
|
137
|
+
'rack-cache.metastore' => 'heap:/',
|
138
|
+
'rack-cache.entitystore' => 'heap:/',
|
139
|
+
'rack-cache.default_ttl' => 0,
|
124
140
|
'rack-cache.private_headers' => ['Authorization', 'Cookie']
|
125
141
|
}
|
126
142
|
self.options = options
|
data/lib/rack/cache/request.rb
CHANGED
@@ -1,19 +1,15 @@
|
|
1
1
|
require 'rack/request'
|
2
|
-
require 'rack/cache/
|
3
|
-
require 'rack/utils/environment_headers'
|
2
|
+
require 'rack/cache/cachecontrol'
|
4
3
|
|
5
4
|
module Rack::Cache
|
5
|
+
|
6
6
|
# Provides access to the HTTP request. The +request+ and +original_request+
|
7
7
|
# objects exposed by the Core caching engine are instances of this class.
|
8
8
|
#
|
9
9
|
# Request objects respond to a variety of convenience methods, including
|
10
10
|
# everything defined by Rack::Request as well as the Headers and
|
11
11
|
# RequestHeaders modules.
|
12
|
-
|
13
12
|
class Request < Rack::Request
|
14
|
-
include Rack::Cache::Headers
|
15
|
-
include Rack::Cache::RequestHeaders
|
16
|
-
|
17
13
|
# The HTTP request method. This is the standard implementation of this
|
18
14
|
# method but is respecified here due to libraries that attempt to modify
|
19
15
|
# the behavior to respect POST tunnel method specifiers. We always want
|
@@ -22,16 +18,16 @@ module Rack::Cache
|
|
22
18
|
@env['REQUEST_METHOD']
|
23
19
|
end
|
24
20
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
# ...
|
29
|
-
# end
|
30
|
-
def request_method?(*methods)
|
31
|
-
method = request_method
|
32
|
-
methods.any? { |test| test.to_s.upcase == method }
|
21
|
+
# A CacheControl instance based on the request's Cache-Control header.
|
22
|
+
def cache_control
|
23
|
+
@cache_control ||= CacheControl.new(env['HTTP_CACHE_CONTROL'])
|
33
24
|
end
|
34
25
|
|
35
|
-
|
26
|
+
# True when the Cache-Control/no-cache directive is present or the
|
27
|
+
# Pragma header is set to no-cache.
|
28
|
+
def no_cache?
|
29
|
+
cache_control['no-cache'] ||
|
30
|
+
env['HTTP_PRAGMA'] == 'no-cache'
|
31
|
+
end
|
36
32
|
end
|
37
33
|
end
|
data/lib/rack/cache/response.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
require 'time'
|
1
2
|
require 'set'
|
2
|
-
require 'rack/
|
3
|
+
require 'rack/response'
|
4
|
+
require 'rack/utils'
|
5
|
+
require 'rack/cache/cachecontrol'
|
3
6
|
|
4
7
|
module Rack::Cache
|
8
|
+
|
5
9
|
# Provides access to the response generated by the downstream application. The
|
6
10
|
# +response+, +original_response+, and +entry+ objects exposed by the Core
|
7
11
|
# caching engine are instances of this class.
|
@@ -14,21 +18,14 @@ module Rack::Cache
|
|
14
18
|
# not perform many of the same initialization and finalization tasks. For
|
15
19
|
# example, the body is not slurped during initialization and there are no
|
16
20
|
# facilities for generating response output.
|
17
|
-
|
18
21
|
class Response
|
19
22
|
include Rack::Response::Helpers
|
20
|
-
include Rack::Cache::Headers
|
21
|
-
include Rack::Cache::ResponseHeaders
|
22
23
|
|
23
|
-
#
|
24
|
-
attr_accessor :status
|
24
|
+
# Rack response tuple accessors.
|
25
|
+
attr_accessor :status, :headers, :body
|
25
26
|
|
26
|
-
# The
|
27
|
-
|
28
|
-
attr_accessor :body
|
29
|
-
|
30
|
-
# The response headers.
|
31
|
-
attr_reader :headers
|
27
|
+
# The time when the Response object was instantiated.
|
28
|
+
attr_reader :now
|
32
29
|
|
33
30
|
# Create a Response instance given the response status code, header hash,
|
34
31
|
# and body.
|
@@ -37,7 +34,7 @@ module Rack::Cache
|
|
37
34
|
@headers = Rack::Utils::HeaderHash.new(headers)
|
38
35
|
@body = body
|
39
36
|
@now = Time.now
|
40
|
-
@headers['Date'] ||= now.httpdate
|
37
|
+
@headers['Date'] ||= @now.httpdate
|
41
38
|
end
|
42
39
|
|
43
40
|
def initialize_copy(other)
|
@@ -45,32 +42,226 @@ module Rack::Cache
|
|
45
42
|
@headers = other.headers.dup
|
46
43
|
end
|
47
44
|
|
48
|
-
# Return the
|
49
|
-
def
|
50
|
-
headers
|
45
|
+
# Return the status, headers, and body in a three-tuple.
|
46
|
+
def to_a
|
47
|
+
[status, headers.to_hash, body]
|
51
48
|
end
|
52
49
|
|
53
|
-
#
|
54
|
-
|
55
|
-
|
50
|
+
# Status codes of responses that MAY be stored by a cache or used in reply
|
51
|
+
# to a subsequent request.
|
52
|
+
#
|
53
|
+
# http://tools.ietf.org/html/rfc2616#section-13.4
|
54
|
+
CACHEABLE_RESPONSE_CODES = [
|
55
|
+
200, # OK
|
56
|
+
203, # Non-Authoritative Information
|
57
|
+
300, # Multiple Choices
|
58
|
+
301, # Moved Permanently
|
59
|
+
302, # Found
|
60
|
+
404, # Not Found
|
61
|
+
410 # Gone
|
62
|
+
].to_set
|
63
|
+
|
64
|
+
# A Hash of name=value pairs that correspond to the Cache-Control header.
|
65
|
+
# Valueless parameters (e.g., must-revalidate, no-store) have a Hash value
|
66
|
+
# of true. This method always returns a Hash, empty if no Cache-Control
|
67
|
+
# header is present.
|
68
|
+
def cache_control
|
69
|
+
@cache_control ||= CacheControl.new(headers['Cache-Control'])
|
56
70
|
end
|
57
71
|
|
58
|
-
#
|
59
|
-
|
60
|
-
|
72
|
+
# Set the Cache-Control header to the values specified by the Hash. See
|
73
|
+
# the #cache_control method for information on expected Hash structure.
|
74
|
+
def cache_control=(value)
|
75
|
+
if value.respond_to? :to_hash
|
76
|
+
cache_control.clear
|
77
|
+
cache_control.merge!(value)
|
78
|
+
value = cache_control.to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
if value.nil? || value.empty?
|
82
|
+
headers.delete('Cache-Control')
|
83
|
+
else
|
84
|
+
headers['Cache-Control'] = value
|
85
|
+
end
|
61
86
|
end
|
62
87
|
|
63
|
-
#
|
64
|
-
|
65
|
-
|
88
|
+
# Determine if the response is "fresh". Fresh responses may be served from
|
89
|
+
# cache without any interaction with the origin. A response is considered
|
90
|
+
# fresh when it includes a Cache-Control/max-age indicator or Expiration
|
91
|
+
# header and the calculated age is less than the freshness lifetime.
|
92
|
+
def fresh?
|
93
|
+
ttl && ttl > 0
|
66
94
|
end
|
67
95
|
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
96
|
+
# Determine if the response is worth caching under any circumstance. Responses
|
97
|
+
# marked "private" with an explicit Cache-Control directive are considered
|
98
|
+
# uncacheable
|
99
|
+
#
|
100
|
+
# Responses with neither a freshness lifetime (Expires, max-age) nor cache
|
101
|
+
# validator (Last-Modified, ETag) are considered uncacheable.
|
102
|
+
def cacheable?
|
103
|
+
return false unless CACHEABLE_RESPONSE_CODES.include?(status)
|
104
|
+
return false if cache_control.no_store? || cache_control.private?
|
105
|
+
validateable? || fresh?
|
72
106
|
end
|
73
107
|
|
74
|
-
|
108
|
+
# Determine if the response includes headers that can be used to validate
|
109
|
+
# the response with the origin using a conditional GET request.
|
110
|
+
def validateable?
|
111
|
+
headers.key?('Last-Modified') || headers.key?('ETag')
|
112
|
+
end
|
113
|
+
|
114
|
+
# Mark the response "private", making it ineligible for serving other
|
115
|
+
# clients.
|
116
|
+
def private=(value)
|
117
|
+
value = value ? true : nil
|
118
|
+
self.cache_control = cache_control.
|
119
|
+
merge('public' => !value, 'private' => value)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Indicates that the cache must not serve a stale response in any
|
123
|
+
# circumstance without first revalidating with the origin. When present,
|
124
|
+
# the TTL of the response should not be overriden to be greater than the
|
125
|
+
# value provided by the origin.
|
126
|
+
def must_revalidate?
|
127
|
+
cache_control.must_revalidate || cache_control.proxy_revalidate
|
128
|
+
end
|
129
|
+
|
130
|
+
# Mark the response stale by setting the Age header to be equal to the
|
131
|
+
# maximum age of the response.
|
132
|
+
def expire!
|
133
|
+
headers['Age'] = max_age.to_s if fresh?
|
134
|
+
end
|
135
|
+
|
136
|
+
# The date, as specified by the Date header. When no Date header is present,
|
137
|
+
# set the Date header to Time.now and return.
|
138
|
+
def date
|
139
|
+
if date = headers['Date']
|
140
|
+
Time.httpdate(date)
|
141
|
+
else
|
142
|
+
headers['Date'] = now.httpdate unless headers.frozen?
|
143
|
+
now
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# The age of the response.
|
148
|
+
def age
|
149
|
+
(headers['Age'] || [(now - date).to_i, 0].max).to_i
|
150
|
+
end
|
151
|
+
|
152
|
+
# The number of seconds after the time specified in the response's Date
|
153
|
+
# header when the the response should no longer be considered fresh. First
|
154
|
+
# check for a s-maxage directive, then a max-age directive, and then fall
|
155
|
+
# back on an expires header; return nil when no maximum age can be
|
156
|
+
# established.
|
157
|
+
def max_age
|
158
|
+
cache_control.shared_max_age ||
|
159
|
+
cache_control.max_age ||
|
160
|
+
(expires && (expires - date))
|
161
|
+
end
|
162
|
+
|
163
|
+
# The value of the Expires header as a Time object.
|
164
|
+
def expires
|
165
|
+
headers['Expires'] && Time.httpdate(headers['Expires'])
|
166
|
+
end
|
75
167
|
|
168
|
+
# The number of seconds after which the response should no longer
|
169
|
+
# be considered fresh. Sets the Cache-Control max-age directive.
|
170
|
+
def max_age=(value)
|
171
|
+
self.cache_control = cache_control.merge('max-age' => value.to_s)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Like #max_age= but sets the s-maxage directive, which applies only
|
175
|
+
# to shared caches.
|
176
|
+
def shared_max_age=(value)
|
177
|
+
self.cache_control = cache_control.merge('s-maxage' => value.to_s)
|
178
|
+
end
|
179
|
+
|
180
|
+
# The response's time-to-live in seconds, or nil when no freshness
|
181
|
+
# information is present in the response. When the responses #ttl
|
182
|
+
# is <= 0, the response may not be served from cache without first
|
183
|
+
# revalidating with the origin.
|
184
|
+
def ttl
|
185
|
+
max_age - age if max_age
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set the response's time-to-live for shared caches to the specified number
|
189
|
+
# of seconds. This adjusts the Cache-Control/s-maxage directive.
|
190
|
+
def ttl=(seconds)
|
191
|
+
self.shared_max_age = age + seconds
|
192
|
+
end
|
193
|
+
|
194
|
+
# Set the response's time-to-live for private/client caches. This adjusts
|
195
|
+
# the Cache-Control/max-age directive.
|
196
|
+
def client_ttl=(seconds)
|
197
|
+
self.max_age = age + seconds
|
198
|
+
end
|
199
|
+
|
200
|
+
# The String value of the Last-Modified header exactly as it appears
|
201
|
+
# in the response (i.e., no date parsing / conversion is performed).
|
202
|
+
def last_modified
|
203
|
+
headers['Last-Modified']
|
204
|
+
end
|
205
|
+
|
206
|
+
# The literal value of ETag HTTP header or nil if no ETag is specified.
|
207
|
+
def etag
|
208
|
+
headers['ETag']
|
209
|
+
end
|
210
|
+
|
211
|
+
# Determine if the response was last modified at the time provided.
|
212
|
+
# time_value is the exact string provided in an origin response's
|
213
|
+
# Last-Modified header.
|
214
|
+
def last_modified_at?(time_value)
|
215
|
+
time_value && last_modified == time_value
|
216
|
+
end
|
217
|
+
|
218
|
+
# Determine if response's ETag matches the etag value provided. Return
|
219
|
+
# false when either value is nil.
|
220
|
+
def etag_matches?(etag)
|
221
|
+
etag && self.etag == etag
|
222
|
+
end
|
223
|
+
|
224
|
+
# Headers that MUST NOT be included with 304 Not Modified responses.
|
225
|
+
#
|
226
|
+
# http://tools.ietf.org/html/rfc2616#section-10.3.5
|
227
|
+
NOT_MODIFIED_OMIT_HEADERS = %w[
|
228
|
+
Allow
|
229
|
+
Content-Encoding
|
230
|
+
Content-Language
|
231
|
+
Content-Length
|
232
|
+
Content-MD5
|
233
|
+
Content-Type
|
234
|
+
Last-Modified
|
235
|
+
].to_set
|
236
|
+
|
237
|
+
# Modify the response so that it conforms to the rules defined for
|
238
|
+
# '304 Not Modified'. This sets the status, removes the body, and
|
239
|
+
# discards any headers that MUST NOT be included in 304 responses.
|
240
|
+
#
|
241
|
+
# http://tools.ietf.org/html/rfc2616#section-10.3.5
|
242
|
+
def not_modified!
|
243
|
+
self.status = 304
|
244
|
+
self.body = []
|
245
|
+
NOT_MODIFIED_OMIT_HEADERS.each { |name| headers.delete(name) }
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
|
249
|
+
# The literal value of the Vary header, or nil when no header is present.
|
250
|
+
def vary
|
251
|
+
headers['Vary']
|
252
|
+
end
|
253
|
+
|
254
|
+
# Does the response include a Vary header?
|
255
|
+
def vary?
|
256
|
+
! vary.nil?
|
257
|
+
end
|
258
|
+
|
259
|
+
# An array of header names given in the Vary header or an empty
|
260
|
+
# array when no Vary header is present.
|
261
|
+
def vary_header_names
|
262
|
+
return [] unless vary = headers['Vary']
|
263
|
+
vary.split(/[\s,]+/)
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
76
267
|
end
|
data/lib/rack/cache/storage.rb
CHANGED
@@ -3,11 +3,11 @@ require 'rack/cache/metastore'
|
|
3
3
|
require 'rack/cache/entitystore'
|
4
4
|
|
5
5
|
module Rack::Cache
|
6
|
+
|
6
7
|
# Maintains a collection of MetaStore and EntityStore instances keyed by
|
7
8
|
# URI. A single instance of this class can be used across a single process
|
8
9
|
# to ensure that only a single instance of a backing store is created per
|
9
10
|
# unique storage URI.
|
10
|
-
|
11
11
|
class Storage
|
12
12
|
def initialize
|
13
13
|
@metastores = {}
|
@@ -22,7 +22,6 @@ module Rack::Cache
|
|
22
22
|
@entitystores[uri.to_s] ||= create_store(EntityStore, uri)
|
23
23
|
end
|
24
24
|
|
25
|
-
# Clear store instances.
|
26
25
|
def clear
|
27
26
|
@metastores.clear
|
28
27
|
@entitystores.clear
|
data/rack-cache.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'rack-cache'
|
6
|
-
s.version = '0.3.
|
7
|
-
s.date = '
|
6
|
+
s.version = '0.3.9'
|
7
|
+
s.date = '2009-03-07'
|
8
8
|
|
9
9
|
s.description = "HTTP Caching for Rack"
|
10
10
|
s.summary = "HTTP Caching for Rack"
|
@@ -28,33 +28,28 @@ Gem::Specification.new do |s|
|
|
28
28
|
doc/rack-cache.css
|
29
29
|
doc/server.ru
|
30
30
|
doc/storage.markdown
|
31
|
+
example/sinatra/app.rb
|
32
|
+
example/sinatra/views/index.erb
|
31
33
|
lib/rack/cache.rb
|
32
|
-
lib/rack/cache/
|
33
|
-
lib/rack/cache/config/busters.rb
|
34
|
-
lib/rack/cache/config/default.rb
|
35
|
-
lib/rack/cache/config/no-cache.rb
|
34
|
+
lib/rack/cache/cachecontrol.rb
|
36
35
|
lib/rack/cache/context.rb
|
37
|
-
lib/rack/cache/core.rb
|
38
36
|
lib/rack/cache/entitystore.rb
|
39
|
-
lib/rack/cache/
|
37
|
+
lib/rack/cache/key.rb
|
40
38
|
lib/rack/cache/metastore.rb
|
41
39
|
lib/rack/cache/options.rb
|
42
40
|
lib/rack/cache/request.rb
|
43
41
|
lib/rack/cache/response.rb
|
44
42
|
lib/rack/cache/storage.rb
|
45
|
-
lib/rack/utils/environment_headers.rb
|
46
43
|
rack-cache.gemspec
|
47
44
|
test/cache_test.rb
|
48
|
-
test/
|
45
|
+
test/cachecontrol_test.rb
|
49
46
|
test/context_test.rb
|
50
|
-
test/core_test.rb
|
51
47
|
test/entitystore_test.rb
|
52
|
-
test/
|
53
|
-
test/headers_test.rb
|
54
|
-
test/logging_test.rb
|
48
|
+
test/key_test.rb
|
55
49
|
test/metastore_test.rb
|
56
50
|
test/options_test.rb
|
57
51
|
test/pony.jpg
|
52
|
+
test/request_test.rb
|
58
53
|
test/response_test.rb
|
59
54
|
test/spec_setup.rb
|
60
55
|
test/storage_test.rb
|