class Rack::Cache::MetaStore
The MetaStore
is responsible for storing meta information about a request/response pair keyed by the request’s URL.
The meta store keeps a list of request/response pairs for each canonical request URL. A request/response pair is a two element Array of the form:
[request, response]
The request
element is a Hash of Rack
environment keys. Only protocol keys (i.e., those that start with “HTTP_”) are stored. The response
element is a Hash of cached HTTP response headers for the paired request.
The MetaStore
class is abstract and should not be instanstiated directly. Concrete subclasses should implement the protected read
, write
, and purge
methods. Care has been taken to keep these low-level methods dumb and straight-forward to implement.
Constants
- DISK
-
Concrete
MetaStore
implementation that stores request/response pairs on disk. - FILE
-
Concrete
MetaStore
implementation that stores request/response pairs on disk. - GAE
- GAECACHE
- HEAP
-
Concrete
MetaStore
implementation that uses a simple Hash to store request/response pairs on the heap. - MEM
-
Concrete
MetaStore
implementation that uses a simple Hash to store request/response pairs on the heap. - MEMCACHE
- MEMCACHED
Public Instance Methods
Source
# File lib/rack/cache/meta_store.rb 111 def cache_key(request) 112 keygen = request.env['rack-cache.cache_key'] || Key 113 keygen.call(request) 114 end
Generate a cache key for the request.
Source
# File lib/rack/cache/meta_store.rb 117 def invalidate(request, entity_store) 118 modified = false 119 key = cache_key(request) 120 entries = 121 read(key).map do |req, res| 122 response = restore_response(res) 123 if response.fresh? 124 response.expire! 125 modified = true 126 [req, persist_response(response)] 127 else 128 [req, res] 129 end 130 end 131 write key, entries if modified 132 end
Invalidate all cache entries that match the request.
Source
# File lib/rack/cache/meta_store.rb 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'] || 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 # the metastore referenced an entity that doesn't exist in 45 # the entitystore, purge the entry from the meta-store 46 begin 47 purge(key) 48 rescue NotImplementedError 49 @@warned_on_purge ||= begin 50 warn "WARNING: Future releases may require purge implementation for #{self.class.name}" 51 true 52 end 53 nil 54 end 55 end 56 end
Locate a cached response for the request provided. Returns a Rack::Cache::Response
object if the cache hits or nil if no cache entry was found.
Source
# File lib/rack/cache/meta_store.rb 61 def store(request, response, entity_store) 62 key = cache_key(request) 63 stored_env = persist_request(request) 64 65 # write the response body to the entity store if this is the 66 # original response. 67 if response.headers['x-content-digest'].nil? 68 if request.env['rack-cache.use_native_ttl'] && response.fresh? 69 digest, size = entity_store.write(response.body, response.ttl) 70 else 71 digest, size = entity_store.write(response.body) 72 end 73 response.headers['x-content-digest'] = digest 74 response.headers['content-length'] = size.to_s unless response.headers['Transfer-Encoding'] 75 76 # If the entitystore backend is a Noop, do not try to read the body from the backend, it always returns an empty array 77 unless entity_store.is_a? Rack::Cache::EntityStore::Noop 78 # A stream body can only be read once and is currently closed by #write. 79 # (To avoid having to keep giant objects in memory when writing to disk cache 80 # the body is never converted to a single string) 81 # We cannot always reply on body to be re-readable, 82 # so we have to read it from the cache. 83 # BUG: if the cache was unable to store a stream, the stream will be closed 84 # and rack will try to read it again, resulting in hard to track down exception 85 response.body = entity_store.open(digest) || response.body 86 end 87 end 88 89 # read existing cache entries, remove non-varying, and add this one to 90 # the list 91 vary = response.vary 92 entries = 93 read(key).reject do |env, res| 94 (vary == (res['vary'])) && 95 requests_match?(vary, env, stored_env) 96 end 97 98 headers = persist_response(response) 99 headers.delete('age') 100 101 entries.unshift [stored_env, headers] 102 if request.env['rack-cache.use_native_ttl'] && response.fresh? 103 write key, entries, response.ttl 104 else 105 write key, entries 106 end 107 key 108 end
Write a cache entry to the store under the given key. Existing entries are read and any that match the response are removed. This method calls write
with the new list of cache entries.
Protected Instance Methods
Source
# File lib/rack/cache/meta_store.rb 186 def purge(key) 187 raise NotImplementedError 188 end
Remove all cached entries at the key specified. No error is raised when the key does not exist.
Source
# File lib/rack/cache/meta_store.rb 173 def read(key) 174 raise NotImplementedError 175 end
Locate all cached request/response pairs that match the specified URL key. The result must be an Array of all cached request/response pairs. An empty Array must be returned if nothing is cached for the specified key.
Source
# File lib/rack/cache/meta_store.rb 180 def write(key, negotiations, ttl = nil) 181 raise NotImplementedError 182 end
Store an Array of request/response pairs for the given key. Concrete implementations should not attempt to filter or concatenate the list in any way.
Private Instance Methods
Source
# File lib/rack/cache/meta_store.rb 193 def hexdigest(data) 194 Digest::SHA1.hexdigest(data) 195 end
Generate a SHA1 hex digest for the specified string. This is a simple utility method for meta store implementations.
Source
# File lib/rack/cache/meta_store.rb 139 def persist_request(request) 140 env = request.env.dup 141 env.reject! { |key,val| key =~ /[^0-9A-Z_]/ || !val.respond_to?(:to_str) } 142 env 143 end
Extract the environment Hash from request
while making any necessary modifications in preparation for persistence. The Hash returned must be marshalable.
Source
# File lib/rack/cache/meta_store.rb 152 def persist_response(response) 153 hash = response.headers.dup 154 hash['x-status'] = response.status.to_s 155 hash 156 end
Source
# File lib/rack/cache/meta_store.rb 160 def requests_match?(vary, env1, env2) 161 return true if vary.nil? || vary == '' 162 vary.split(/[\s,]+/).all? do |header| 163 key = "HTTP_#{header.upcase.tr('-', '_')}" 164 env1[key] == env2[key] 165 end 166 end
Determine whether the two environment hashes are non-varying based on the vary response header value provided.
Source
# File lib/rack/cache/meta_store.rb 147 def restore_response(hash, body=[]) 148 status = hash.delete('x-status').to_i 149 Rack::Cache::Response.new(status, hash, body) 150 end
Converts a stored response hash into a Response
object. The caller is responsible for loading and passing the body if needed.