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 99 def cache_key(request) 100 keygen = request.env['rack-cache.cache_key'] || Key 101 keygen.call(request) 102 end
Invalidate all cache entries that match the request.
# File lib/rack/cache/meta_store.rb 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
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'], 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
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 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
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 174 def purge(key) 175 raise NotImplementedError 176 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 161 def read(key) 162 raise NotImplementedError 163 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 168 def write(key, negotiations) 169 raise NotImplementedError 170 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 181 def hexdigest(data) 182 Digest::SHA1.hexdigest(data) 183 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 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
# File lib/rack/cache/meta_store.rb 140 def persist_response(response) 141 hash = response.headers.to_hash 142 hash['X-Status'] = response.status.to_s 143 hash 144 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 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
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 135 def restore_response(hash, body=[]) 136 status = hash.delete('X-Status').to_i 137 Rack::Cache::Response.new(status, hash, body) 138 end