rtomayko-rack-cache 0.2.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.
Files changed (44) hide show
  1. data/CHANGES +50 -0
  2. data/COPYING +18 -0
  3. data/README +96 -0
  4. data/Rakefile +144 -0
  5. data/TODO +42 -0
  6. data/doc/configuration.markdown +224 -0
  7. data/doc/events.dot +27 -0
  8. data/doc/faq.markdown +133 -0
  9. data/doc/index.markdown +113 -0
  10. data/doc/layout.html.erb +33 -0
  11. data/doc/license.markdown +24 -0
  12. data/doc/rack-cache.css +362 -0
  13. data/doc/storage.markdown +162 -0
  14. data/lib/rack/cache/config/busters.rb +16 -0
  15. data/lib/rack/cache/config/default.rb +134 -0
  16. data/lib/rack/cache/config/no-cache.rb +13 -0
  17. data/lib/rack/cache/config.rb +65 -0
  18. data/lib/rack/cache/context.rb +95 -0
  19. data/lib/rack/cache/core.rb +271 -0
  20. data/lib/rack/cache/entitystore.rb +224 -0
  21. data/lib/rack/cache/headers.rb +277 -0
  22. data/lib/rack/cache/metastore.rb +292 -0
  23. data/lib/rack/cache/options.rb +119 -0
  24. data/lib/rack/cache/request.rb +37 -0
  25. data/lib/rack/cache/response.rb +76 -0
  26. data/lib/rack/cache/storage.rb +50 -0
  27. data/lib/rack/cache.rb +51 -0
  28. data/lib/rack/utils/environment_headers.rb +78 -0
  29. data/rack-cache.gemspec +74 -0
  30. data/test/cache_test.rb +35 -0
  31. data/test/config_test.rb +66 -0
  32. data/test/context_test.rb +505 -0
  33. data/test/core_test.rb +84 -0
  34. data/test/entitystore_test.rb +176 -0
  35. data/test/environment_headers_test.rb +71 -0
  36. data/test/headers_test.rb +222 -0
  37. data/test/logging_test.rb +45 -0
  38. data/test/metastore_test.rb +210 -0
  39. data/test/options_test.rb +64 -0
  40. data/test/pony.jpg +0 -0
  41. data/test/response_test.rb +37 -0
  42. data/test/spec_setup.rb +189 -0
  43. data/test/storage_test.rb +94 -0
  44. metadata +122 -0
