rack-cache 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack-cache might be problematic. Click here for more details.
- data/CHANGES +27 -0
- data/COPYING +18 -0
- data/README +96 -0
- data/Rakefile +144 -0
- data/TODO +40 -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.rb +51 -0
- data/lib/rack/cache/config.rb +65 -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/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 +237 -0
- data/lib/rack/cache/metastore.rb +309 -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/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 +465 -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 +215 -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 +120 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/cache/storage'
|
3
|
+
|
4
|
+
module Rack::Cache
|
5
|
+
# Configuration options and utility methods for option access. Rack::Cache
|
6
|
+
# uses the Rack Environment to store option values. All options documented
|
7
|
+
# below are stored in the Rack Environment as "rack-cache.<option>", where
|
8
|
+
# <option> is the option name.
|
9
|
+
#
|
10
|
+
# The #set method can be used within an event or a top-level configuration
|
11
|
+
# block to configure a option values. When #set is called at the top-level,
|
12
|
+
# the value applies to all requests; when called from within an event, the
|
13
|
+
# values applies only to the request being processed.
|
14
|
+
|
15
|
+
module Options
|
16
|
+
class << self
|
17
|
+
private
|
18
|
+
def option_accessor(key)
|
19
|
+
define_method(key) { || read_option(key) }
|
20
|
+
define_method("#{key}=") { |value| write_option(key, value) }
|
21
|
+
define_method("#{key}?") { || !! read_option(key) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Enable verbose trace logging. This option is currently enabled by
|
26
|
+
# default but is likely to be disabled in a future release.
|
27
|
+
option_accessor :verbose
|
28
|
+
|
29
|
+
# The storage resolver. Defaults to the Rack::Cache.storage singleton instance
|
30
|
+
# of Rack::Cache::Storage. This object is responsible for resolving metastore
|
31
|
+
# and entitystore URIs to an implementation instances.
|
32
|
+
option_accessor :storage
|
33
|
+
|
34
|
+
# A URI specifying the meta-store implementation that should be used to store
|
35
|
+
# request/response meta information. The following URIs schemes are
|
36
|
+
# supported:
|
37
|
+
#
|
38
|
+
# * heap:/
|
39
|
+
# * file:/absolute/path or file:relative/path
|
40
|
+
# * memcached://localhost:11211[/namespace]
|
41
|
+
#
|
42
|
+
# If no meta store is specified the 'heap:/' store is assumed. This
|
43
|
+
# implementation has significant draw-backs so explicit configuration is
|
44
|
+
# recommended.
|
45
|
+
option_accessor :metastore
|
46
|
+
|
47
|
+
# A URI specifying the entity-store implement that should be used to store
|
48
|
+
# response bodies. See the metastore option for information on supported URI
|
49
|
+
# schemes.
|
50
|
+
#
|
51
|
+
# If no entity store is specified the 'heap:/' store is assumed. This
|
52
|
+
# implementation has significant draw-backs so explicit configuration is
|
53
|
+
# recommended.
|
54
|
+
option_accessor :entitystore
|
55
|
+
|
56
|
+
# The number of seconds that a cache entry should be considered
|
57
|
+
# "fresh" when no explicit freshness information is provided in
|
58
|
+
# a response. Explicit Cache-Control or Expires headers
|
59
|
+
# override this value.
|
60
|
+
#
|
61
|
+
# Default: 0
|
62
|
+
option_accessor :default_ttl
|
63
|
+
|
64
|
+
# The underlying options Hash. During initialization (or outside of a
|
65
|
+
# request), this is a default values Hash. During a request, this is the
|
66
|
+
# Rack environment Hash. The default values Hash is merged in underneath
|
67
|
+
# the Rack environment before each request is processed.
|
68
|
+
def options
|
69
|
+
@env || @default_options
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set multiple options.
|
73
|
+
def options=(hash={})
|
74
|
+
hash.each { |key,value| write_option(key, value) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Set an option. When +option+ is a Symbol, it is set in the Rack
|
78
|
+
# Environment as "rack-cache.option". When +option+ is a String, it
|
79
|
+
# exactly as specified. The +option+ argument may also be a Hash in
|
80
|
+
# which case each key/value pair is merged into the environment as if
|
81
|
+
# the #set method were called on each.
|
82
|
+
def set(option, value=self)
|
83
|
+
if value == self
|
84
|
+
self.options = option.to_hash
|
85
|
+
else
|
86
|
+
write_option option, value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def read_option(key)
|
92
|
+
options[option_name(key)]
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_option(key, value)
|
96
|
+
options[option_name(key)] = value
|
97
|
+
end
|
98
|
+
|
99
|
+
def option_name(key)
|
100
|
+
case key
|
101
|
+
when Symbol ; "rack-cache.#{key}"
|
102
|
+
when String ; key
|
103
|
+
else raise ArgumentError
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def initialize_options(options={})
|
109
|
+
@default_options = {
|
110
|
+
'rack-cache.verbose' => true,
|
111
|
+
'rack-cache.storage' => Rack::Cache::Storage.instance,
|
112
|
+
'rack-cache.metastore' => 'heap:/',
|
113
|
+
'rack-cache.entitystore' => 'heap:/',
|
114
|
+
'rack-cache.default_ttl' => 0
|
115
|
+
}
|
116
|
+
self.options = options
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -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
|
@@ -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[COPYING]
|
66
|
+
s.add_dependency 'rack', '~> 0.4'
|
67
|
+
|
68
|
+
s.has_rdoc = true
|
69
|
+
s.homepage = "http://github.com/rtomayko/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
|