Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sinatra - Setting Cache-Control Headers via config.ru

I'm currently running an Octopress (based on Jekyll) site on Heroku's Cedar stack—the code lives here: https://github.com/elithrar/octopress

I want to selectively apply a Cache-Control header based on the file type:

  • .html files get a value of public, max-age=3600
  • .css|.js|.png|.ico (etc) get a value of public, max-age=604800 - alternatively, I'd like to apply this rule to anything served from the /stylesheets', '/javascripts', '/imgs' directories.

Have used both set :static_cache_control , [:public, :max_age => 3600] and just the vanilla cache_control :public, :max_age => 3600 statements with no luck.

I have managed to set public, max-age=3600 on the articles themselves (e.g. /2012/lazy-sundays/), but have not been able to get the headers to apply to the CSS/JS (e.g. /stylesheets/screen.css)

My config.ru currently looks like this (updated):

require 'bundler/setup'
require 'sinatra/base'

# The project root directory
$root = ::File.dirname(__FILE__)

class SinatraStaticServer < Sinatra::Base

  get(/.+/) do
    cache_control :public, :max_age => 7200
    send_sinatra_file(request.path) {404}
  end

  not_found do
    send_sinatra_file('404.html') {"Sorry, I cannot find #{request.path}"}
    cache_control :no_cache, :max_age => 0
  end

  def send_sinatra_file(path, &missing_file_block)
    file_path = File.join(File.dirname(__FILE__), 'public',  path)
    file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i  
    File.exist?(file_path) ? send_file(file_path) : missing_file_block.call
  end

end

use Rack::Deflater

run SinatraStaticServer
like image 333
elithrar Avatar asked Apr 18 '26 20:04

elithrar


2 Answers

Here's how to set long expiry headers for static assets, and an arbitrary expiry header for you main content on Heroku:

gemfile:

gem 'rack-contrib'

config.ru:

require 'rack/contrib'

get '*.html' do |page|
  # whatever code you need to serve up your main pages
  # goes here... use Rack::File I guess.
  page
end


# Set content headers for that content...
before do
  expires 5001, :public, :must_revalidate
end


# Assets in /static/stylesheets (domain.com/stylesheets) 
# are served by Rack StaticCache, with a default 2 year expiry.

use Rack::StaticCache, :urls => ["/stylesheets"], :root => Dir.pwd + '/static'

run Sinatra::Application

By default that will give you a 2 year expiry for content listed in the array of urls (static/stylesheets, static/images etc.).

You have to move from /public to /static because otherwise you are unnecessarily fighting with Heroku's nginx config (the right place to apply these sorts of settings really...).

I know you said you're trying to not use Rack Contrib but that makes no sense. There's no harm in using a tiny 90 line library to do this https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/static_cache.rb.

The "right" way would be to host static content on an environment where you can configure nginx, and the second best way is renaming your static file path so heroku ignores it, and use rack static to serve static files with the headers you want.

--

Also to be clear, simply renaming your public folder to something else will allow you to do this via routes, and the normal Sinatra expires function. But I'd use StaticCache because it's less verbose. (The real issue is Heroku doesn't let nginx talk to your app for requests to public/, I believe.)

like image 165
robomc Avatar answered Apr 21 '26 12:04

robomc


I have very little familiarity with Sinatra, but I think something like this would do the trick:

class SinatraStaticServer < Sinatra::Base
  before '*.html' do
    response.headers['Cache-Control'] = 'public, max-age=3600'
  end

  before %r{\.(css)|(js)|(png)|(ico)} do
    response.headers['Cache-Control'] = 'public, max-age=604800'
  end

  # ...
end

Update: I looked into it further when you said that the above was not successfully getting the headers added. I determined that the issue was that Sinatra was automatically serving the files out of public/ rather than going through the app, and thus the headers weren't being added. My solution was to move the static files from public/ to public/public/ and adjust send_sinatra_file accordingly:

class SinatraStaticServer < Sinatra::Base
  # ...

  def send_sinatra_file(path, &missing_file_block)
    file_path = File.join(File.dirname(__FILE__), 'public/public',  path)
    file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i
    File.exist?(file_path) ? send_file(file_path) : missing_file_block.call
  end

  # ...
end

I confirmed that this works on my machine. Note that I used response.headers['Cache-Control'] as in the first part of my answer, not set :static_cache_control which you tried, but I think is meant to only be run once, in a configure do block.

Also note that with this current set-up, a 404 that matches the above, e.g. nonexistant.png will serve a 404 status with the Cache-Control header still there. I can see several ways around that, but I figure you do to, so I'm just pointing it out and figure you'll deal with it however you like.

like image 43
Darshan Rivka Whittle Avatar answered Apr 21 '26 12:04

Darshan Rivka Whittle



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!