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
Generate a cache key for the request.
# File lib/rack/cache/meta_store.rb 112 def cache_key(request) 113 keygen = request.env['rack-cache.cache_key'] || Key 114 keygen.call(request) 115 end
Invalidate all cache entries that match the request.
# File lib/rack/cache/meta_store.rb 118 def invalidate(request, entity_store) 119 modified = false 120 key = cache_key(request) 121 entries = 122 read(key).map do |req, res| 123 response = restore_response(res) 124 if response.fresh? 125 response.expire! 126 modified = true 127 end 128 [req, persist_response(response)] 129 end 130 write key, entries if modified 131 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.
# 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 entity_key = res['x-content-digest'] 42 if entity_key && body = entity_store.open(entity_key) 43 restore_response(res, body) 44 else 45 # the metastore referenced an entity that doesn't exist in 46 # the entitystore, purge the entry from the meta-store 47 begin 48 purge(key) 49 rescue NotImplementedError 50 @@warned_on_purge ||= begin 51 warn "WARNING: Future releases may require purge implementation for #{self.class.name}" 52 true 53 end 54 nil 55 end 56 end 57 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.
# File lib/rack/cache/meta_store.rb 62 def store(request, response, entity_store) 63 key = cache_key(request) 64 stored_env = persist_request(request) 65 66 # write the response body to the entity store if this is the 67 # original response. 68 if response.headers['x-content-digest'].nil? 69 if request.env['rack-cache.use_native_ttl'] && response.fresh? 70 digest, size = entity_store.write(response.body, response.ttl) 71 else 72 digest, size = entity_store.write(response.body) 73 end 74 response.headers['x-content-digest'] = digest 75 response.headers['content-length'] = size.to_s unless response.headers['Transfer-Encoding'] 76 77 # If the entitystore backend is a Noop, do not try to read the body from the backend, it always returns an empty array 78 unless entity_store.is_a? Rack::Cache::EntityStore::Noop 79 # A stream body can only be read once and is currently closed by #write. 80 # (To avoid having to keep giant objects in memory when writing to disk cache 81 # the body is never converted to a single string) 82 # We cannot always reply on body to be re-readable, 83 # so we have to read it from the cache. 84 # BUG: if the cache was unable to store a stream, the stream will be closed 85 # and rack will try to read it again, resulting in hard to track down exception 86 response.body = entity_store.open(digest) || response.body 87 end 88 end 89 90 # read existing cache entries, remove non-varying, and add this one to 91 # the list 92 vary = response.vary 93 entries = 94 read(key).reject do |env, res| 95 (vary == (res['vary'])) && 96 requests_match?(vary, env, stored_env) 97 end 98 99 headers = persist_response(response) 100 headers.delete('age') 101 102 entries.unshift [stored_env, headers] 103 if request.env['rack-cache.use_native_ttl'] && response.fresh? 104 write key, entries, response.ttl 105 else 106 write key, entries 107 end 108 key 109 end
Protected Instance Methods
Remove all cached entries at the key specified. No error is raised when the key does not exist.
# File lib/rack/cache/meta_store.rb 185 def purge(key) 186 raise NotImplementedError 187 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.
# File lib/rack/cache/meta_store.rb 172 def read(key) 173 raise NotImplementedError 174 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.
# File lib/rack/cache/meta_store.rb 179 def write(key, negotiations, ttl = nil) 180 raise NotImplementedError 181 end
Private Instance Methods
Generate a SHA1 hex digest for the specified string. This is a simple utility method for meta store implementations.
# File lib/rack/cache/meta_store.rb 192 def hexdigest(data) 193 Digest::SHA1.hexdigest(data) 194 end
Extract the environment Hash from request
while making any necessary modifications in preparation for persistence. The Hash returned must be marshalable.
# File lib/rack/cache/meta_store.rb 138 def persist_request(request) 139 env = request.env.dup 140 env.reject! { |key,val| key =~ /[^0-9A-Z_]/ || !val.respond_to?(:to_str) } 141 env 142 end
# File lib/rack/cache/meta_store.rb 151 def persist_response(response) 152 hash = response.headers.dup 153 hash['x-status'] = response.status.to_s 154 hash 155 end
Determine whether the two environment hashes are non-varying based on the vary response header value provided.
# File lib/rack/cache/meta_store.rb 159 def requests_match?(vary, env1, env2) 160 return true if vary.nil? || vary == '' 161 vary.split(/[\s,]+/).all? do |header| 162 key = "HTTP_#{header.upcase.tr('-', '_')}" 163 env1[key] == env2[key] 164 end 165 end
Converts a stored response hash into a Response
object. The caller is responsible for loading and passing the body if needed.
# File lib/rack/cache/meta_store.rb 146 def restore_response(hash, body=[]) 147 status = hash.delete('x-status').to_i 148 Rack::Cache::Response.new(status, hash, body) 149 end