Class: Rack::Cache::MetaStore

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/cache/meta_store.rb

Overview

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.

Direct Known Subclasses

Disk, GAEStore, Heap, MemCacheBase

Defined Under Namespace

Classes: Dalli, Disk, GAEStore, Heap, MemCacheBase, MemCached

Constant Summary collapse

HEAP =
Heap
MEM =
HEAP
DISK =
Disk
FILE =
Disk
MEMCACHE =
if defined?(::Memcached)
  MemCached
else
  Dalli
end
MEMCACHED =
MEMCACHE
GAECACHE =
GAEStore
GAE =
GAEStore

Instance Method Summary collapse

Instance Method Details

#cache_key(request) ⇒ Object

Generate a cache key for the request.



112
113
114
115
# File 'lib/rack/cache/meta_store.rb', line 112

def cache_key(request)
  keygen = request.env['rack-cache.cache_key'] || Key
  keygen.call(request)
end

#invalidate(request, entity_store) ⇒ Object

Invalidate all cache entries that match the request.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/rack/cache/meta_store.rb', line 118

def invalidate(request, entity_store)
  modified = false
  key = cache_key(request)
  entries =
    read(key).map do |req, res|
      response = restore_response(res)
      if response.fresh?
        response.expire!
        modified = true
        [req, persist_response(response)]
      else
        [req, res]
      end
    end
  write key, entries if modified
end

#lookup(request, entity_store) ⇒ Object

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.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/rack/cache/meta_store.rb', line 28

def lookup(request, entity_store)
  key = cache_key(request)
  entries = read(key)

  # bail out if we have nothing cached
  return nil if entries.empty?

  # find a cached entry that matches the request.
  env = request.env
  match = entries.detect{ |req,res| requests_match?((res['Vary'] || res['vary']), env, req) }
  return nil if match.nil?

  _, res = match
  if body = entity_store.open(res['X-Content-Digest'])
    restore_response(res, body)
  else
    # the metastore referenced an entity that doesn't exist in
    # the entitystore, purge the entry from the meta-store
    begin
      purge(key)
    rescue NotImplementedError
      @@warned_on_purge ||= begin
        warn "WARNING: Future releases may require purge implementation for #{self.class.name}"
        true
      end
      nil
    end
  end
end

#store(request, response, entity_store) ⇒ Object

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.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/rack/cache/meta_store.rb', line 61

def store(request, response, entity_store)
  key = cache_key(request)
  stored_env = persist_request(request)

  # write the response body to the entity store if this is the
  # original response.
  if response.headers['X-Content-Digest'].nil?
    if request.env['rack-cache.use_native_ttl'] && response.fresh?
      digest, size = entity_store.write(response.body, response.ttl)
    else
      digest, size = entity_store.write(response.body)
    end
    response.headers['X-Content-Digest'] = digest
    response.headers['Content-Length'] = size.to_s unless response.headers['Transfer-Encoding']

    # If the entitystore backend is a Noop, do not try to read the body from the backend, it always returns an empty array
    unless entity_store.is_a? Rack::Cache::EntityStore::Noop
      # A stream body can only be read once and is currently closed by #write.
      # (To avoid having to keep giant objects in memory when writing to disk cache
      # the body is never converted to a single string)
      # We cannot always reply on body to be re-readable,
      # so we have to read it from the cache.
      # BUG: if the cache was unable to store a stream, the stream will be closed
      #      and rack will try to read it again, resulting in hard to track down exception
      response.body = entity_store.open(digest) || response.body
    end
  end

  # read existing cache entries, remove non-varying, and add this one to
  # the list
  vary = response.vary
  entries =
    read(key).reject do |env, res|
      (vary == (res['Vary'] || res['vary'])) &&
        requests_match?(vary, env, stored_env)
    end

  headers = persist_response(response)
  headers.delete('Age')
  headers.delete('age')

  entries.unshift [stored_env, headers]
  if request.env['rack-cache.use_native_ttl'] && response.fresh?
    write key, entries, response.ttl
  else
    write key, entries
  end
  key
end