rack-cache 1.5.1 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,429 @@
1
+ require 'fileutils'
2
+ require 'digest/sha1'
3
+ require 'rack/utils'
4
+ require 'rack/cache/key'
5
+
6
+ module Rack::Cache
7
+
8
+ # The MetaStore is responsible for storing meta information about a
9
+ # request/response pair keyed by the request's URL.
10
+ #
11
+ # The meta store keeps a list of request/response pairs for each canonical
12
+ # request URL. A request/response pair is a two element Array of the form:
13
+ # [request, response]
14
+ #
15
+ # The +request+ element is a Hash of Rack environment keys. Only protocol
16
+ # keys (i.e., those that start with "HTTP_") are stored. The +response+
17
+ # element is a Hash of cached HTTP response headers for the paired request.
18
+ #
19
+ # The MetaStore class is abstract and should not be instanstiated
20
+ # directly. Concrete subclasses should implement the protected #read,
21
+ # #write, and #purge methods. Care has been taken to keep these low-level
22
+ # methods dumb and straight-forward to implement.
23
+ class MetaStore
24
+
25
+ # Locate a cached response for the request provided. Returns a
26
+ # Rack::Cache::Response object if the cache hits or nil if no cache entry
27
+ # was found.
28
+ def lookup(request, entity_store)
29
+ key = cache_key(request)
30
+ entries = read(key)
31
+
32
+ # bail out if we have nothing cached
33
+ return nil if entries.empty?
34
+
35
+ # find a cached entry that matches the request.
36
+ env = request.env
37
+ match = entries.detect{|req,res| requests_match?(res['Vary'], env, req)}
38
+ return nil if match.nil?
39
+
40
+ _, res = match
41
+ if body = entity_store.open(res['X-Content-Digest'])
42
+ restore_response(res, body)
43
+ else
44
+ # TODO the metastore referenced an entity that doesn't exist in
45
+ # the entitystore. we definitely want to return nil but we should
46
+ # also purge the entry from the meta-store when this is detected.
47
+ end
48
+ end
49
+
50
+ # Write a cache entry to the store under the given key. Existing
51
+ # entries are read and any that match the response are removed.
52
+ # This method calls #write with the new list of cache entries.
53
+ def store(request, response, entity_store)
54
+ key = cache_key(request)
55
+ stored_env = persist_request(request)
56
+
57
+ # write the response body to the entity store if this is the
58
+ # original response.
59
+ if response.headers['X-Content-Digest'].nil?
60
+ if request.env['rack-cache.use_native_ttl'] && response.fresh?
61
+ digest, size = entity_store.write(response.body, response.ttl)
62
+ else
63
+ digest, size = entity_store.write(response.body)
64
+ end
65
+ response.headers['X-Content-Digest'] = digest
66
+ response.headers['Content-Length'] = size.to_s unless response.headers['Transfer-Encoding']
67
+
68
+ # If the entitystore backend is a Noop, do not try to read the body from the backend, it always returns an empty array
69
+ unless entity_store.is_a? Rack::Cache::EntityStore::Noop
70
+ # A stream body can only be read once and is currently closed by #write.
71
+ # (To avoid having to keep giant objects in memory when writing to disk cache
72
+ # the body is never converted to a single string)
73
+ # We cannot always reply on body to be re-readable,
74
+ # so we have to read it from the cache.
75
+ # BUG: if the cache was unable to store a stream, the stream will be closed
76
+ # and rack will try to read it again, resulting in hard to track down exception
77
+ response.body = entity_store.open(digest) || response.body
78
+ end
79
+ end
80
+
81
+ # read existing cache entries, remove non-varying, and add this one to
82
+ # the list
83
+ vary = response.vary
84
+ entries =
85
+ read(key).reject do |env,res|
86
+ (vary == res['Vary']) &&
87
+ requests_match?(vary, env, stored_env)
88
+ end
89
+
90
+ headers = persist_response(response)
91
+ headers.delete 'Age'
92
+
93
+ entries.unshift [stored_env, headers]
94
+ write key, entries
95
+ key
96
+ end
97
+
98
+ # Generate a cache key for the request.
99
+ def cache_key(request)
100
+ keygen = request.env['rack-cache.cache_key'] || Key
101
+ keygen.call(request)
102
+ end
103
+
104
+ # Invalidate all cache entries that match the request.
105
+ def invalidate(request, entity_store)
106
+ modified = false
107
+ key = cache_key(request)
108
+ entries =
109
+ read(key).map do |req, res|
110
+ response = restore_response(res)
111
+ if response.fresh?
112
+ response.expire!
113
+ modified = true
114
+ [req, persist_response(response)]
115
+ else
116
+ [req, res]
117
+ end
118
+ end
119
+ write key, entries if modified
120
+ end
121
+
122
+ private
123
+
124
+ # Extract the environment Hash from +request+ while making any
125
+ # necessary modifications in preparation for persistence. The Hash
126
+ # returned must be marshalable.
127
+ def persist_request(request)
128
+ env = request.env.dup
129
+ env.reject! { |key,val| key =~ /[^0-9A-Z_]/ || !val.respond_to?(:to_str) }
130
+ env
131
+ end
132
+
133
+ # Converts a stored response hash into a Response object. The caller
134
+ # is responsible for loading and passing the body if needed.
135
+ def restore_response(hash, body=[])
136
+ status = hash.delete('X-Status').to_i
137
+ Rack::Cache::Response.new(status, hash, body)
138
+ end
139
+
140
+ def persist_response(response)
141
+ hash = response.headers.to_hash
142
+ hash['X-Status'] = response.status.to_s
143
+ hash
144
+ end
145
+
146
+ # Determine whether the two environment hashes are non-varying based on
147
+ # the vary response header value provided.
148
+ def requests_match?(vary, env1, env2)
149
+ return true if vary.nil? || vary == ''
150
+ vary.split(/[\s,]+/).all? do |header|
151
+ key = "HTTP_#{header.upcase.tr('-', '_')}"
152
+ env1[key] == env2[key]
153
+ end
154
+ end
155
+
156
+ protected
157
+ # Locate all cached request/response pairs that match the specified
158
+ # URL key. The result must be an Array of all cached request/response
159
+ # pairs. An empty Array must be returned if nothing is cached for
160
+ # the specified key.
161
+ def read(key)
162
+ raise NotImplementedError
163
+ end
164
+
165
+ # Store an Array of request/response pairs for the given key. Concrete
166
+ # implementations should not attempt to filter or concatenate the
167
+ # list in any way.
168
+ def write(key, negotiations)
169
+ raise NotImplementedError
170
+ end
171
+
172
+ # Remove all cached entries at the key specified. No error is raised
173
+ # when the key does not exist.
174
+ def purge(key)
175
+ raise NotImplementedError
176
+ end
177
+
178
+ private
179
+ # Generate a SHA1 hex digest for the specified string. This is a
180
+ # simple utility method for meta store implementations.
181
+ def hexdigest(data)
182
+ Digest::SHA1.hexdigest(data)
183
+ end
184
+
185
+ public
186
+ # Concrete MetaStore implementation that uses a simple Hash to store
187
+ # request/response pairs on the heap.
188
+ class Heap < MetaStore
189
+ def initialize(hash={})
190
+ @hash = hash
191
+ end
192
+
193
+ def read(key)
194
+ if data = @hash[key]
195
+ Marshal.load(data)
196
+ else
197
+ []
198
+ end
199
+ end
200
+
201
+ def write(key, entries)
202
+ @hash[key] = Marshal.dump(entries)
203
+ end
204
+
205
+ def purge(key)
206
+ @hash.delete(key)
207
+ nil
208
+ end
209
+
210
+ def to_hash
211
+ @hash
212
+ end
213
+
214
+ def self.resolve(uri)
215
+ new
216
+ end
217
+ end
218
+
219
+ HEAP = Heap
220
+ MEM = HEAP
221
+
222
+ # Concrete MetaStore implementation that stores request/response
223
+ # pairs on disk.
224
+ class Disk < MetaStore
225
+ attr_reader :root
226
+
227
+ def initialize(root="/tmp/rack-cache/meta-#{ARGV[0]}")
228
+ @root = File.expand_path(root)
229
+ FileUtils.mkdir_p(root, :mode => 0755)
230
+ end
231
+
232
+ def read(key)
233
+ path = key_path(key)
234
+ File.open(path, 'rb') { |io| Marshal.load(io) }
235
+ rescue Errno::ENOENT, IOError
236
+ []
237
+ end
238
+
239
+ def write(key, entries)
240
+ tries = 0
241
+ begin
242
+ path = key_path(key)
243
+ File.open(path, 'wb') { |io| Marshal.dump(entries, io, -1) }
244
+ rescue Errno::ENOENT, IOError
245
+ Dir.mkdir(File.dirname(path), 0755)
246
+ retry if (tries += 1) == 1
247
+ end
248
+ end
249
+
250
+ def purge(key)
251
+ path = key_path(key)
252
+ File.unlink(path)
253
+ nil
254
+ rescue Errno::ENOENT, IOError
255
+ nil
256
+ end
257
+
258
+ private
259
+ def key_path(key)
260
+ File.join(root, spread(hexdigest(key)))
261
+ end
262
+
263
+ def spread(sha, n=2)
264
+ sha = sha.dup
265
+ sha[n,0] = '/'
266
+ sha
267
+ end
268
+
269
+ public
270
+ def self.resolve(uri)
271
+ path = File.expand_path(uri.opaque || uri.path)
272
+ new path
273
+ end
274
+
275
+ end
276
+
277
+ DISK = Disk
278
+ FILE = Disk
279
+
280
+ # Stores request/response pairs in memcached. Keys are not stored
281
+ # directly since memcached has a 250-byte limit on key names. Instead,
282
+ # the SHA1 hexdigest of the key is used.
283
+ class MemCacheBase < MetaStore
284
+ extend Rack::Utils
285
+
286
+ # The MemCache object used to communicated with the memcached
287
+ # daemon.
288
+ attr_reader :cache
289
+
290
+ # Create MemCache store for the given URI. The URI must specify
291
+ # a host and may specify a port, namespace, and options:
292
+ #
293
+ # memcached://example.com:11211/namespace?opt1=val1&opt2=val2
294
+ #
295
+ # Query parameter names and values are documented with the memcached
296
+ # library: http://tinyurl.com/4upqnd
297
+ def self.resolve(uri)
298
+ if uri.respond_to?(:scheme)
299
+ server = "#{uri.host}:#{uri.port || '11211'}"
300
+ options = parse_query(uri.query)
301
+ options.keys.each do |key|
302
+ value =
303
+ case value = options.delete(key)
304
+ when 'true' ; true
305
+ when 'false' ; false
306
+ else value.to_sym
307
+ end
308
+ options[key.to_sym] = value
309
+ end
310
+
311
+ options[:namespace] = uri.path.to_s.sub(/^\//, '')
312
+
313
+ new server, options
314
+ else
315
+ # if the object provided is not a URI, pass it straight through
316
+ # to the underlying implementation.
317
+ new uri
318
+ end
319
+ end
320
+ end
321
+
322
+ class Dalli < MemCacheBase
323
+ def initialize(server="localhost:11211", options={})
324
+ @cache =
325
+ if server.respond_to?(:stats)
326
+ server
327
+ else
328
+ require 'dalli'
329
+ ::Dalli::Client.new(server, options)
330
+ end
331
+ end
332
+
333
+ def read(key)
334
+ key = hexdigest(key)
335
+ cache.get(key) || []
336
+ end
337
+
338
+ def write(key, entries)
339
+ key = hexdigest(key)
340
+ cache.set(key, entries)
341
+ end
342
+
343
+ def purge(key)
344
+ cache.delete(hexdigest(key))
345
+ nil
346
+ end
347
+ end
348
+
349
+ class MemCached < MemCacheBase
350
+ # The Memcached instance used to communicated with the memcached
351
+ # daemon.
352
+ attr_reader :cache
353
+
354
+ def initialize(server="localhost:11211", options={})
355
+ options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
356
+ @cache =
357
+ if server.respond_to?(:stats)
358
+ server
359
+ else
360
+ require 'memcached'
361
+ Memcached.new(server, options)
362
+ end
363
+ end
364
+
365
+ def read(key)
366
+ key = hexdigest(key)
367
+ cache.get(key)
368
+ rescue Memcached::NotFound
369
+ []
370
+ end
371
+
372
+ def write(key, entries)
373
+ key = hexdigest(key)
374
+ cache.set(key, entries)
375
+ end
376
+
377
+ def purge(key)
378
+ key = hexdigest(key)
379
+ cache.delete(key)
380
+ nil
381
+ rescue Memcached::NotFound
382
+ nil
383
+ end
384
+ end
385
+
386
+ MEMCACHE =
387
+ if defined?(::Memcached)
388
+ MemCached
389
+ else
390
+ Dalli
391
+ end
392
+ MEMCACHED = MEMCACHE
393
+
394
+ class GAEStore < MetaStore
395
+ attr_reader :cache
396
+
397
+ def initialize(options = {})
398
+ require 'rack/cache/app_engine'
399
+ @cache = Rack::Cache::AppEngine::MemCache.new(options)
400
+ end
401
+
402
+ def read(key)
403
+ key = hexdigest(key)
404
+ cache.get(key) || []
405
+ end
406
+
407
+ def write(key, entries)
408
+ key = hexdigest(key)
409
+ cache.put(key, entries)
410
+ end
411
+
412
+ def purge(key)
413
+ key = hexdigest(key)
414
+ cache.delete(key)
415
+ nil
416
+ end
417
+
418
+ def self.resolve(uri)
419
+ self.new(:namespace => uri.host)
420
+ end
421
+
422
+ end
423
+
424
+ GAECACHE = GAEStore
425
+ GAE = GAEStore
426
+
427
+ end
428
+
429
+ end
@@ -1,418 +1,2 @@
1
- require 'fileutils'
2
- require 'digest/sha1'
3
- require 'rack/utils'
4
- require 'rack/cache/key'
5
-
6
- module Rack::Cache
7
-
8
- # The MetaStore is responsible for storing meta information about a
9
- # request/response pair keyed by the request's URL.
10
- #
11
- # The meta store keeps a list of request/response pairs for each canonical
12
- # request URL. A request/response pair is a two element Array of the form:
13
- # [request, response]
14
- #
15
- # The +request+ element is a Hash of Rack environment keys. Only protocol
16
- # keys (i.e., those that start with "HTTP_") are stored. The +response+
17
- # element is a Hash of cached HTTP response headers for the paired request.
18
- #
19
- # The MetaStore class is abstract and should not be instanstiated
20
- # directly. Concrete subclasses should implement the protected #read,
21
- # #write, and #purge methods. Care has been taken to keep these low-level
22
- # methods dumb and straight-forward to implement.
23
- class MetaStore
24
-
25
- # Locate a cached response for the request provided. Returns a
26
- # Rack::Cache::Response object if the cache hits or nil if no cache entry
27
- # was found.
28
- def lookup(request, entity_store)
29
- key = cache_key(request)
30
- entries = read(key)
31
-
32
- # bail out if we have nothing cached
33
- return nil if entries.empty?
34
-
35
- # find a cached entry that matches the request.
36
- env = request.env
37
- match = entries.detect{|req,res| requests_match?(res['Vary'], env, req)}
38
- return nil if match.nil?
39
-
40
- _, res = match
41
- if body = entity_store.open(res['X-Content-Digest'])
42
- restore_response(res, body)
43
- else
44
- # TODO the metastore referenced an entity that doesn't exist in
45
- # the entitystore. we definitely want to return nil but we should
46
- # also purge the entry from the meta-store when this is detected.
47
- end
48
- end
49
-
50
- # Write a cache entry to the store under the given key. Existing
51
- # entries are read and any that match the response are removed.
52
- # This method calls #write with the new list of cache entries.
53
- def store(request, response, entity_store)
54
- key = cache_key(request)
55
- stored_env = persist_request(request)
56
-
57
- # write the response body to the entity store if this is the
58
- # original response.
59
- if response.headers['X-Content-Digest'].nil?
60
- if request.env['rack-cache.use_native_ttl'] && response.fresh?
61
- digest, size = entity_store.write(response.body, response.ttl)
62
- else
63
- digest, size = entity_store.write(response.body)
64
- end
65
- response.headers['X-Content-Digest'] = digest
66
- response.headers['Content-Length'] = size.to_s unless response.headers['Transfer-Encoding']
67
- response.body = entity_store.open(digest) || response.body
68
- end
69
-
70
- # read existing cache entries, remove non-varying, and add this one to
71
- # the list
72
- vary = response.vary
73
- entries =
74
- read(key).reject do |env,res|
75
- (vary == res['Vary']) &&
76
- requests_match?(vary, env, stored_env)
77
- end
78
-
79
- headers = persist_response(response)
80
- headers.delete 'Age'
81
-
82
- entries.unshift [stored_env, headers]
83
- write key, entries
84
- key
85
- end
86
-
87
- # Generate a cache key for the request.
88
- def cache_key(request)
89
- keygen = request.env['rack-cache.cache_key'] || Key
90
- keygen.call(request)
91
- end
92
-
93
- # Invalidate all cache entries that match the request.
94
- def invalidate(request, entity_store)
95
- modified = false
96
- key = cache_key(request)
97
- entries =
98
- read(key).map do |req, res|
99
- response = restore_response(res)
100
- if response.fresh?
101
- response.expire!
102
- modified = true
103
- [req, persist_response(response)]
104
- else
105
- [req, res]
106
- end
107
- end
108
- write key, entries if modified
109
- end
110
-
111
- private
112
-
113
- # Extract the environment Hash from +request+ while making any
114
- # necessary modifications in preparation for persistence. The Hash
115
- # returned must be marshalable.
116
- def persist_request(request)
117
- env = request.env.dup
118
- env.reject! { |key,val| key =~ /[^0-9A-Z_]/ || !val.respond_to?(:to_str) }
119
- env
120
- end
121
-
122
- # Converts a stored response hash into a Response object. The caller
123
- # is responsible for loading and passing the body if needed.
124
- def restore_response(hash, body=nil)
125
- status = hash.delete('X-Status').to_i
126
- Rack::Cache::Response.new(status, hash, body)
127
- end
128
-
129
- def persist_response(response)
130
- hash = response.headers.to_hash
131
- hash['X-Status'] = response.status.to_s
132
- hash
133
- end
134
-
135
- # Determine whether the two environment hashes are non-varying based on
136
- # the vary response header value provided.
137
- def requests_match?(vary, env1, env2)
138
- return true if vary.nil? || vary == ''
139
- vary.split(/[\s,]+/).all? do |header|
140
- key = "HTTP_#{header.upcase.tr('-', '_')}"
141
- env1[key] == env2[key]
142
- end
143
- end
144
-
145
- protected
146
- # Locate all cached request/response pairs that match the specified
147
- # URL key. The result must be an Array of all cached request/response
148
- # pairs. An empty Array must be returned if nothing is cached for
149
- # the specified key.
150
- def read(key)
151
- raise NotImplementedError
152
- end
153
-
154
- # Store an Array of request/response pairs for the given key. Concrete
155
- # implementations should not attempt to filter or concatenate the
156
- # list in any way.
157
- def write(key, negotiations)
158
- raise NotImplementedError
159
- end
160
-
161
- # Remove all cached entries at the key specified. No error is raised
162
- # when the key does not exist.
163
- def purge(key)
164
- raise NotImplementedError
165
- end
166
-
167
- private
168
- # Generate a SHA1 hex digest for the specified string. This is a
169
- # simple utility method for meta store implementations.
170
- def hexdigest(data)
171
- Digest::SHA1.hexdigest(data)
172
- end
173
-
174
- public
175
- # Concrete MetaStore implementation that uses a simple Hash to store
176
- # request/response pairs on the heap.
177
- class Heap < MetaStore
178
- def initialize(hash={})
179
- @hash = hash
180
- end
181
-
182
- def read(key)
183
- if data = @hash[key]
184
- Marshal.load(data)
185
- else
186
- []
187
- end
188
- end
189
-
190
- def write(key, entries)
191
- @hash[key] = Marshal.dump(entries)
192
- end
193
-
194
- def purge(key)
195
- @hash.delete(key)
196
- nil
197
- end
198
-
199
- def to_hash
200
- @hash
201
- end
202
-
203
- def self.resolve(uri)
204
- new
205
- end
206
- end
207
-
208
- HEAP = Heap
209
- MEM = HEAP
210
-
211
- # Concrete MetaStore implementation that stores request/response
212
- # pairs on disk.
213
- class Disk < MetaStore
214
- attr_reader :root
215
-
216
- def initialize(root="/tmp/rack-cache/meta-#{ARGV[0]}")
217
- @root = File.expand_path(root)
218
- FileUtils.mkdir_p(root, :mode => 0755)
219
- end
220
-
221
- def read(key)
222
- path = key_path(key)
223
- File.open(path, 'rb') { |io| Marshal.load(io) }
224
- rescue Errno::ENOENT, IOError
225
- []
226
- end
227
-
228
- def write(key, entries)
229
- tries = 0
230
- begin
231
- path = key_path(key)
232
- File.open(path, 'wb') { |io| Marshal.dump(entries, io, -1) }
233
- rescue Errno::ENOENT, IOError
234
- Dir.mkdir(File.dirname(path), 0755)
235
- retry if (tries += 1) == 1
236
- end
237
- end
238
-
239
- def purge(key)
240
- path = key_path(key)
241
- File.unlink(path)
242
- nil
243
- rescue Errno::ENOENT, IOError
244
- nil
245
- end
246
-
247
- private
248
- def key_path(key)
249
- File.join(root, spread(hexdigest(key)))
250
- end
251
-
252
- def spread(sha, n=2)
253
- sha = sha.dup
254
- sha[n,0] = '/'
255
- sha
256
- end
257
-
258
- public
259
- def self.resolve(uri)
260
- path = File.expand_path(uri.opaque || uri.path)
261
- new path
262
- end
263
-
264
- end
265
-
266
- DISK = Disk
267
- FILE = Disk
268
-
269
- # Stores request/response pairs in memcached. Keys are not stored
270
- # directly since memcached has a 250-byte limit on key names. Instead,
271
- # the SHA1 hexdigest of the key is used.
272
- class MemCacheBase < MetaStore
273
- extend Rack::Utils
274
-
275
- # The MemCache object used to communicated with the memcached
276
- # daemon.
277
- attr_reader :cache
278
-
279
- # Create MemCache store for the given URI. The URI must specify
280
- # a host and may specify a port, namespace, and options:
281
- #
282
- # memcached://example.com:11211/namespace?opt1=val1&opt2=val2
283
- #
284
- # Query parameter names and values are documented with the memcached
285
- # library: http://tinyurl.com/4upqnd
286
- def self.resolve(uri)
287
- if uri.respond_to?(:scheme)
288
- server = "#{uri.host}:#{uri.port || '11211'}"
289
- options = parse_query(uri.query)
290
- options.keys.each do |key|
291
- value =
292
- case value = options.delete(key)
293
- when 'true' ; true
294
- when 'false' ; false
295
- else value.to_sym
296
- end
297
- options[key.to_sym] = value
298
- end
299
-
300
- options[:namespace] = uri.path.to_s.sub(/^\//, '')
301
-
302
- new server, options
303
- else
304
- # if the object provided is not a URI, pass it straight through
305
- # to the underlying implementation.
306
- new uri
307
- end
308
- end
309
- end
310
-
311
- class Dalli < MemCacheBase
312
- def initialize(server="localhost:11211", options={})
313
- @cache =
314
- if server.respond_to?(:stats)
315
- server
316
- else
317
- require 'dalli'
318
- ::Dalli::Client.new(server, options)
319
- end
320
- end
321
-
322
- def read(key)
323
- key = hexdigest(key)
324
- cache.get(key) || []
325
- end
326
-
327
- def write(key, entries)
328
- key = hexdigest(key)
329
- cache.set(key, entries)
330
- end
331
-
332
- def purge(key)
333
- cache.delete(hexdigest(key))
334
- nil
335
- end
336
- end
337
-
338
- class MemCached < MemCacheBase
339
- # The Memcached instance used to communicated with the memcached
340
- # daemon.
341
- attr_reader :cache
342
-
343
- def initialize(server="localhost:11211", options={})
344
- options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace)
345
- @cache =
346
- if server.respond_to?(:stats)
347
- server
348
- else
349
- require 'memcached'
350
- Memcached.new(server, options)
351
- end
352
- end
353
-
354
- def read(key)
355
- key = hexdigest(key)
356
- cache.get(key)
357
- rescue Memcached::NotFound
358
- []
359
- end
360
-
361
- def write(key, entries)
362
- key = hexdigest(key)
363
- cache.set(key, entries)
364
- end
365
-
366
- def purge(key)
367
- key = hexdigest(key)
368
- cache.delete(key)
369
- nil
370
- rescue Memcached::NotFound
371
- nil
372
- end
373
- end
374
-
375
- MEMCACHE =
376
- if defined?(::Memcached)
377
- MemCached
378
- else
379
- Dalli
380
- end
381
- MEMCACHED = MEMCACHE
382
-
383
- class GAEStore < MetaStore
384
- attr_reader :cache
385
-
386
- def initialize(options = {})
387
- require 'rack/cache/appengine'
388
- @cache = Rack::Cache::AppEngine::MemCache.new(options)
389
- end
390
-
391
- def read(key)
392
- key = hexdigest(key)
393
- cache.get(key) || []
394
- end
395
-
396
- def write(key, entries)
397
- key = hexdigest(key)
398
- cache.put(key, entries)
399
- end
400
-
401
- def purge(key)
402
- key = hexdigest(key)
403
- cache.delete(key)
404
- nil
405
- end
406
-
407
- def self.resolve(uri)
408
- self.new(:namespace => uri.host)
409
- end
410
-
411
- end
412
-
413
- GAECACHE = GAEStore
414
- GAE = GAEStore
415
-
416
- end
417
-
418
- end
1
+ warn "use require 'rack/cache/meta_store'"
2
+ require 'rack/cache/meta_store'