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.

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