Edward’s Notes

Technical topics and photos.

Replacing Page Cache With Varnish

I am finally getting somewhere with configuring varnish to run in front of typo. I just need to add code to purge the varnish cache when pages are expired and add ESI support so page can be semi cached in varnish and composed on demand. The following is the code so fare. First adding PURGE to net/http

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require "net/http"
module Net
  class HTTP
    class Purge < HTTPRequest
      METHOD = "PURGE"
      REQUEST_HAS_BODY = false
      RESPONSE_HAS_BODY = false
    end
    def purge(path, initheader = nil)
      request(Purge.new(path, initheader))
    end
  end
  def HTTP.purge(path, initheader = nil)
    uri = URI.parse(path)
    new(uri.host,uri.port).start do |http|
      http.purge(uri.path)
    end
  end
end

Then overriding the page cache.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module VarnishCache
  module Caching
    def self.included(base) #:nodoc:
      base.class_eval do
        include Pages
      end
    end
  end
  module Pages
    def self.included(base) #:nodoc:
      base.extend(ClassMethods)
    end
    module ClassMethods
      def cache_page(content, path)
        logger.info "VarnishCache cache_page(#{path})"
        content
      end
      def caches_action(*actions)
        logger.info "VarnishCache caches_action"
      end
    end
  end
end

I also had to make changes to the PageCache class zap_pages method so that it forced varnish to purge the cache

app/models/page_cache.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def self.zap_pages(paths)
  logger.debug "PageCache - About to zap: #{paths.inspect}"
  srcs = paths.inject([]) { |o,v|
    o + Dir.glob(public_path + "/#{v}")
  }
  varnish_purge(paths)
  return true if srcs.empty?
  logger.debug "PageCache - About to delete: #{srcs.inspect}"
  trash = RAILS_ROOT + "/tmp/typodel.#{UUID.random_create}"
  FileUtils.makedirs(trash)
  FileUtils.mv(srcs, trash, :force => true)
  FileUtils.rm_rf(trash)
end

def self.varnish_purge(paths)
  paths.split.flatten.each do |path|
    uri = "http://blog.vortorus.net/#{path}"
    Net::HTTP.purge(uri)
    logger.debug "Varnish purging #{uri}"
  end
end

The is also the varnish vcl file changes to make purge work

/etc/varnish/default.vcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
sub vcl_recv {
  # Host header check.
  if (!req.http.host ~ "blog.vortorus.net") {
     error 404 "Unknown virtual host";
  }

  if (req.request != "GET" && req.request != "HEAD") {
    if (req.request == "PURGE") {
      if (client.ip ~ purge) {
        purge_url(req.url);
        error 200 "Purged";
      }
    }
    pipe;
  }

  if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") {
    remove req.http.cookie;
    remove req.http.authenticate;
    lookup;
  }

  # only need session for admin functions
  if (req.url ~ "^/(admin|acounts)") {
    pipe;
  } else {
    # disable Etags
    remove req.http.If-None-Match;
    remove req.http.cookie;
    remove req.http.authenticate;
    lookup;
  }
}

sub vcl_hit {
  deliver;
}

sub vcl_fetch {
  if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") {
    deliver;
  }
  if (req.url ~ "^/(admin|acounts)") {
    pass;
  } else {
    remove obj.http.Set-Cookie;
    remove obj.http.Etag;
    set obj.http.Cache-Control = "no-cache";
    set obj.ttl = 7d;
    deliver;
  }
}

The source for the varnish rails plugin can be found hear

$ git clone git://git.vortorus.net/varnish-cache.git

Comments