Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit dd1c6b0e6951429dc0e37a6e9404a6be204a51f3 Sven Fuchs committed Sep 9, 2009
@@ -0,0 +1,36 @@
+require 'uri'
+
+require 'rack/cache'
+require 'rack/cache/storage'
+require 'rack/cache/utils'
+
+require 'rack/cache/tags/context'
+require 'rack/cache/tags/storage'
+require 'rack/cache/tags/tag_store'
+require 'rack/cache/tags/meta_store'
+
+module Rack::Cache
+ TAGS_HEADER = 'X-Cache-Tags'
+ PURGE_TAGS_HEADER = 'X-Cache-Purge-Tags'
+
+ Context.class_eval do
+ option_accessor :tagstore
+
+ def tagstore
+ uri = options['rack-cache.tagstore']
+ storage.resolve_tagstore_uri(uri)
+ end
+ end
+
+ module Tags
+ class << self
+ def new(backend, options={}, &b)
+ Context.new(backend, options, &b)
+ end
+
+ def normalize(tags)
+ Array(tags).join(',').split(',').map { |tag| tag.strip }
+ end
+ end
+ end
+end
@@ -0,0 +1,48 @@
+module Rack::Cache::Tags
+ class Context
+ def initialize(backend, options = {})
+ @backend = backend
+ @options = options
+ yield self if block_given?
+ end
+
+ def call(env)
+ @env = env
+ request = Rack::Cache::Request.new(env)
+ response = Rack::Cache::Response.new(*@backend.call(env))
+
+ tags = response.headers.delete(Rack::Cache::PURGE_TAGS_HEADER)
+ if tags
+ tags = Rack::Cache::Tags.normalize(tags)
+ uris = tagged_uris(tags)
+ response.headers[Rack::Cache::PURGE_HEADER] = uris.join("\n")
+
+ purge_taggings(request, uris)
+ end
+
+ response.to_a
+ end
+
+ protected
+
+ def tagged_uris(tags)
+ tags.inject([]) { |uris, tag| uris + tagstore.by_tag[tag] }
+ end
+
+ def purge_taggings(request, uris)
+ uris.each do |uri|
+ key = Rack::Cache::Utils::Key.call(request, uri)
+ tagstore.purge(key)
+ end
+ end
+
+ def tagstore
+ uri = @env['rack-cache.tagstore']
+ storage.resolve_tagstore_uri(uri)
+ end
+
+ def storage
+ Rack::Cache::Storage.instance
+ end
+ end
+end
@@ -0,0 +1,19 @@
+Rack::Cache::MetaStore::Heap.class_eval do
+ def store(request, response, entity_store)
+ key = super
+
+ tags = response.headers[Rack::Cache::TAGS_HEADER]
+ tagstore(request).store(key, tags) if tags
+
+ key
+ end
+
+ def tagstore(request)
+ uri = request.env['rack-cache.tagstore']
+ storage.resolve_tagstore_uri(uri)
+ end
+
+ def storage
+ Rack::Cache::Storage.instance
+ end
+end
@@ -0,0 +1,6 @@
+Rack::Cache::Storage.class_eval do
+ def resolve_tagstore_uri(uri)
+ @tagstores ||= {}
+ @tagstores[uri.to_s] ||= create_store(Rack::Cache::Tags::TagStore, uri)
+ end
+end
@@ -0,0 +1,75 @@
+require 'fileutils'
+require 'digest/sha1'
+require 'rack/utils'
+
+module Rack::Cache::Tags
+ class TagStore
+ def store(key, tags)
+ tags = Rack::Cache::Tags.normalize(tags)
+ write(key, tags)
+ end
+
+ def by_key(key)
+ raise NotImplemented
+ end
+
+ def by_tag(tag)
+ raise NotImplemented
+ end
+
+ protected
+
+ def write(key, tags)
+ raise NotImplemented
+ end
+
+ def purge(key)
+ raise NotImplemented
+ end
+
+ public
+
+ class Heap < TagStore
+ def self.resolve(uri)
+ new
+ end
+
+ def initialize
+ @hash = { :by_key => {}, :by_tag => {} }
+ end
+
+ def by_key
+ @hash[:by_key]
+ end
+
+ def by_tag
+ @hash[:by_tag]
+ end
+
+ def purge(key)
+ if tags = by_key[key]
+ tags.each do |tag|
+ next unless by_tag[tag]
+ by_tag[tag].delete(key)
+ by_tag.delete(tag) if by_tag[tag].empty?
+ end
+ end
+
+ by_key.delete(key)
+ nil
+ end
+
+ def write(key, tags)
+ by_key[key] = tags
+
+ tags.each do |tag|
+ by_tag[tag] ||= []
+ by_tag[tag] << key unless by_tag[tag].include?(key)
+ end
+ end
+ end
+
+ HEAP = Heap
+ MEM = HEAP
+ end
+end
@@ -0,0 +1,3 @@
+Dir[File.dirname(__FILE__) + '/**/*_test.rb'].each do |filename|
+ require filename
+end
@@ -0,0 +1,43 @@
+require "#{File.dirname(__FILE__)}/test_setup"
+
+describe 'Rack::Cache::Tags::Tagstore' do
+ before(:each) do
+ @store = Rack::Cache::Tags::TagStore::Heap.new
+ end
+
+ it "can be resolved from an uri" do
+ tag_store = Rack::Cache::Storage.new.resolve_tagstore_uri('heap:/')
+ tag_store.should.be.kind_of Rack::Cache::Tags::TagStore::Heap
+ end
+
+ it "writes to both by_key and by_tag" do
+ @store.store('1234', 'tag-1,tag-2')
+
+ @store.by_key['1234'].should == ['tag-1', 'tag-2']
+ @store.by_tag['tag-1'].should == ['1234']
+ @store.by_tag['tag-2'].should == ['1234']
+ @store.by_tag['tag-3'].should == nil
+
+ @store.store('5678', 'tag-1,tag-3')
+
+ @store.by_key['1234'].should == ['tag-1', 'tag-2']
+ @store.by_key['5678'].should == ['tag-1', 'tag-3']
+ @store.by_tag['tag-1'].should == ['1234', '5678']
+ @store.by_tag['tag-2'].should == ['1234']
+ @store.by_tag['tag-3'].should == ['5678']
+ end
+
+ it "purges from both by_key and by_tag" do
+ @store.store('1234', 'tag-1,tag-2')
+ @store.store('5678', 'tag-1,tag-3')
+
+ @store.purge('1234')
+
+ @store.by_key['1234'].should == nil
+ @store.by_key['5678'].should == ['tag-1', 'tag-3']
+ @store.by_tag['tag-1'].should == ['5678']
+ @store.by_tag['tag-2'].should == nil
+ @store.by_tag['tag-3'].should == ['5678']
+ end
+
+end
@@ -0,0 +1,43 @@
+require "#{File.dirname(__FILE__)}/test_setup"
+
+describe 'Rack::Cache::Tags' do
+ before(:each) { setup_cache_context }
+ after(:each) { teardown_cache_context }
+
+ it "writes tags for a key on :store" do
+ respond_with 200, { 'X-Cache-Tags' => 'page-1,user-2', 'Cache-Control' => 'public, max-age=10000' }, 'body'
+
+ response = get '/'
+ tagstore.by_key.should == { 'http://example.org/' => ['page-1', 'user-2'] }
+ tagstore.by_tag.should == { 'page-1' => ['http://example.org/'], 'user-2' => ['http://example.org/'] }
+ end
+
+ it "deletes tags for a key on :purge" do
+ respond_with 200, { 'Cache-Control' => 'public, max-age=10000' }, 'body'
+ respond_with 200, { 'Cache-Control' => 'public, max-age=500' }, 'body' do |req, res|
+ case req.path
+ when '/'
+ res.headers['X-Cache-Tags'] = ['page-1,user-2']
+ when '/users/2'
+ res.headers['X-Cache-Purge-Tags'] = 'user-2'
+ end
+ end
+
+ get '/'
+ tagstore.by_key['http://example.org/'].should.include 'user-2'
+ cache.trace.should.include :store
+
+ get '/'
+ cache.trace.should.include :fresh
+
+ post '/users/2'
+ tagstore.by_key['http://example.org/'].should.be.nil
+
+ get '/'
+ cache.trace.should.include :miss
+ end
+
+ def tagstore
+ cache.send(:tagstore)
+ end
+end
Oops, something went wrong.

0 comments on commit dd1c6b0

Please sign in to comment.