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.

Files changed (44) hide show
  1. data/CHANGES +27 -0
  2. data/COPYING +18 -0
  3. data/README +96 -0
  4. data/Rakefile +144 -0
  5. data/TODO +40 -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.rb +51 -0
  15. data/lib/rack/cache/config.rb +65 -0
  16. data/lib/rack/cache/config/busters.rb +16 -0
  17. data/lib/rack/cache/config/default.rb +134 -0
  18. data/lib/rack/cache/config/no-cache.rb +13 -0
  19. data/lib/rack/cache/context.rb +95 -0
  20. data/lib/rack/cache/core.rb +271 -0
  21. data/lib/rack/cache/entitystore.rb +224 -0
  22. data/lib/rack/cache/headers.rb +237 -0
  23. data/lib/rack/cache/metastore.rb +309 -0
  24. data/lib/rack/cache/options.rb +119 -0
  25. data/lib/rack/cache/request.rb +37 -0
  26. data/lib/rack/cache/response.rb +76 -0
  27. data/lib/rack/cache/storage.rb +50 -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 +465 -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 +215 -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 +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
@@ -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
@@ -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