rack-cache-buster 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+