class Racknga::Middleware::Cache

This is a middleware that provides page cache.

This stores page contents into a groonga database. A groonga database can access by multi process. It means that your Rack application processes can share the same cache. For example, Passenger runs your Rack application with multi processes.

Cache key is the request URL by default. It can be customized by env. For example, Racknga::Middleware::PerUserAgentCache and Racknga::Middleware::JSONP use it.

This only caches the following responses:

Usage:

use Racnkga::Middleware::Cache, :database_path => "var/cache/db"
run YourApplication

@see Racknga::Middleware::PerUserAgentCache @see Racknga::Middleware::JSONP @see Racknga::Middleware::Deflater @see Racknga::CacheDatabase

Constants

KEY
START_TIME

Attributes

database[R]

@return [Racknga::CacheDatabase] the database used

by this middleware.

Public Class Methods

new(application, options={}) click to toggle source

@option options [String] :database_path the database

path to be stored caches.
# File lib/racknga/middleware/cache.rb, line 97
def initialize(application, options={})
  @application = application
  @options = Utils.normalize_options(options || {})
  database_path = @options[:database_path]
  raise ArgumentError, ":database_path is missing" if database_path.nil?
  @database = CacheDatabase.new(database_path)
end

Public Instance Methods

call(environment) click to toggle source

For Rack.

# File lib/racknga/middleware/cache.rb, line 106
def call(environment)
  request = Rack::Request.new(environment)
  return @application.call(environment) unless use_cache?(request)
  age = @database.configuration.age
  key = normalize_key(environment[KEY] || request.fullpath)
  environment[START_TIME] = Time.now
  cache = @database.responses
  record = cache[key]
  if record and record.age == age
    handle_request_with_cache(cache, key, age, record, request)
  else
    handle_request(cache, key, age, request)
  end
end
close_database() click to toggle source

close the cache database.

# File lib/racknga/middleware/cache.rb, line 127
def close_database
  @database.close_database
end
ensure_database() click to toggle source

ensures creating cache database.

# File lib/racknga/middleware/cache.rb, line 122
def ensure_database
  @database.ensure_database
end

Private Instance Methods

compute_checksum(status, encoded_headers, encoded_body) click to toggle source
# File lib/racknga/middleware/cache.rb, line 204
def compute_checksum(status, encoded_headers, encoded_body)
  checksum = Digest::SHA1.new
  checksum << status.to_s
  checksum << ":"
  checksum << encoded_headers
  checksum << ":"
  checksum << encoded_body
  checksum.hexdigest.force_encoding("ASCII-8BIT")
end
handle_request(cache, key, age, request) click to toggle source
# File lib/racknga/middleware/cache.rb, line 160
def handle_request(cache, key, age, request)
  status, headers, body = @application.call(request.env)
  if skip_caching_response?(status, headers, body)
    log("skip", request)
    return [status, headers, body]
  end

  now = Time.now
  headers = Rack::Utils::HeaderHash.new(headers)
  headers["Last-Modified"] ||= now.httpdate
  stringified_body = ''
  body.each do |data|
    stringified_body << data
  end
  headers = headers.to_hash
  encoded_headers = headers.to_yaml
  encoded_body = stringified_body.force_encoding("ASCII-8BIT")
  cache[key] = {
    :status => status,
    :headers => encoded_headers,
    :body => encoded_body,
    :checksum => compute_checksum(status, encoded_headers, encoded_body),
    :age => age,
    :created_at => now,
  }
  body = [stringified_body]
  log("store", request)
  [status, headers, body]
end
handle_request_with_cache(cache, key, age, record, request) click to toggle source
# File lib/racknga/middleware/cache.rb, line 190
def handle_request_with_cache(cache, key, age, record, request)
  status = record.status
  headers = record.headers
  body = record.body
  checksum = record.checksum
  unless valid_cache?(status, headers, body, checksum)
    log("invalid", request)
    return handle_request(cache, key, age, request)
  end

  log("hit", request)
  [status, YAML.load(headers), [body]]
end
log(tag, request) click to toggle source
# File lib/racknga/middleware/cache.rb, line 220
def log(tag, request)
  return unless Middleware.const_defined?(:Log)
  env = request.env
  logger = env[Middleware::Log::LOGGER]
  return if logger.nil?
  start_time = env[START_TIME]
  runtime = Time.now - start_time
  logger.log("cache-#{tag}", request.fullpath, :runtime => runtime)
end
normalize_key(key) click to toggle source
# File lib/racknga/middleware/cache.rb, line 152
def normalize_key(key)
  if key.size > 4096
    Digest::SHA1.hexdigest(key).force_encoding("ASCII-8BIT")
  else
    key
  end
end
skip_caching_response?(status, headers, body) click to toggle source
# File lib/racknga/middleware/cache.rb, line 136
def skip_caching_response?(status, headers, body)
  return true if status != 200

  headers = Rack::Utils::HeaderHash.new(headers)
  content_type = headers["Content-Type"]
  if /\A(\w+)\/([\w.+\-]+)\b/ =~ content_type
    media_type = $1
    sub_type = $2
    return false if media_type == "text"
    return false if sub_type == "json"
    return false if sub_type == "xml"
    return false if /\+xml\z/ =~ sub_type
  end
  true
end
use_cache?(requeust) click to toggle source
# File lib/racknga/middleware/cache.rb, line 132
def use_cache?(requeust)
  requeust.get? or requeust.head?
end
valid_cache?(status, encoded_headers, encoded_body, checksum) click to toggle source
# File lib/racknga/middleware/cache.rb, line 214
def valid_cache?(status, encoded_headers, encoded_body, checksum)
  return false if status.nil? or encoded_headers.nil? or encoded_body.nil?
  return false if checksum.nil?
  compute_checksum(status, encoded_headers, encoded_body) == checksum
end