= lacquer

Rails drop in for Varnish support.

== Install
Basic installation

  sudo gem install lacquer  
  rails generate lacquer:install

config/initializers/lacquer.rb

  Lacquer.configure do |config|
    # Globally enable/disable cache
    config.enable_cache = true

    # Unless overridden in a controller or action, the default will be used
    config.default_ttl = 1.week

    # Can be :none, :delayed_job, :resque
    config.job_backend = :none

    # Array of Varnish servers to manage
    config.varnish_servers << {
      :host => "0.0.0.0", :port => 6082 # if you have authentication enabled, add :secret => "your secret"
    }

    # Number of retries
    config.retries = 5

    # config handler (optional, if you use Hoptoad or another error tracking service)
    config.command_error_handler = lambda { |s| HoptoadNotifier.notify(s) }
  end

app/controllers/application_controller.rb

  class ApplicationController < ActionController::Base
    include Lacquer::CacheUtils
  end
  
config/varnishd.yml

  development:
    listen: localhost:3001
    telnet: localhost:6082
    sbin_path: /usr/local/sbin
    storage: "file,#{Rails.root}/log/varnishd.#{Rails.env}.cache,100MB"
    
  test:
    listen: localhost:3002
    telnet: localhost:6083
    sbin_path: /usr/local/sbin
    storage: "file,#{Rails.root}/log/varnishd.#{Rails.env}.cache,100MB"

  production:
    listen: :80
    telnet: localhost:6082
    sbin_path: /usr/local/sbin
    storage: "file,#{Rails.root}/log/varnishd.#{Rails.env}.cache,100MB"
    params:
      overflow_max: 2000
      thread_pool_add_delay: 2
      thread_pools: 4             # <Number of cpu cores>
      thread_pool_min: 200        # <800/number of cpu cores>
      thread_pool_max: 4000

If only some urls of the application should be cached by varnish, Lacquer::CacheControl will be helpful.

config/initializers/caches.rb

  require "lacquer/cache_control"

  Lacquer.cache_control.configure do |config|
    config.register :static,              :url => "^/images",                                           
                                          :expires_in => "365d"
                                        
    config.register :static,              :url => "^/stylesheets",
                                          :expires_in => "365d"
                                        
    config.register :static,              :url => "^/javascripts",                                       
                                          :expires_in => "365d"
  
    config.register :class_section,       :url => "^(/[a-z]{2})?/(info_screens|class_sections)/%s.*$",   
                                          :args => "[0-9]+", 
                                          :expires_in => "1m"
                                        
    config.register :open_scoring,        :url => "^(/[a-z]{2})?/class_sections/%s/open_scoring.*$",
                                          :args => "[0-9]+",
                                          :expires_in => "1m"
                                        
  end

In the sweeper we can do something like this

  class_section = ClassSection.find(1)
  Lacquer.cache_control.purge(:open_scoring, class_section) 
  
This will purge "^(/[a-z]{2})?/class_sections/1/open_scoring.*$" (/sv/class_sections/1/open_scoring.js, /sv/class_sections/1/open_scoring.html)
  
The varnish.vcl is preprocssed when starting varnishd with the rake tasks

  rake varnishd:start
  
config/varnish.vcl.erb

  sub vcl_recv {
    # Lookup requests that we know should be cached
    if (<%= Lacquer.cache_control.to_vcl_conditions %>) {    
      # Clear cookie and authorization headers, set grace time, lookup in the cache
      unset req.http.Cookie;
      unset req.http.Authorization;
      return(lookup);
    }
    
    # Generates
    #
    # if(req.url ~ "^/images" || 
    #    req.url ~ "^/stylesheets" || 
    #    req.url ~ "^/javascripts" || 
    #    req.url ~ "^(/[a-z]{2})?/(info_screens|class_sections)/[0-9]+.*$" || 
    #    req.url ~ "^(/[a-z]{2})?/class_sections/[0-9]+/open_scoring.*$") {
    #    unset req.http.Cookie;
    #    unset req.http.Authorization;
    #    return(lookup);         
    # }
  }

  sub vcl_fetch {
    <%= Lacquer.cache_control.to_vcl_override_ttl_urls %>
    
    # Generates
    #
    # if(req.url ~ "^/images" || req.url ~ "^/stylesheets" || req.url ~ "^/javascripts") {
    #   unset beresp.http.Set-Cookie;
    #   set beresp.ttl = 365d;
    #   return(deliver);
    # }
    #
    # if(req.url ~ "^(/[a-z]{2})?/(info_screens|class_sections)/[0-9]+.*$" || 
    #   req.url ~ "^(/[a-z]{2})?/class_sections/[0-9]+/open_scoring.*$") {
    #   unset beresp.http.Set-Cookie;
    #   set beresp.ttl = 1m;
    #   return(deliver);
    # }
  }
 
This makes it much simpler to perform cacheing, it's only setuped in one place, purge it or just let it expire.
  
== Usage

To set a custom ttl for a controller:

  before_filter { |controller| controller.set_cache_ttl(15.minutes) }

Clearing the cache:

  class Posts < ApplicationController
    after_filter :clear_cache, :only => [ :create, :update, :destroy ]

  private

    def clear_cache
      clear_cache_for(
        root_path,
        posts_path,
        post_path(@post))
    end
  end
  
Control varnishd with the following rake tasks

  rake varnishd:start
  rake varnishd:stop
  rake varnishd:restart
  rake varnishd:status
  rake varnishd:global_purge  

== Gotchas

The default TTL for most actions is set to 0, since for most cases you'll probably want to be fairly explicit about what pages do get cached by varnish. The default cache header is typically:

  Cache-Control: max-age=0, no-cache, private

This is good for normal controller actions, since you won't want to cache them. If TTL for an action is set to 0, it won't mess with the default header.

The key gotcha here is that cached pages strip cookies, so if your application relies on sessions and uses authenticity tokens, the user will need a session cookie set before form actions will work. Setting default TTL to 0 here will make sure these session cookies won't break.

As a result, all you have to do to set a cacheable action is the before filter above.

== Note on Patches/Pull Requests

* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a future version unintentionally.
* Commit, do not mess with rakefile, version, or history.  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.

== Copyright

Copyright (c) 2010 Russ Smith. See LICENSE for details.