rack-cache 0.3.0 → 0.4

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.

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