@@ -0,0 +1,37 @@
1
+ require 'rack/request'
2
+ require 'rack/cache/headers'
3
+ require 'rack/utils/environment_headers'
4
+
5
+ module Rack::Cache
6
+ # Provides access to the HTTP request. The +request+ and +original_request+
7
+ # objects exposed by the Core caching engine are instances of this class.
8
+ #
9
+ # Request objects respond to a variety of convenience methods, including
10
+ # everything defined by Rack::Request as well as the Headers and
11
+ # RequestHeaders modules.
12
+
13
+ class Request < Rack::Request
14
+ include Rack::Cache::Headers
15
+ include Rack::Cache::RequestHeaders
16
+
17
+ # The HTTP request method. This is the standard implementation of this
18
+ # method but is respecified here due to libraries that attempt to modify
19
+ # the behavior to respect POST tunnel method specifiers. We always want
20
+ # the real request method.
21
+ def request_method
22
+ @env['REQUEST_METHOD']
23
+ end
24
+
25
+ # Determine if the request's method matches any of the values
26
+ # provided:
27
+ # if request.request_method?('GET', 'POST')
28
+ # ...
29
+ # end
30
+ def request_method?(*methods)
31
+ method = request_method
32
+ methods.any? { |test| test.to_s.upcase == method }
33
+ end
34
+
35
+ alias_method :method?, :request_method?
36
+ end
37
+ end
@@ -0,0 +1,76 @@
1
+ require 'set'
2
+ require 'rack/cache/headers'
3
+
4
+ module Rack::Cache
5
+ # Provides access to the response generated by the downstream application. The
6
+ # +response+, +original_response+, and +entry+ objects exposed by the Core
7
+ # caching engine are instances of this class.
8
+ #
9
+ # Response objects respond to a variety of convenience methods, including
10
+ # those defined in Rack::Response::Helpers, Rack::Cache::Headers,
11
+ # and Rack::Cache::ResponseHeaders.
12
+ #
13
+ # Note that Rack::Cache::Response is not a subclass of Rack::Response and does
14
+ # not perform many of the same initialization and finalization tasks. For
15
+ # example, the body is not slurped during initialization and there are no
16
+ # facilities for generating response output.
17
+
18
+ class Response
19
+ include Rack::Response::Helpers
20
+ include Rack::Cache::Headers
21
+ include Rack::Cache::ResponseHeaders
22
+
23
+ # The response's status code (integer).
24
+ attr_accessor :status
25
+
26
+ # The response body. See the Rack spec for information on the behavior
27
+ # required by this object.
28
+ attr_accessor :body
29
+
30
+ # The response headers.
31
+ attr_reader :headers
32
+
33
+ # Create a Response instance given the response status code, header hash,
34
+ # and body.
35
+ def initialize(status, headers, body)
36
+ @status = status
37
+ @headers = headers
38
+ @body = body
39
+ @now = Time.now
40
+ @headers['Date'] ||= now.httpdate
41
+ end
42
+
43
+ def initialize_copy(other)
44
+ super
45
+ @headers = other.headers.dup
46
+ end
47
+
48
+ # Return the value of the named response header.
49
+ def [](header_name)
50
+ headers[header_name]
51
+ end
52
+
53
+ # Set a response header value.
54
+ def []=(header_name, header_value)
55
+ headers[header_name] = header_value
56
+ end
57
+
58
+ # Called immediately after an object is loaded from the cache.
59
+ def activate!
60
+ headers['Age'] = age.to_i.to_s
61
+ end
62
+
63
+ # Return the status, headers, and body in a three-tuple.
64
+ def to_a
65
+ [status, headers, body]
66
+ end
67
+
68
+ # Freezes
69
+ def freeze
70
+ @headers.freeze
71
+ super
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,50 @@
1
+ require 'uri'
2
+ require 'rack/cache/metastore'
3
+ require 'rack/cache/entitystore'
4
+
5
+ module Rack::Cache
6
+ # Maintains a collection of MetaStore and EntityStore instances keyed by
7
+ # URI. A single instance of this class can be used across a single process
8
+ # to ensure that only a single instance of a backing store is created per
9
+ # unique storage URI.
10
+
11
+ class Storage
12
+ def initialize
13
+ @metastores = {}
14
+ @entitystores = {}
15
+ end
16
+
17
+ def resolve_metastore_uri(uri)
18
+ @metastores[uri.to_s] ||= create_store(MetaStore, uri)
19
+ end
20
+
21
+ def resolve_entitystore_uri(uri)
22
+ @entitystores[uri.to_s] ||= create_store(EntityStore, uri)
23
+ end
24
+
25
+ # Clear store instances.
26
+ def clear
27
+ @metastores.clear
28
+ @entitystores.clear
29
+ nil
30
+ end
31
+
32
+ private
33
+ def create_store(type, uri)
34
+ uri = URI.parse(uri) unless uri.respond_to?(:scheme)
35
+ if type.const_defined?(uri.scheme.upcase)
36
+ klass = type.const_get(uri.scheme.upcase)
37
+ klass.resolve(uri)
38
+ else
39
+ fail "Unknown storage provider: #{uri.to_s}"
40
+ end
41
+ end
42
+
43
+ public
44
+ @@singleton_instance = new
45
+ def self.instance
46
+ @@singleton_instance
47
+ end
48
+ end
49
+
50
+ end
data/lib/rack/cache.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'fileutils'
2
+ require 'time'
3
+ require 'rack'
4
+
5
+ module Rack #:nodoc:
6
+ end
7
+
8
+ # = HTTP Caching For Rack
9
+ #
10
+ # Rack::Cache is suitable as a quick, drop-in component to enable HTTP caching
11
+ # for Rack-enabled applications that produce freshness (+Expires+, +Cache-Control+)
12
+ # and/or validation (+Last-Modified+, +ETag+) information.
13
+ #
14
+ # * Standards-based (RFC 2616 compliance)
15
+ # * Freshness/expiration based caching and validation
16
+ # * Supports HTTP Vary
17
+ # * Portable: 100% Ruby / works with any Rack-enabled framework
18
+ # * VCL-like configuration language for advanced caching policies
19
+ # * Disk, memcached, and heap memory storage backends
20
+ #
21
+ # === Usage
22
+ #
23
+ # Create with default options:
24
+ # require 'rack/cache'
25
+ # Rack::Cache.new(app, :verbose => true, :entitystore => 'file:cache')
26
+ #
27
+ # Within a rackup file (or with Rack::Builder):
28
+ # require 'rack/cache'
29
+ # use Rack::Cache do
30
+ # set :verbose, true
31
+ # set :metastore, 'memcached://localhost:11211/meta'
32
+ # set :entitystore, 'file:/var/cache/rack'
33
+ # end
34
+ # run app
35
+ #
36
+ module Rack::Cache
37
+ require 'rack/cache/request'
38
+ require 'rack/cache/response'
39
+ require 'rack/cache/context'
40
+ require 'rack/cache/storage'
41
+
42
+ # Create a new Rack::Cache middleware component that fetches resources from
43
+ # the specified backend application. The +options+ Hash can be used to
44
+ # specify default configuration values (see attributes defined in
45
+ # Rack::Cache::Options for possible key/values). When a block is given, it
46
+ # is executed within the context of the newly create Rack::Cache::Context
47
+ # object.
48
+ def self.new(backend, options={}, &b)
49
+ Context.new(backend, options, &b)
50
+ end
51
+ end
@@ -0,0 +1,78 @@
1
+ require 'rack/utils'
2
+
3
+ module Rack::Utils #:nodoc:
4
+ # A facade over a Rack Environment Hash that gives access to headers
5
+ # using their normal RFC 2616 names.
6
+
7
+ class EnvironmentHeaders
8
+ include Enumerable
9
+
10
+ # Create the facade over the given Rack Environment Hash.
11
+ def initialize(env)
12
+ @env = env
13
+ end
14
+
15
+ # Return the value of the specified header. The +header_name+ should
16
+ # be as specified by RFC 2616 (e.g., "Content-Type", "Accept", etc.)
17
+ def [](header_name)
18
+ @env[env_name(header_name)]
19
+ end
20
+
21
+ # Set the value of the specified header. The +header_name+ should
22
+ # be as specified by RFC 2616 (e.g., "Content-Type", "Accept", etc.)
23
+ def []=(header_name, value)
24
+ @env[env_name(header_name)] = value
25
+ end
26
+
27
+ # Determine if the underlying Rack Environment includes a header
28
+ # of the given name.
29
+ def include?(header_name)
30
+ @env.include?(env_name(header_name))
31
+ end
32
+
33
+ # Iterate over all headers yielding a (name, value) tuple to the
34
+ # block. Rack Environment keys that do not map to an header are not
35
+ # included.
36
+ def each
37
+ @env.each do |key,value|
38
+ next unless key =~ /^(HTTP_|CONTENT_)/
39
+ yield header_name(key), value
40
+ end
41
+ end
42
+
43
+ # Delete the entry in the underlying Rack Environment that corresponds
44
+ # to the given RFC 2616 header name.
45
+ def delete(header_name)
46
+ @env.delete(env_name(header_name))
47
+ end
48
+
49
+ # Return the underlying Rack Environment Hash.
50
+ def to_env
51
+ @env
52
+ end
53
+
54
+ alias_method :to_hash, :to_env
55
+
56
+ private
57
+
58
+ # Return the Rack Environment key for the given RFC 2616 header name.
59
+ def env_name(header_name)
60
+ case header_name = header_name.upcase
61
+ when 'CONTENT-TYPE' then 'CONTENT_TYPE'
62
+ when 'CONTENT-LENGTH' then 'CONTENT_LENGTH'
63
+ else "HTTP_#{header_name.tr('-', '_')}"
64
+ end
65
+ end
66
+
67
+ # Return the RFC 2616 header name for the given Rack Environment key.
68
+ def header_name(env_name)
69
+ env_name.
70
+ sub(/^HTTP_/, '').
71
+ downcase.
72
+ capitalize.
73
+ gsub(/_(.)/) { '-' + $1.upcase }
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,74 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+
5
+ s.name = 'rack-cache'
6
+ s.version = '0.2.0'
7
+ s.date = '2008-10-24'
8
+
9
+ s.description = "HTTP Caching for Rack"
10
+ s.summary = "HTTP Caching for Rack"
11
+
12
+ s.authors = ["Ryan Tomayko"]
13
+ s.email = "r@tomayko.com"
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ CHANGES
18
+ COPYING
19
+ README
20
+ Rakefile
21
+ TODO
22
+ doc/configuration.markdown
23
+ doc/events.dot
24
+ doc/faq.markdown
25
+ doc/index.markdown
26
+ doc/layout.html.erb
27
+ doc/license.markdown
28
+ doc/rack-cache.css
29
+ doc/storage.markdown
30
+ lib/rack/cache.rb
31
+ lib/rack/cache/config.rb
32
+ lib/rack/cache/config/busters.rb
33
+ lib/rack/cache/config/default.rb
34
+ lib/rack/cache/config/no-cache.rb
35
+ lib/rack/cache/context.rb
36
+ lib/rack/cache/core.rb
37
+ lib/rack/cache/entitystore.rb
38
+ lib/rack/cache/headers.rb
39
+ lib/rack/cache/metastore.rb
40
+ lib/rack/cache/options.rb
41
+ lib/rack/cache/request.rb
42
+ lib/rack/cache/response.rb
43
+ lib/rack/cache/storage.rb
44
+ lib/rack/utils/environment_headers.rb
45
+ rack-cache.gemspec
46
+ test/cache_test.rb
47
+ test/config_test.rb
48
+ test/context_test.rb
49
+ test/core_test.rb
50
+ test/entitystore_test.rb
51
+ test/environment_headers_test.rb
52
+ test/headers_test.rb
53
+ test/logging_test.rb
54
+ test/metastore_test.rb
55
+ test/options_test.rb
56
+ test/pony.jpg
57
+ test/response_test.rb
58
+ test/spec_setup.rb
59
+ test/storage_test.rb
60
+ ]
61
+ # = MANIFEST =
62
+
63
+ s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
64
+
65
+ s.extra_rdoc_files = %w[README COPYING TODO CHANGES]
66
+ s.add_dependency 'rack', '~> 0.4'
67
+
68
+ s.has_rdoc = true
69
+ s.homepage = "http://tomayko.com/src/rack-cache/"
70
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Rack::Cache", "--main", "Rack::Cache"]
71
+ s.require_paths = %w[lib]
72
+ s.rubyforge_project = 'wink'
73
+ s.rubygems_version = '1.1.1'
74
+ end
@@ -0,0 +1,35 @@
1
+ require "#{File.dirname(__FILE__)}/spec_setup"
2
+
3
+ def dumb_app(env)
4
+ body = block_given? ? [yield] : ['Hi']
5
+ [ 200, {'Content-Type' => 'text/plain'}, body ]
6
+ end
7
+
8
+ describe 'Rack::Cache::new' do
9
+ before { @app = method(:dumb_app) }
10
+
11
+ it 'takes a backend and returns a middleware component' do
12
+ Rack::Cache.new(@app).
13
+ should.respond_to :call
14
+ end
15
+ it 'takes an options Hash' do
16
+ lambda { Rack::Cache.new(@app, {}) }.
17
+ should.not.raise(ArgumentError)
18
+ end
19
+ it 'sets options provided in the options Hash' do
20
+ object = Rack::Cache.new(@app, :foo => 'bar', 'foo.bar' => 'bling')
21
+ object.options['foo.bar'].should.be == 'bling'
22
+ object.options['rack-cache.foo'].should.be == 'bar'
23
+ end
24
+ it 'takes a block; executes it during initialization' do
25
+ state, block_scope = 'not invoked', nil
26
+ object =
27
+ Rack::Cache.new @app do
28
+ block_scope = self
29
+ state = 'invoked'
30
+ should.respond_to :on
31
+ end
32
+ state.should.be == 'invoked'
33
+ object.should.be block_scope
34
+ end
35
+ end
@@ -0,0 +1,66 @@
1
+ require "#{File.dirname(__FILE__)}/spec_setup"
2
+ require 'rack/cache/config'
3
+
4
+ class MockConfig
5
+ include Rack::Cache::Config
6
+ def configured!
7
+ @configured = true
8
+ end
9
+ def configured?
10
+ @configured
11
+ end
12
+ end
13
+
14
+ describe 'Rack::Cache::Config' do
15
+ before :each do
16
+ @config = MockConfig.new
17
+ @tempdir = create_temp_directory
18
+ $:.unshift @tempdir
19
+ end
20
+ after :each do
21
+ @config = nil
22
+ $:.shift if $:.first == @tempdir
23
+ remove_entry_secure @tempdir
24
+ end
25
+
26
+ def make_temp_file(filename, data='configured!')
27
+ create_temp_file @tempdir, filename, data
28
+ end
29
+
30
+ it 'loads config files from the load path when file is relative' do
31
+ make_temp_file 'foo/bar.rb'
32
+ @config.import 'foo/bar.rb'
33
+ @config.should.be.configured
34
+ end
35
+ it 'assumes a .rb file extension when no file extension exists' do
36
+ make_temp_file 'foo/bar.rb'
37
+ @config.import 'foo/bar'
38
+ @config.should.be.configured
39
+ end
40
+ it 'does not assume a .rb file extension when other file extension exists' do
41
+ make_temp_file 'foo/bar.conf'
42
+ @config.import 'foo/bar.conf'
43
+ @config.should.be.configured
44
+ end
45
+ it 'should locate files with absolute path names' do
46
+ make_temp_file 'foo/bar.rb'
47
+ @config.import File.join(@tempdir, 'foo/bar.rb')
48
+ @config.should.be.configured
49
+ end
50
+ it 'raises a LoadError when the file cannot be found' do
51
+ assert_raises(LoadError) {
52
+ @config.import('this/file/is/very-likely/not/to/exist.rb')
53
+ }
54
+ end
55
+ it 'executes within the context of the object instance' do
56
+ make_temp_file 'foo/bar.rb',
57
+ 'self.should.be.kind_of Rack::Cache::Config ; configured!'
58
+ @config.import 'foo/bar'
59
+ @config.should.be.configured
60
+ end
61
+ it 'does not import files more than once' do
62
+ make_temp_file 'foo/bar.rb', "import 'foo/bar'"
63
+ @config.import('foo/bar').should.be true
64
+ @config.import('foo/bar').should.be false
65
+ end
66
+ end