Class: Rack::ResponseCache

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/contrib/response_cache.rb

Overview

Rack::ResponseCache is a Rack middleware that caches responses for successful GET requests with no query string to disk or any ruby object that has an []= method (so it works with memcached). As with Rails’ page caching, this middleware only writes to the cache – it never reads. The logic of whether a cached response should be served is left either to your web server, via something like the try_files directive in nginx, or to your cache-reading middleware of choice, mounted before Rack::ResponseCache in the stack.

Constant Summary collapse

DEFAULT_PATH_PROC =

The default proc used if a block is not provided to .new It unescapes the PATH_INFO of the environment, and makes sure that it doesn’t include ‘..’. If the Content-Type of the response is text/(html|css|xml), return a path with the appropriate extension (.html, .css, or .xml). If the path ends with a / and the Content-Type is text/html, change the basename of the path to index.html.

proc do |env, res|
  path = Rack::Utils.unescape(env['PATH_INFO'])
  headers = res[1]
  # Content-Type is almost always at headers['Content-Type'], but to fully
  # comply with HTTP RFC 7230, we fall back to a case-insensitive lookup
  content_type = headers.fetch('Content-Type') do |titlecase_key|
    _, val = headers.find { |key, _| key.casecmp(titlecase_key) == 0 }
    val
  end
  if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(content_type)
    type = match[1]
    path = "#{path}.#{type}" unless /\.#{type}\z/.match(path)
    path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html'
    path
  end
end

Instance Method Summary collapse

Constructor Details

#initialize(app, cache, &block) ⇒ ResponseCache

Initialize a new ResponseCache object with the given arguments. Arguments:

  • app : The next middleware in the chain. This is always called.

  • cache : The place to cache responses. If a string is provided, a disk cache is used, and all cached files will use this directory as the root directory. If anything other than a string is provided, it should respond to []=, which will be called with a path string and a body value (the 3rd element of the response).

  • &block : If provided, it is called with the environment and the response from the next middleware. It should return nil or false if the path should not be cached, and should return the pathname to use as a string if the result should be cached. If not provided, the DEFAULT_PATH_PROC is used.



46
47
48
49
50
# File 'lib/rack/contrib/response_cache.rb', line 46

def initialize(app, cache, &block)
  @app = app
  @cache = cache
  @path_proc = block || DEFAULT_PATH_PROC
end

Instance Method Details

#call(env) ⇒ Object

Call the next middleware with the environment. If the request was successful (response status 200), was a GET request, and had an empty query string, call the block set up in initialize to get the path. If the cache is a string, create any necessary middle directories, and cache the file in the appropriate subdirectory of cache. Otherwise, cache the body of the response as the value with the path as the key.



56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/rack/contrib/response_cache.rb', line 56

def call(env)
  res = @app.call(env)
  if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and res[0] == 200 and path = @path_proc.call(env, res)
    if @cache.is_a?(String)
      path = File.join(@cache, path) if @cache
      FileUtils.mkdir_p(File.dirname(path))
      File.open(path, 'wb'){|f| res[2].each{|c| f.write(c)}}
    else
      @cache[path] = res[2]
    end
  end
  res
end