rack-cache-buster 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,34 @@
1
+ # Rack::CacheBuster
2
+
3
+ Place this in your rack stack and all caching will be gone.
4
+
5
+ Add an optional key to salt the ETags in and out of your app.
6
+
7
+ ## Usage:
8
+ use Rack::CacheBuster, APP_VERSION
9
+
10
+ # Rack::CacheBuster::Auto
11
+
12
+ Use to wind down a running app when needed.
13
+
14
+ Busts the cache when enabled file exists, salts the ETags when key_file exists.
15
+
16
+ ## Usage:
17
+
18
+ ### Setup:
19
+
20
+ ensure you have a REVISION file on your production servers, capistrano does this by default.
21
+
22
+ use Rack::CacheBuster::Auto, File.join(Rails.root, "REVISION"), File.join(Rails.root, "WIND_DOWN")
23
+
24
+ ### Before you deploy:
25
+
26
+ * Find the maximum cache duration for all pages in your app, start this process with at least that amount time before you deploy.
27
+
28
+ * Create a WIND_DOWN file in the app root of all your app servers.
29
+
30
+ * Restart all instances (touch tmp/restart.txt will be fine for passenger apps).
31
+
32
+ During this period all caching will be disabled. If you respect If-Modified (using `stale?` in rails for example), these will still be respected.
33
+
34
+ Once all the caches should have expired you can deploy as normal.
@@ -0,0 +1,54 @@
1
+ require 'digest/md5'
2
+
3
+ module Rack
4
+ class CacheBuster
5
+ autoload :Auto, "rack/cache_buster/auto"
6
+
7
+ def initialize(app, key = nil)
8
+ @app = app
9
+ if key
10
+ @key = "-"+Digest::MD5.hexdigest(key).freeze
11
+ @key_regexp = /#{@key}/.freeze
12
+ end
13
+ end
14
+
15
+ def call(env)
16
+ env = env.dup
17
+ unpatch_etag!(env) if key
18
+
19
+ status, headers, body = app.call(env)
20
+
21
+ headers = headers.dup
22
+ patch_etag!(headers) if key
23
+ bust_cache!(headers)
24
+
25
+ [status, headers, body]
26
+ end
27
+
28
+ protected
29
+ QUOTE_STRIPPER=/^"|"$/.freeze
30
+ ETAGGY_HEADERS = ["HTTP_IF_NONE_MATCH", "HTTP_IF_MATCH", "HTTP_IF_RANGE"].freeze
31
+ ETag = "ETag".freeze
32
+ CacheControl = "Cache-Control".freeze
33
+
34
+ attr_reader :app
35
+ attr_reader :key
36
+
37
+
38
+ def bust_cache!(headers)
39
+ headers[CacheControl] = "no-cache"
40
+ end
41
+
42
+ def unpatch_etag!(headers)
43
+ ETAGGY_HEADERS.each do |k|
44
+ headers[k] = headers[k].gsub(@key_regexp, "") if headers[k]
45
+ end
46
+ end
47
+
48
+ def patch_etag!(headers)
49
+ stripped_tag = headers[ETag].to_s.gsub(QUOTE_STRIPPER, "")
50
+ return if stripped_tag.empty?
51
+ headers[ETag] = %Q{"#{stripped_tag}#{key}"}
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ require "rack/cache_buster"
2
+
3
+ class Rack::CacheBuster::Auto < Rack::CacheBuster
4
+ def initialize(app, key_filename, enabled_filename)
5
+ @enabled = File.exists?(enabled_filename)
6
+ key = File.exists?(key_filename) && File.open(key_filename).gets.chomp
7
+ super(app, key)
8
+ end
9
+
10
+ def bust_cache!(headers)
11
+ @enabled ? super(headers) : headers
12
+ end
13
+ end
data/test/auto_test.rb ADDED
@@ -0,0 +1,70 @@
1
+ #FOO
2
+ require "test_helper"
3
+
4
+ class CacheBusterAutoTest < IntegrationTest
5
+ DIGEST_OF_HASH_FOO=Digest::MD5.hexdigest("#FOO")
6
+
7
+ context "with no key files" do
8
+ setup do
9
+ @app = Rack::CacheBuster::Auto.new(CACHEY_APP, "/no-file-here.txt", "/no-file-here.txt")
10
+ end
11
+
12
+ should "not set cache-control to no-cache" do
13
+ get "/foooo"
14
+ assert_not_equal "no-cache", last_response["Cache-Control"]
15
+ end
16
+
17
+ should "leave the ETag alone" do
18
+ get "/foooo"
19
+ assert_equal %Q{"an-etag"}, last_response["ETag"]
20
+ end
21
+ end
22
+
23
+ context "with a key file" do
24
+ setup do
25
+ @app = Rack::CacheBuster::Auto.new(CACHEY_APP, __FILE__, "/no-file-here.txt")
26
+ end
27
+
28
+ should "still not set cache-control to no-cache" do
29
+ get "/foooo"
30
+ assert_not_equal "no-cache", last_response["Cache-Control"]
31
+ end
32
+
33
+ should "fiddle with the ETag" do
34
+ get "/foooo"
35
+ assert_equal %Q{"an-etag-#{DIGEST_OF_HASH_FOO}"}, last_response["ETag"]
36
+ end
37
+ end
38
+
39
+ context "with an enabled file" do
40
+ setup do
41
+ @app = Rack::CacheBuster::Auto.new(CACHEY_APP, "/no-file-here.txt", __FILE__)
42
+ end
43
+
44
+ should "set cache-control to no-cache" do
45
+ get "/foooo"
46
+ assert_equal "no-cache", last_response["Cache-Control"]
47
+ end
48
+
49
+ should "leave the ETag alone" do
50
+ get "/foooo"
51
+ assert_equal %Q{"an-etag"}, last_response["ETag"]
52
+ end
53
+ end
54
+
55
+ context "with an enabled file and a key file" do
56
+ setup do
57
+ @app = Rack::CacheBuster::Auto.new(CACHEY_APP, __FILE__, __FILE__)
58
+ end
59
+
60
+ should "set cache-control to no-cache" do
61
+ get "/foooo"
62
+ assert_equal "no-cache", last_response["Cache-Control"]
63
+ end
64
+
65
+ should "fiddle with the ETag" do
66
+ get "/foooo"
67
+ assert_equal %Q{"an-etag-#{DIGEST_OF_HASH_FOO}"}, last_response["ETag"]
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,47 @@
1
+ require "test_helper"
2
+
3
+ class CacheBustingTest < IntegrationTest
4
+ DIGEST_OF_FOO=Digest::MD5.hexdigest("foo")
5
+
6
+ should "forward the request to the app" do
7
+ given_env = nil
8
+ @app = Rack::CacheBuster.new(lambda { |env| given_env = env; [200, {}, []] })
9
+ get "/foooo"
10
+ assert_not_nil given_env
11
+ assert_equal given_env["PATH_INFO"], "/foooo"
12
+ end
13
+
14
+ should "forward the request to the app with digests removed" do
15
+ given_env = nil
16
+
17
+ @app = Rack::CacheBuster.new(lambda { |env| given_env = env; [200, {}, []] }, "foo")
18
+
19
+ ["If-None-Match", "If-Match", "If-Range"].each do |k|
20
+ header(k, "foo-#{DIGEST_OF_FOO}")
21
+ end
22
+
23
+ get "/foooo"
24
+
25
+ Rack::CacheBuster::ETAGGY_HEADERS.each do |k|
26
+ assert_equal "foo", given_env[k], "#{k} should be set to foo"
27
+ end
28
+ end
29
+
30
+ should "set cache-control to no-cache" do
31
+ @app = Rack::CacheBuster.new(CACHEY_APP)
32
+ get "/foooo"
33
+ assert_equal "no-cache", last_response["Cache-Control"]
34
+ end
35
+
36
+ should 'append version to etag of response' do
37
+ @app = Rack::CacheBuster.new(CACHEY_APP, "foo")
38
+ get "/"
39
+ assert_equal %Q{"an-etag-#{DIGEST_OF_FOO}"}, last_response["ETag"]
40
+ end
41
+
42
+ should "leave the ETag alone if no key is given" do
43
+ @app = Rack::CacheBuster.new(CACHEY_APP)
44
+ get "/foooo"
45
+ assert_equal %Q{"an-etag"}, last_response["ETag"]
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ $:<<File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require 'shoulda'
3
+ require 'rack/cache_buster'
4
+ require 'test/unit'
5
+ require 'rack/test'
6
+
7
+ class IntegrationTest < Test::Unit::TestCase
8
+ include Rack::Test::Methods
9
+ attr_reader :app
10
+
11
+ CACHEY_APP = lambda{|env| [200, {"Cache-Control" => "max-age=200, public", "ETag" => '"an-etag"'}, []] }
12
+
13
+ def test_nothing
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-cache-buster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Lea
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-21 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: commit@tomlea.co.uk
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - README.markdown
26
+ - test/auto_test.rb
27
+ - test/cache_busting_test.rb
28
+ - test/test_helper.rb
29
+ - lib/rack/cache_buster/auto.rb
30
+ - lib/rack/cache_buster.rb
31
+ has_rdoc: true
32
+ homepage: http://tomlea.co.uk/
33
+ licenses: []
34
+
35
+ post_install_message:
36
+ rdoc_options:
37
+ - --main
38
+ - README.markdown
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project: rack-cache-buster
56
+ rubygems_version: 1.3.5
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Place this in your rack stack and all caching will be gone.
60
+ test_files: []
61
+