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.

Files changed (44) hide show
  1. data/CHANGES +43 -0
  2. data/README +18 -9
  3. data/Rakefile +1 -14
  4. data/TODO +13 -14
  5. data/doc/configuration.markdown +7 -153
  6. data/doc/faq.markdown +8 -0
  7. data/doc/index.markdown +7 -9
  8. data/example/sinatra/app.rb +25 -0
  9. data/example/sinatra/views/index.erb +44 -0
  10. data/lib/rack/cache.rb +5 -11
  11. data/lib/rack/cache/cachecontrol.rb +193 -0
  12. data/lib/rack/cache/context.rb +190 -52
  13. data/lib/rack/cache/entitystore.rb +10 -4
  14. data/lib/rack/cache/key.rb +52 -0
  15. data/lib/rack/cache/metastore.rb +52 -16
  16. data/lib/rack/cache/options.rb +60 -39
  17. data/lib/rack/cache/request.rb +11 -15
  18. data/lib/rack/cache/response.rb +221 -30
  19. data/lib/rack/cache/storage.rb +1 -2
  20. data/rack-cache.gemspec +9 -15
  21. data/test/cache_test.rb +9 -6
  22. data/test/cachecontrol_test.rb +139 -0
  23. data/test/context_test.rb +251 -169
  24. data/test/entitystore_test.rb +12 -11
  25. data/test/key_test.rb +50 -0
  26. data/test/metastore_test.rb +57 -14
  27. data/test/options_test.rb +11 -0
  28. data/test/request_test.rb +19 -0
  29. data/test/response_test.rb +164 -23
  30. data/test/spec_setup.rb +7 -0
  31. metadata +12 -20
  32. data/doc/events.dot +0 -27
  33. data/lib/rack/cache/config.rb +0 -65
  34. data/lib/rack/cache/config/busters.rb +0 -16
  35. data/lib/rack/cache/config/default.rb +0 -133
  36. data/lib/rack/cache/config/no-cache.rb +0 -13
  37. data/lib/rack/cache/core.rb +0 -299
  38. data/lib/rack/cache/headers.rb +0 -325
  39. data/lib/rack/utils/environment_headers.rb +0 -78
  40. data/test/config_test.rb +0 -66
  41. data/test/core_test.rb +0 -84
  42. data/test/environment_headers_test.rb +0 -69
  43. data/test/headers_test.rb +0 -298
  44. data/test/logging_test.rb +0 -45
