class ResponseBank::ResponseCacheHandler

Public Class Methods

new( key_data:, version_data:, env:, cache_age_tolerance:, serve_unversioned:, headers:, force_refill_cache: false, cache_store: ResponseBank.cache_store, &block ) click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 6
def initialize(
  key_data:,
  version_data:,
  env:,
  cache_age_tolerance:,
  serve_unversioned:,
  headers:,
  force_refill_cache: false,
  cache_store: ResponseBank.cache_store,
  &block
)
  @cache_miss_block = block

  @key_data = key_data
  @version_data = version_data
  @env = env
  @cache_age_tolerance = cache_age_tolerance

  @serve_unversioned = serve_unversioned
  @force_refill_cache = force_refill_cache
  @cache_store = cache_store
  @headers = headers || {}
end

Public Instance Methods

run!() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 30
def run!
  @env['cacheable.cache']           = true
  @env['cacheable.key']             = versioned_key_hash
  @env['cacheable.unversioned-key'] = unversioned_key_hash

  ResponseBank.log(cacheable_info_dump)

  if @force_refill_cache
    refill_cache
  else
    try_to_serve_from_cache
  end
end
unversioned_key_hash() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 48
def unversioned_key_hash
  @unversioned_key_hash ||= key_hash(unversioned_key)
end
versioned_key_hash() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 44
def versioned_key_hash
  @versioned_key_hash ||= key_hash(versioned_key)
end

Private Instance Methods

cacheable_info_dump() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 66
def cacheable_info_dump
  log_info = [
    "Raw cacheable.key: #{versioned_key}",
    "cacheable.key: #{versioned_key_hash}",
  ]

  if @env['HTTP_IF_NONE_MATCH']
    log_info.push("If-None-Match: #{@env['HTTP_IF_NONE_MATCH']}")
  end

  log_info.join(', ')
end
key_hash(key) click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 54
def key_hash(key)
  "cacheable:#{Digest::MD5.hexdigest(key)}"
end
page_too_old?(timestamp, cache_age_tolerance) click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 164
def page_too_old?(timestamp, cache_age_tolerance)
  !timestamp || timestamp < (Time.now.to_i - cache_age_tolerance)
end
refill_cache() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 168
def refill_cache
  @env['cacheable.miss'] = true

  ResponseBank.log("Refilling cache")

  @cache_miss_block.call
end
serve_from_browser_cache(cache_key_hash) click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 115
def serve_from_browser_cache(cache_key_hash)
  if @env["HTTP_IF_NONE_MATCH"] == cache_key_hash
    @env['cacheable.miss']  = false
    @env['cacheable.store'] = 'client'

    @headers.delete('Content-Type')
    @headers.delete('Content-Length')

    ResponseBank.log("Cache hit: client")

    [304, @headers, []]
  end
end
serve_from_cache(cache_key_hash, message, cache_age_tolerance = nil) click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 129
def serve_from_cache(cache_key_hash, message, cache_age_tolerance = nil)
  raw = ResponseBank.read_from_backing_cache_store(@env, cache_key_hash, backing_cache_store: @cache_store)

  if raw
    hit = MessagePack.load(raw)

    @env['cacheable.miss']  = false
    @env['cacheable.store'] = 'server'

    status, content_type, body, timestamp, location = hit

    if cache_age_tolerance && page_too_old?(timestamp, cache_age_tolerance)
      ResponseBank.log("Found an unversioned cache entry, but it was too old (#{timestamp})")

      nil
    else
      @headers['Content-Type'] = content_type

      @headers['Location'] = location if location

      if @env["gzip"]
        @headers['Content-Encoding'] = "gzip"
      else
        # we have to uncompress because the client doesn't support gzip
        ResponseBank.log("uncompressing for client without gzip")
        body = ResponseBank.decompress(body)
      end

      ResponseBank.log(message)

      [status, @headers, [body]]
    end
  end
end
serving_from_noncurrent_but_recent_version_acceptable?() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 111
def serving_from_noncurrent_but_recent_version_acceptable?
  @cache_age_tolerance > 0
end
try_to_serve_from_cache() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 79
def try_to_serve_from_cache
  # Etag
  response = serve_from_browser_cache(versioned_key_hash)

  return response if response

  # Memcached
  response = if @serve_unversioned
    serve_from_cache(unversioned_key_hash, "Cache hit: server (unversioned)")
  else
    serve_from_cache(versioned_key_hash, "Cache hit: server")
  end

  return response if response

  @env['cacheable.locked'] ||= false

  if @env['cacheable.locked'] || ResponseBank.acquire_lock(versioned_key_hash)
    # execute if we can get the lock
    @env['cacheable.locked'] = true
  elsif serving_from_noncurrent_but_recent_version_acceptable?
    # serve a stale version
    response = serve_from_cache(unversioned_key_hash, "Cache hit: server (recent)", @cache_age_tolerance)

    return response if response
  end

  # No cache hit; this request cannot be handled from cache.
  # Yield to the controller and mark for writing into cache.
  refill_cache
end
unversioned_key() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 62
def unversioned_key
  @unversioned_key ||= ResponseBank.cache_key_for(key: @key_data)
end
versioned_key() click to toggle source
# File lib/response_bank/response_cache_handler.rb, line 58
def versioned_key
  @versioned_key ||= ResponseBank.cache_key_for(key: @key_data, version: @version_data)
end