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.
- data/CHANGES +50 -0
- data/COPYING +18 -0
- data/README +96 -0
- data/Rakefile +144 -0
- data/TODO +42 -0
- data/doc/configuration.markdown +224 -0
- data/doc/events.dot +27 -0
- data/doc/faq.markdown +133 -0
- data/doc/index.markdown +113 -0
- data/doc/layout.html.erb +33 -0
- data/doc/license.markdown +24 -0
- data/doc/rack-cache.css +362 -0
- data/doc/storage.markdown +162 -0
- data/lib/rack/cache/config/busters.rb +16 -0
- data/lib/rack/cache/config/default.rb +134 -0
- data/lib/rack/cache/config/no-cache.rb +13 -0
- data/lib/rack/cache/config.rb +65 -0
- data/lib/rack/cache/context.rb +95 -0
- data/lib/rack/cache/core.rb +271 -0
- data/lib/rack/cache/entitystore.rb +224 -0
- data/lib/rack/cache/headers.rb +277 -0
- data/lib/rack/cache/metastore.rb +292 -0
- data/lib/rack/cache/options.rb +119 -0
- data/lib/rack/cache/request.rb +37 -0
- data/lib/rack/cache/response.rb +76 -0
- data/lib/rack/cache/storage.rb +50 -0
- data/lib/rack/cache.rb +51 -0
- data/lib/rack/utils/environment_headers.rb +78 -0
- data/rack-cache.gemspec +74 -0
- data/test/cache_test.rb +35 -0
- data/test/config_test.rb +66 -0
- data/test/context_test.rb +505 -0
- data/test/core_test.rb +84 -0
- data/test/entitystore_test.rb +176 -0
- data/test/environment_headers_test.rb +71 -0
- data/test/headers_test.rb +222 -0
- data/test/logging_test.rb +45 -0
- data/test/metastore_test.rb +210 -0
- data/test/options_test.rb +64 -0
- data/test/pony.jpg +0 -0
- data/test/response_test.rb +37 -0
- data/test/spec_setup.rb +189 -0
- data/test/storage_test.rb +94 -0
- 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
|
data/rack-cache.gemspec
ADDED
@@ -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
|
data/test/cache_test.rb
ADDED
@@ -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
|
data/test/config_test.rb
ADDED
@@ -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
|