@@ -1,325 +0,0 @@
1
- require 'set'
2
- require 'rack/utils/environment_headers'
3
-
4
- module Rack::Cache
5
- # Generic HTTP header helper methods. Provides access to headers that can be
6
- # included in requests and responses. This can be mixed into any object that
7
- # responds to #headers by returning a Hash.
8
-
9
- module Headers
10
- # Determine if any of the header names exist:
11
- # if header?('Authorization', 'Cookie')
12
- # ...
13
- # end
14
- def header?(*names)
15
- names.any? { |name| headers.include?(name) }
16
- end
17
-
18
- # A Hash of name=value pairs that correspond to the Cache-Control header.
19
- # Valueless parameters (e.g., must-revalidate, no-store) have a Hash value
20
- # of true. This method always returns a Hash, empty if no Cache-Control
21
- # header is present.
22
- def cache_control
23
- @cache_control ||=
24
- headers['Cache-Control'].to_s.split(/\s*[,;]\s*/).inject({}) {|hash,token|
25
- name, value = token.split(/\s*=\s*/, 2)
26
- hash[name.downcase] = (value || true) unless name.empty?
27
- hash
28
- }.freeze
29
- end
30
-
31
- # Set the Cache-Control header to the values specified by the Hash. See
32
- # the #cache_control method for information on expected Hash structure.
33
- def cache_control=(hash)
34
- value =
35
- hash.collect { |key,value|
36
- next nil unless value
37
- next key if value == true
38
- "#{key}=#{value}"
39
- }.compact.join(', ')
40
- if value.empty?
41
- headers.delete('Cache-Control')
42
- @cache_control = {}
43
- else
44
- headers['Cache-Control'] = value
45
- @cache_control = hash.dup.freeze
46
- end
47
- end
48
-
49
- # The literal value of the ETag HTTP header or nil if no ETag is specified.
50
- def etag
51
- headers['Etag']
52
- end
53
- end
54
-
55
- # HTTP request header helpers. When included in Rack::Cache::Request, headers
56
- # may be accessed by their standard RFC 2616 names using the #headers Hash.
57
- module RequestHeaders
58
- include Rack::Cache::Headers
59
-
60
- # A Hash-like object providing access to HTTP request headers.
61
- def headers
62
- @headers ||= Rack::Utils::EnvironmentHeaders.new(env)
63
- end
64
-
65
- # The literal value of the If-Modified-Since request header or nil when
66
- # no If-Modified-Since header is present.
67
- def if_modified_since
68
- headers['If-Modified-Since']
69
- end
70
-
71
- # The literal value of the If-None-Match request header or nil when
72
- # no If-None-Match header is present.
73
- def if_none_match
74
- headers['If-None-Match']
75
- end
76
- end
77
-
78
- # HTTP response header helper methods.
79
- module ResponseHeaders
80
- include Rack::Cache::Headers
81
-
82
- # Status codes of responses that MAY be stored by a cache or used in reply
83
- # to a subsequent request.
84
- #
85
- # http://tools.ietf.org/html/rfc2616#section-13.4
86
- CACHEABLE_RESPONSE_CODES = [
87
- 200, # OK
88
- 203, # Non-Authoritative Information
89
- 300, # Multiple Choices
90
- 301, # Moved Permanently
91
- 302, # Found
92
- 404, # Not Found
93
- 410 # Gone
94
- ].to_set
95
-
96
- # Determine if the response is "fresh". Fresh responses may be served from
97
- # cache without any interaction with the origin. A response is considered
98
- # fresh when it includes a Cache-Control/max-age indicator or Expiration
99
- # header and the calculated age is less than the freshness lifetime.
100
- def fresh?
101
- ttl && ttl > 0
102
- end
103
-
104
- # Determine if the response is "stale". Stale responses must be validated
105
- # with the origin before use. This is the inverse of #fresh?.
106
- def stale?
107
- !fresh?
108
- end
109
-
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
- #
114
- # Responses with neither a freshness lifetime (Expires, max-age) nor cache
115
- # validator (Last-Modified, Etag) are considered uncacheable.
116
- def cacheable?
117
- return false unless CACHEABLE_RESPONSE_CODES.include?(status)
118
- return false if no_store? || private?
119
- validateable? || fresh?
120
- end
121
-
122
- # The response includes specific information about its freshness. True when
123
- # a +Cache-Control+ header with +max-age+ value is present or when the
124
- # +Expires+ header is set.
125
- def freshness_information?
126
- header?('Expires') ||
127
- !!(cache_control['s-maxage'] || cache_control['max-age'])
128
- end
129
-
130
- # Determine if the response includes headers that can be used to validate
131
- # the response with the origin using a conditional GET request.
132
- def validateable?
133
- header?('Last-Modified') || header?('Etag')
134
- end
135
-
136
- # Indicates that the response should not be served from cache without first
137
- # revalidating with the origin. Note that this does not necessary imply that
138
- # a caching agent ought not store the response in its cache.
139
- def no_cache?
140
- cache_control['no-cache']
141
- end
142
-
143
- # Indicates that the response should not be stored under any circumstances.
144
- def no_store?
145
- cache_control['no-store']
146
- end
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
-
184
- # The date, as specified by the Date header. When no Date header is present,
185
- # set the Date header to Time.now and return.
186
- def date
187
- if date = headers['Date']
188
- Time.httpdate(date)
189
- else
190
- headers['Date'] = now.httpdate unless headers.frozen?
191
- now
192
- end
193
- end
194
-
195
- # The age of the response.
196
- def age
197
- [(now - date).to_i, 0].max
198
- end
199
-
200
- # The number of seconds after the time specified in the response's Date
201
- # header when the the response should no longer be considered fresh. First
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.
205
- def max_age
206
- if age = (cache_control['s-maxage'] || cache_control['max-age'])
207
- age.to_i
208
- elsif headers['Expires']
209
- Time.httpdate(headers['Expires']) - date
210
- end
211
- end
212
-
213
- # The number of seconds after which the response should no longer
214
- # be considered fresh. Sets the Cache-Control max-age directive.
215
- def max_age=(value)
216
- self.cache_control = cache_control.merge('max-age' => value.to_s)
217
- end
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
-
225
- # The Time when the response should be considered stale. With a
226
- # Cache-Control/max-age value is present, this is calculated by adding the
227
- # number of seconds specified to the responses #date value. Falls back to
228
- # the time specified in the Expires header or returns nil if neither is
229
- # present.
230
- def expires_at
231
- if max_age = (cache_control['s-maxage'] || cache_control['max-age'])
232
- date + max_age.to_i
233
- elsif time = headers['Expires']
234
- Time.httpdate(time)
235
- end
236
- end
237
-
238
- # The response's time-to-live in seconds, or nil when no freshness
239
- # information is present in the response. When the responses #ttl
240
- # is <= 0, the response may not be served from cache without first
241
- # revalidating with the origin.
242
- def ttl
243
- max_age - age if max_age
244
- end
245
-
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.
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)
255
- self.max_age = age + seconds
256
- end
257
-
258
- # The String value of the Last-Modified header exactly as it appears
259
- # in the response (i.e., no date parsing / conversion is performed).
260
- def last_modified
261
- headers['Last-Modified']
262
- end
263
-
264
- # Determine if the response was last modified at the time provided.
265
- # time_value is the exact string provided in an origin response's
266
- # Last-Modified header.
267
- def last_modified_at?(time_value)
268
- time_value && last_modified == time_value
269
- end
270
-
271
- # Determine if response's ETag matches the etag value provided. Return
272
- # false when either value is nil.
273
- def etag_matches?(etag)
274
- etag && self.etag == etag
275
- end
276
-
277
- # Headers that MUST NOT be included with 304 Not Modified responses.
278
- #
279
- # http://tools.ietf.org/html/rfc2616#section-10.3.5
280
- NOT_MODIFIED_OMIT_HEADERS = %w[
281
- Allow
282
- Content-Encoding
283
- Content-Language
284
- Content-Length
285
- Content-Md5
286
- Content-Type
287
- Last-Modified
288
- ].to_set
289
-
290
- # Modify the response so that it conforms to the rules defined for
291
- # '304 Not Modified'. This sets the status, removes the body, and
292
- # discards any headers that MUST NOT be included in 304 responses.
293
- #
294
- # http://tools.ietf.org/html/rfc2616#section-10.3.5
295
- def not_modified!
296
- self.status = 304
297
- self.body = []
298
- NOT_MODIFIED_OMIT_HEADERS.each { |name| headers.delete(name) }
299
- nil
300
- end
301
-
302
- # The literal value of the Vary header, or nil when no header is present.
303
- def vary
304
- headers['Vary']
305
- end
306
-
307
- # Does the response include a Vary header?
308
- def vary?
309
- ! vary.nil?
310
- end
311
-
312
- # An array of header names given in the Vary header or an empty
313
- # array when no Vary header is present.
314
- def vary_header_names
315
- return [] unless vary = headers['Vary']
316
- vary.split(/[\s,]+/)
317
- end
318
-
319
- private
320
- def now
321
- @now ||= Time.now
322
- end
323
- end
324
-
325
- end
@@ -1,78 +0,0 @@
1
- require 'rack/utils'
2
-
3
- module Rack::Utils #:nodoc:
4
- # A facade over a Rack Environment Hash that gives access to headers
5
- # using their normal RFC 2616 names.
6
-
7
- class EnvironmentHeaders
8
- include Enumerable
9
-
10
- # Create the facade over the given Rack Environment Hash.
11
- def initialize(env)
12
- @env = env
13
- end
14
-
15
- # Return the value of the specified header. The +header_name+ should
16
- # be as specified by RFC 2616 (e.g., "Content-Type", "Accept", etc.)
17
- def [](header_name)
18
- @env[env_name(header_name)]
19
- end
20
-
21
- # Set the value of the specified header. The +header_name+ should
22
- # be as specified by RFC 2616 (e.g., "Content-Type", "Accept", etc.)
23
- def []=(header_name, value)
24
- @env[env_name(header_name)] = value
25
- end
26
-
27
- # Determine if the underlying Rack Environment includes a header
28
- # of the given name.
29
- def include?(header_name)
30
- @env.include?(env_name(header_name))
31
- end
32
-
33
- # Iterate over all headers yielding a (name, value) tuple to the
34
- # block. Rack Environment keys that do not map to an header are not
35
- # included.
36
- def each
37
- @env.each do |key,value|
38
- next unless key =~ /^(HTTP_|CONTENT_)/
39
- yield header_name(key), value
40
- end
41
- end
42
-
43
- # Delete the entry in the underlying Rack Environment that corresponds
44
- # to the given RFC 2616 header name.
45
- def delete(header_name)
46
- @env.delete(env_name(header_name))
47
- end
48
-
49
- # Return the underlying Rack Environment Hash.
50
- def to_env
51
- @env
52
- end
53
-
54
- alias_method :to_hash, :to_env
55
-
56
- private
57
-
58
- # Return the Rack Environment key for the given RFC 2616 header name.
59
- def env_name(header_name)
60
- case header_name = header_name.upcase
61
- when 'CONTENT-TYPE' then 'CONTENT_TYPE'
62
- when 'CONTENT-LENGTH' then 'CONTENT_LENGTH'
63
- else "HTTP_#{header_name.tr('-', '_')}"
64
- end
65
- end
66
-
67
- # Return the RFC 2616 header name for the given Rack Environment key.
68
- def header_name(env_name)
69
- env_name.
70
- sub(/^HTTP_/, '').
71
- downcase.
72
- capitalize.
73
- gsub(/_(.)/) { '-' + $1.upcase }
74
- end
75
-
76
- end
77
-
78
- end
data/test/config_test.rb DELETED
@@ -1,66 +0,0 @@
1
- require "#{File.dirname(__FILE__)}/spec_setup"
2
- require 'rack/cache/config'
3
-
4
- class MockConfig
5
- include Rack::Cache::Config
6
- def configured!
7
- @configured = true
8
- end
9
- def configured?
10
- @configured
11
- end
12
- end
13
-
14
- describe 'Rack::Cache::Config' do
15
- before :each do
16
- @config = MockConfig.new
17
- @tempdir = create_temp_directory
18
- $:.unshift @tempdir
19
- end
20
- after :each do
21
- @config = nil
22
- $:.shift if $:.first == @tempdir
23
- remove_entry_secure @tempdir
24
- end
25
-
26
- def make_temp_file(filename, data='configured!')
27
- create_temp_file @tempdir, filename, data
28
- end
29
-
30
- it 'loads config files from the load path when file is relative' do
31
- make_temp_file 'foo/bar.rb'
32
- @config.import 'foo/bar.rb'
33
- @config.should.be.configured
34
- end
35
- it 'assumes a .rb file extension when no file extension exists' do
36
- make_temp_file 'foo/bar.rb'
37
- @config.import 'foo/bar'
38
- @config.should.be.configured
39
- end
40
- it 'does not assume a .rb file extension when other file extension exists' do
41
- make_temp_file 'foo/bar.conf'
42
- @config.import 'foo/bar.conf'
43
- @config.should.be.configured
44
- end
45
- it 'should locate files with absolute path names' do
46
- make_temp_file 'foo/bar.rb'
47
- @config.import File.join(@tempdir, 'foo/bar.rb')
48
- @config.should.be.configured
49
- end
50
- it 'raises a LoadError when the file cannot be found' do
51
- assert_raises(LoadError) {
52
- @config.import('this/file/is/very-likely/not/to/exist.rb')
53
- }
54
- end
55
- it 'executes within the context of the object instance' do
56
- make_temp_file 'foo/bar.rb',
57
- 'self.should.be.kind_of Rack::Cache::Config ; configured!'
58
- @config.import 'foo/bar'
59
- @config.should.be.configured
60
- end
61
- it 'does not import files more than once' do
62
- make_temp_file 'foo/bar.rb', "import 'foo/bar'"
63
- @config.import('foo/bar').should.be true
64
- @config.import('foo/bar').should.be false
65
- end
66
- end