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

cache_key(request) click to toggle source

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(request, entity_store) click to toggle source

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         [req, persist_response(response)]
128       else
129         [req, res]
130       end
131     end
132   write key, entries if modified
133 end
lookup(request, entity_store) click to toggle source

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   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
store(request, response, entity_store) click to toggle source

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
 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'] || res['vary'])) &&
 95         requests_match?(vary, env, stored_env)
 96     end
 97 
 98   headers = persist_response(response)
 99   headers.delete('Age')
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

purge(key) click to toggle source

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
187 def purge(key)
188   raise NotImplementedError
189 end
read(key) click to toggle source

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
174 def read(key)
175   raise NotImplementedError
176 end
write(key, negotiations, ttl = nil) click to toggle source

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
181 def write(key, negotiations, ttl = nil)
182   raise NotImplementedError
183 end

Private Instance Methods

hexdigest(data) click to toggle source

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
194 def hexdigest(data)
195   Digest::SHA1.hexdigest(data)
196 end
persist_request(request) click to toggle source

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
140 def persist_request(request)
141   env = request.env.dup
142   env.reject! { |key,val| key =~ /[^0-9A-Z_]/ || !val.respond_to?(:to_str) }
143   env
144 end
persist_response(response) click to toggle source
    # File lib/rack/cache/meta_store.rb
153 def persist_response(response)
154   hash = response.headers.to_hash
155   hash['X-Status'] = response.status.to_s
156   hash
157 end
requests_match?(vary, env1, env2) click to toggle source

Determine whether the two environment hashes are non-varying based on the vary response header value provided.

    # File lib/rack/cache/meta_store.rb
161 def requests_match?(vary, env1, env2)
162   return true if vary.nil? || vary == ''
163   vary.split(/[\s,]+/).all? do |header|
164     key = "HTTP_#{header.upcase.tr('-', '_')}"
165     env1[key] == env2[key]
166   end
167 end
restore_response(hash, body=[]) click to toggle source

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
148 def restore_response(hash, body=[])
149   status = hash.delete('X-Status').to_i
150   Rack::Cache::Response.new(status, hash, body)
151 end