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.
@@ -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 within an event or a top-level configuration
11
- # block to configure a option values. When #set is called at the top-level,
12
- # the value applies to all requests; when called from within an event, the
13
- # values applies only to the request being processed.
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 value == self
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.verbose' => true,
120
- 'rack-cache.storage' => Rack::Cache::Storage.instance,
121
- 'rack-cache.metastore' => 'heap:/',
122
- 'rack-cache.entitystore' => 'heap:/',
123
- 'rack-cache.default_ttl' => 0,
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
@@ -1,19 +1,15 @@
1
1
  require 'rack/request'
2
- require 'rack/cache/headers'
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
- # Determine if the request's method matches any of the values
26
- # provided:
27
- # if request.request_method?('GET', 'POST')
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
- alias_method :method?, :request_method?
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
@@ -1,7 +1,11 @@
1
+ require 'time'
1
2
  require 'set'
2
- require 'rack/cache/headers'
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
- # The response's status code (integer).
24
- attr_accessor :status
24
+ # Rack response tuple accessors.
25
+ attr_accessor :status, :headers, :body
25
26
 
26
- # The response body. See the Rack spec for information on the behavior
27
- # required by this object.
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 value of the named response header.
49
- def [](header_name)
50
- headers[header_name]
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
- # Set a response header value.
54
- def []=(header_name, header_value)
55
- headers[header_name] = header_value
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
- # Called immediately after an object is loaded from the cache.
59
- def activate!
60
- headers['Age'] = age.to_i.to_s
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
- # Return the status, headers, and body in a three-tuple.
64
- def to_a
65
- [status, headers.to_hash, body]
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
- # Freezes
69
- def freeze
70
- @headers.freeze
71
- super
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
- end
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
@@ -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.0'
7
- s.date = '2008-12-28'
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/config.rb
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/headers.rb
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/config_test.rb
45
+ test/cachecontrol_test.rb
49
46
  test/context_test.rb
50
- test/core_test.rb
51
47
  test/entitystore_test.rb
52
- test/environment_headers_test.rb
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