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.
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
|