Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Tree: 3f42d3afe7
Fetching contributors…

Cannot retrieve contributors at this time

94 lines (86 sloc) 3.9 KB
module Rack
# The Rack::StaticCache middleware automatically adds, removes and modifies
# stuffs in response headers to facilitiate client and proxy caching for static files
# that minimizes http requests and improves overall load times for second time visitors.
# Once a static content is stored in a client/proxy the only way to enforce the browser
# to fetch the latest content and ignore the cache is to rename the static file.
# Alternatively, we can add a version number into the URL to the content to bypass
# the caches. Rack::StaticCache by default handles version numbers in the filename.
# As an example,
# and
# both reffers to the same image file
# Another way to bypass the cache is adding the version number in a field-value pair in the
# URL query string. As an example,
# In that case, set the option :versioning to false to avoid unneccessary regexp calculations.
# It's better to keep the current version number in some config file and use it in every static
# content's URL. So each time we modify our static contents, we just have to change the version
# number to enforce the browser to fetch the latest content.
# You can use Rack::Deflater along with Rack::StaticCache for further improvements in page loading time.
# Examples:
# use Rack::StaticCache, :urls => ["/images", "/css", "/js", "/documents*"], :root => "statics"
# will serve all requests beginning with /images, /csss or /js from the
# directory "statics/images", "statics/css", "statics/js".
# All the files from these directories will have modified headers to enable client/proxy caching,
# except the files from the directory "documents". Append a * (star) at the end of the pattern
# if you want to disable caching for any pattern . In that case, plain static contents will be served with
# default headers.
# use Rack::StaticCache, :urls => ["/images"], :duration => 2, :versioning => false
# will serve all requests begining with /images under the current directory (default for the option :root
# is current directory). All the contents served will have cache expiration duration set to 2 years in headers
# (default for :duration is 1 year), and StaticCache will not compute any versioning logics (default for
# :versioning is true)
class StaticCache
def initialize(app, options={})
@app = app
@urls = options[:urls]
@no_cache = {}
@urls.collect! do |url|
if url =~ /\*$/
url.sub!(/\*$/, '')
@no_cache[url] = 1
root = options[:root] || Dir.pwd
@file_server =
@cache_duration = options[:duration] || 1
@versioning_enabled = true
@versioning_enabled = options[:versioning] unless options[:versioning].nil?
@duration_in_seconds = self.duration_in_seconds
@duration_in_words = self.duration_in_words
def call(env)
path = env["PATH_INFO"]
url = @urls.detect{ |u| path.index(u) == 0 }
unless url.nil?
path.sub!(/-[\d.]+([.][a-zA-Z][\w]+)?$/, '\1') if @versioning_enabled
status, headers, body =
if @no_cache[url].nil?
headers['Cache-Control'] ="max-age=#{@duration_in_seconds}, public"
headers['Expires'] = @duration_in_words
headers.delete 'Etag'
headers.delete 'Pragma'
headers.delete 'Last-Modified'
[status, headers, body]
def duration_in_words
( + self.duration_in_seconds).strftime '%a, %d %b %Y %H:%M:%S GMT'
def duration_in_seconds
60 * 60 * 24 * 365 * @cache_duration
Jump to Line
Something went wrong with that request. Please try again.