josh-rack-cache 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,150 @@
1
+ require 'rack/cache/key'
2
+ require 'rack/cache/storage'
3
+
4
+ module Rack::Cache
5
+
6
+ # Configuration options and utility methods for option access. Rack::Cache
7
+ # uses the Rack Environment to store option values. All options documented
8
+ # below are stored in the Rack Environment as "rack-cache.<option>", where
9
+ # <option> is the option name.
10
+ module Options
11
+ def self.option_accessor(key)
12
+ name = option_name(key)
13
+ define_method(key) { || options[name] }
14
+ define_method("#{key}=") { |value| options[name] = value }
15
+ define_method("#{key}?") { || !! options[name] }
16
+ end
17
+
18
+ def option_name(key)
19
+ case key
20
+ when Symbol ; "rack-cache.#{key}"
21
+ when String ; key
22
+ else raise ArgumentError
23
+ end
24
+ end
25
+ module_function :option_name
26
+
27
+ # Enable verbose trace logging. This option is currently enabled by
28
+ # default but is likely to be disabled in a future release.
29
+ option_accessor :verbose
30
+
31
+ # The storage resolver. Defaults to the Rack::Cache.storage singleton instance
32
+ # of Rack::Cache::Storage. This object is responsible for resolving metastore
33
+ # and entitystore URIs to an implementation instances.
34
+ option_accessor :storage
35
+
36
+ # A URI specifying the meta-store implementation that should be used to store
37
+ # request/response meta information. The following URIs schemes are
38
+ # supported:
39
+ #
40
+ # * heap:/
41
+ # * file:/absolute/path or file:relative/path
42
+ # * memcached://localhost:11211[/namespace]
43
+ #
44
+ # If no meta store is specified the 'heap:/' store is assumed. This
45
+ # implementation has significant draw-backs so explicit configuration is
46
+ # recommended.
47
+ option_accessor :metastore
48
+
49
+ # A custom cache key generator, which can be anything that responds to :call.
50
+ # By default, this is the Rack::Cache::Key class, but you can implement your
51
+ # own generator. A cache key generator gets passed a request and generates the
52
+ # appropriate cache key.
53
+ #
54
+ # In addition to setting the generator to an object, you can just pass a block
55
+ # instead, which will act as the cache key generator:
56
+ #
57
+ # set :cache_key do |request|
58
+ # request.fullpath.replace(/\//, '-')
59
+ # end
60
+ option_accessor :cache_key
61
+
62
+ # A URI specifying the entity-store implementation that should be used to
63
+ # store response bodies. See the metastore option for information on
64
+ # supported URI schemes.
65
+ #
66
+ # If no entity store is specified the 'heap:/' store is assumed. This
67
+ # implementation has significant draw-backs so explicit configuration is
68
+ # recommended.
69
+ option_accessor :entitystore
70
+
71
+ # The number of seconds that a cache entry should be considered
72
+ # "fresh" when no explicit freshness information is provided in
73
+ # a response. Explicit Cache-Control or Expires headers
74
+ # override this value.
75
+ #
76
+ # Default: 0
77
+ option_accessor :default_ttl
78
+
79
+ # Set of request headers that trigger "private" cache-control behavior
80
+ # on responses that don't explicitly state whether the response is
81
+ # public or private via a Cache-Control directive. Applications that use
82
+ # cookies for authorization may need to add the 'Cookie' header to this
83
+ # list.
84
+ #
85
+ # Default: ['Authorization', 'Cookie']
86
+ option_accessor :private_headers
87
+
88
+ # Specifies whether the client can force a cache reload by including a
89
+ # Cache-Control "no-cache" directive in the request. This is enabled by
90
+ # default for compliance with RFC 2616.
91
+ option_accessor :allow_reload
92
+
93
+ # Specifies whether the client can force a cache revalidate by including
94
+ # a Cache-Control "max-age=0" directive in the request. This is enabled by
95
+ # default for compliance with RFC 2616.
96
+ option_accessor :allow_revalidate
97
+
98
+ # The underlying options Hash. During initialization (or outside of a
99
+ # request), this is a default values Hash. During a request, this is the
100
+ # Rack environment Hash. The default values Hash is merged in underneath
101
+ # the Rack environment before each request is processed.
102
+ def options
103
+ @env || @default_options
104
+ end
105
+
106
+ # Set multiple options.
107
+ def options=(hash={})
108
+ hash.each { |key,value| write_option(key, value) }
109
+ end
110
+
111
+ # Set an option. When +option+ is a Symbol, it is set in the Rack
112
+ # Environment as "rack-cache.option". When +option+ is a String, it
113
+ # exactly as specified. The +option+ argument may also be a Hash in
114
+ # which case each key/value pair is merged into the environment as if
115
+ # the #set method were called on each.
116
+ def set(option, value=self, &block)
117
+ if block_given?
118
+ write_option option, block
119
+ elsif value == self
120
+ self.options = option.to_hash
121
+ else
122
+ write_option option, value
123
+ end
124
+ end
125
+
126
+ private
127
+ def initialize_options(options={})
128
+ @default_options = {
129
+ 'rack-cache.cache_key' => Key,
130
+ 'rack-cache.verbose' => true,
131
+ 'rack-cache.storage' => Rack::Cache::Storage.instance,
132
+ 'rack-cache.metastore' => 'heap:/',
133
+ 'rack-cache.entitystore' => 'heap:/',
134
+ 'rack-cache.default_ttl' => 0,
135
+ 'rack-cache.private_headers' => ['Authorization', 'Cookie'],
136
+ 'rack-cache.allow_reload' => false,
137
+ 'rack-cache.allow_revalidate' => false
138
+ }
139
+ self.options = options
140
+ end
141
+
142
+ def read_option(key)
143
+ options[option_name(key)]
144
+ end
145
+
146
+ def write_option(key, value)
147
+ options[option_name(key)] = value
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,33 @@
1
+ require 'rack/request'
2
+ require 'rack/cache/cachecontrol'
3
+
4
+ module Rack::Cache
5
+
6
+ # Provides access to the HTTP request. The +request+ and +original_request+
7
+ # objects exposed by the Core caching engine are instances of this class.
8
+ #
9
+ # Request objects respond to a variety of convenience methods, including
10
+ # everything defined by Rack::Request as well as the Headers and
11
+ # RequestHeaders modules.
12
+ class Request < Rack::Request
13
+ # The HTTP request method. This is the standard implementation of this
14
+ # method but is respecified here due to libraries that attempt to modify
15
+ # the behavior to respect POST tunnel method specifiers. We always want
16
+ # the real request method.
17
+ def request_method
18
+ @env['REQUEST_METHOD']
19
+ end
20
+
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'])
24
+ end
25
+
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
32
+ end
33
+ end
@@ -0,0 +1,267 @@
1
+ require 'time'
2
+ require 'set'
3
+ require 'rack/response'
4
+ require 'rack/utils'
5
+ require 'rack/cache/cachecontrol'
6
+
7
+ module Rack::Cache
8
+
9
+ # Provides access to the response generated by the downstream application. The
10
+ # +response+, +original_response+, and +entry+ objects exposed by the Core
11
+ # caching engine are instances of this class.
12
+ #
13
+ # Response objects respond to a variety of convenience methods, including
14
+ # those defined in Rack::Response::Helpers, Rack::Cache::Headers,
15
+ # and Rack::Cache::ResponseHeaders.
16
+ #
17
+ # Note that Rack::Cache::Response is not a subclass of Rack::Response and does
18
+ # not perform many of the same initialization and finalization tasks. For
19
+ # example, the body is not slurped during initialization and there are no
20
+ # facilities for generating response output.
21
+ class Response
22
+ include Rack::Response::Helpers
23
+
24
+ # Rack response tuple accessors.
25
+ attr_accessor :status, :headers, :body
26
+
27
+ # The time when the Response object was instantiated.
28
+ attr_reader :now
29
+
30
+ # Create a Response instance given the response status code, header hash,
31
+ # and body.
32
+ def initialize(status, headers, body)
33
+ @status = status
34
+ @headers = Rack::Utils::HeaderHash.new(headers)
35
+ @body = body
36
+ @now = Time.now
37
+ @headers['Date'] ||= @now.httpdate
38
+ end
39
+
40
+ def initialize_copy(other)
41
+ super
42
+ @headers = other.headers.dup
43
+ end
44
+
45
+ # Return the status, headers, and body in a three-tuple.
46
+ def to_a
47
+ [status, headers.to_hash, body]
48
+ end
49
+
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'])
70
+ end
71
+
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
86
+ end
87
+
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
94
+ end
95
+
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?
106
+ end
107
+
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
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
267
+ end