rtomayko-rack-cache 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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