kivanio (owner)

Fork Of

Revisions

gist: 227910 Download_button fork
public
Public Clone URL: git://gist.github.com/227910.git
Embed All Files: show embed
cache_buster/cache_control_header.rb #
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
require "rack/cache_buster"
 
class Rack::CacheBuster::CacheControlHeader
  CacheControl = "Cache-Control".freeze
  Age = "Age".freeze
  MaxAge = "max-age".freeze
 
  def initialize(env)
    @age = env[Age].to_i
    @max_age = 0
    if env[CacheControl]
      parts = env[CacheControl].split(/ *, */)
      settings, options = parts.partition{|part| part =~ /=/ }
settings = settings.inject({}){|acc, part|
k, v = part.split(/ *= */, 2)
        acc.merge(k => v)
      }
      @max_age = settings.delete(MaxAge).to_i
      @other_parts = options + settings.map{|k,v| "#{k}=#{v}"}
    end
  end
 
  def expires_in
    @max_age - @age
  end
 
  def expired?
    expires_in <= 0
  end
 
  def expire_time
    Time.now + expires_in
  end
 
  def expire_time=(t)
    @max_age = [t.to_i - Time.now.to_i + @age, @age].max
  end
 
  def update_env(env)
    env[CacheControl] = to_s
  end
 
  def to_s
    ["max-age=#{@max_age}", *@other_parts].join(", ")
  end
end
 
cache_buster/rails.rb #
1
2
3
4
5
6
7
8
9
10
require "rack/cache_buster"
 
class Rack::CacheBuster::Rails < Rack::CacheBuster
  def initialize(app)
    app_version = File.read(File.join(::Rails.root, "REVISION")) rescue nil
    wind_down_time = Time.parse(File.read(File.join(::Rails.root, "WIND_DOWN"))) rescue nil
    super(app, app_version, wind_down_time)
  end
end
 
cache_buster.rb #
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
53
54
55
56
57
58
59
60
61
# Full code is at http://github.com/cwninja/rack-cache-buster
 
require 'digest/md5'
 
module Rack
  class CacheBuster
    def initialize(app, key, target_time = nil)
      @app, @target_time = app, target_time
      @key = "-"+Digest::MD5.hexdigest(key || "blank-key").freeze
      @key_regexp = /#{@key}/.freeze
    end
 
    def call(env)
      status, headers, body = app.call(unpatch_etag(env))
      [status, patch_etag(limit_cache(headers)), body]
    end
 
  protected
    QUOTE_STRIPPER=/^"|"$/.freeze
    ETAGGY_HEADERS = ["HTTP_IF_NONE_MATCH", "HTTP_IF_MATCH", "HTTP_IF_RANGE"].freeze
    ETag = "ETag".freeze
 
    attr_reader :app
    attr_reader :key
 
 
    def limit_cache(headers)
      return headers if @target_time.nil?
      cache_control_header = CacheControlHeader.new(headers)
      if cache_control_header.expired?
        headers
      else
        cache_control_header.expire_time = [@target_time, cache_control_header.expire_time].min
        headers.merge(CacheControlHeader::CacheControl => cache_control_header.to_s)
      end
    end
 
    def unpatch_etag(headers)
      ETAGGY_HEADERS.inject(headers){|memo, k|
        memo.has_key?(k) ? memo.merge(k => strip_etag(memo[k])) : memo
      }
    end
 
    def strip_etag(s)
      s.gsub(@key_regexp, "")
    end
 
    def modify_etag(s)
      s = s.to_s.gsub(QUOTE_STRIPPER, "")
      s.empty? ? s : %Q{"#{s}#{key}"}
    end
 
    def patch_etag(headers)
      headers.merge(ETag => modify_etag(headers[ETag]))
    end
 
    autoload :CacheControlHeader, "rack/cache_buster/cache_control_header"
    autoload :Rails, "rack/cache_buster/rails"
  end
end