rtomayko-rack-cache 0.3.0 → 0.3.9

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 CHANGED
@@ -1,3 +1,44 @@
1
+ ## 0.4.0 / Unreleased
2
+
3
+ * Ruby 1.9.1 / Rack 1.0 compatible.
4
+
5
+ * Invalidate cache entries that match the request URL on non-GET/HEAD
6
+ requests. i.e., POST, PUT, DELETE cause matching cache entries to
7
+ be invalidated. The cache entry is validated with the backend using
8
+ a conditional GET the next time it's requested.
9
+
10
+ * Implement "Cache-Control: max-age=N" request directive by forcing
11
+ validation when the max-age provided exceeds the age of the cache
12
+ entry.
13
+
14
+ * Properly implement "Cache-Control: no-cache" request directive by
15
+ performing a full reload. RFC 2616 states that when "no-cache" is
16
+ present in the request, the cache MUST NOT serve a stored response even
17
+ after successful validation. This is slightly different from the
18
+ "no-cache" directive in responses, which indicates that the cache must
19
+ first validate its entry with the origin. Previously, we implemented
20
+ "no-cache" on requests by passing so no new cache entry would be stored
21
+ based on the response. Now we treat it as a forced miss and enter the
22
+ response into the cache if it's cacheable.
23
+
24
+ * Assume identical semantics for the "Pragma: no-cache" request header
25
+ as the "Cache-Control: no-cache" directive described above.
26
+
27
+ * Less crazy logging. When the verbose option is set, a single log entry
28
+ is written with a comma separated list of trace events. For example, if
29
+ the cache was stale but validated, the following log entry would be
30
+ written: "cache: stale, valid, store". When the verbose option is false,
31
+ no logging occurs.
32
+
33
+ * Added "X-Rack-Cache" response header with the same comma separated trace
34
+ value as described above. This gives some visibility into how the cache
35
+ processed the request.
36
+
37
+ * Add support for canonicalized cache keys, as well as custom cache key
38
+ generators, which are specified in the options as :cache_key as either
39
+ any object that has a call() or as a block. Cache key generators get
40
+ passed a request object and return a cache key string.
41
+
1
42
  ## 0.3.0 / December 2008
2
43
 
3
44
  * Add support for public and private cache control directives. Responses
data/README CHANGED
@@ -12,7 +12,6 @@ validation (Last-Modified, ETag) information:
12
12
  * Cache-Control: public, private, max-age, s-maxage, must-revalidate,
13
13
  and proxy-revalidate.
14
14
  * Portable: 100% Ruby / works with any Rack-enabled framework
15
- * Configuration language for advanced caching policies
16
15
  * Disk, memcached, and heap memory storage backends
17
16
 
18
17
  For more information about Rack::Cache features and usage, see:
data/TODO CHANGED
@@ -1,21 +1,25 @@
1
1
  ## 0.4
2
2
 
3
- - liberal, conservative, sane caching configs
3
+ - Move breakers.rb configuration file into rack-contrib as a middleware
4
+ component.
5
+ - Add docs on using Rack::Cache with Rails 2.3 or link to one of the
6
+ existing tutorials on this.
4
7
  - Sample apps: Rack, Rails, Sinatra, Merb, etc.
5
- - busters.rb and no-cache.rb doc and tests
6
- - Canonicalized URL for cache key:
7
- - sorts params by key, then value
8
- - urlencodes /[^ A-Za-z0-9_.-]/ host, path, and param key/value
9
- - Custom cache keys
10
- - Cache invalidation on PUT, POST, DELETE.
11
- - Invalidate at the request URI; or, anything that's "near" the request URI.
12
- - Invalidate at the URI of the Location or Content-Location response header.
13
8
 
14
9
  ## Backlog
15
10
 
11
+ - Use Bacon instead of test/spec
12
+ - Work with both memcache and memcached gems (memcached hasn't built on MacOS
13
+ for some time now).
14
+ - Fast path pass processing. We do a lot more than necessary just to determine
15
+ that the response should be passed through untouched.
16
+ - Don't purge/remove cache entries when invalidating. The entries should be
17
+ marked as stale and be forced revalidated on the next request instead of
18
+ being removed entirely.
16
19
  - Add missing Expires header if we have a max-age.
17
- - Purge/invalidate specific cache entries
18
20
  - Purge/invalidate everything
21
+ - Invalidate at the URI of the Location or Content-Location response header
22
+ on POST, PUT, or DELETE that results in a redirect.
19
23
  - Maximum size of cached entity
20
24
  - Last-Modified factor: requests that have a Last-Modified header but no Expires
21
25
  header have a TTL assigned based on the last modified age of the response:
@@ -1,51 +1,17 @@
1
- Configuration Language
2
- ======================
1
+ Configuration
2
+ =============
3
3
 
4
4
  __Rack::Cache__ includes a configuration system that can be used to specify
5
5
  fairly sophisticated cache policy on a global or per-request basis.
6
6
 
7
- - [Synopsis](#synopsis)
8
- - [Setting Cache Options](#setopt)
9
- - [Cache Option Reference](#options)
10
- - [Configuration Machinery - Events and Transitions](#machinery)
11
- - [Importing Configuration](#import)
12
- - [Default Configuration Machinery](#default)
13
- - [Notes](#notes)
14
-
15
- <a id='synopsis'></a>
16
-
17
- Synopsis
18
- --------
19
-
20
- use Rack::Cache do
21
- # set cache related options
22
- set :verbose, true
23
- set :metastore, 'memcached://localhost:11211'
24
- set :entitystore, 'file:/var/cache/rack/body'
25
-
26
- # override events / transitions
27
- on :receive do
28
- pass! if request.url =~ %r|/dontcache/|
29
- error! 402 if request.referrer =~ /digg.com/
30
- end
31
-
32
- on :miss do
33
- trace 'missed: %s', request.url
34
- end
35
-
36
- # bring in other configuration machinery
37
- import 'rack/cache/config/breakers'
38
- import 'mycacheconfig'
39
- end
40
-
41
7
  <a id='setopt'></a>
42
8
 
43
9
  Setting Cache Options
44
10
  ---------------------
45
11
 
46
- Cache options can be set when the __Rack::Cache__ object is created; or by using
47
- the `set` method within a configuration block; or by setting a
48
- `rack-cache.<option>` variable in __Rack__'s __Environment__.
12
+ Cache options can be set when the __Rack::Cache__ object is created,
13
+ or by setting a `rack-cache.<option>` variable in __Rack__'s
14
+ __Environment__.
49
15
 
50
16
  When the __Rack::Cache__ object is instantiated:
51
17
 
@@ -54,14 +20,6 @@ When the __Rack::Cache__ object is instantiated:
54
20
  :metastore => 'memcached://localhost:11211/',
55
21
  :entitystore => 'file:/var/cache/rack'
56
22
 
57
- Using the `set` method within __Rack::Cache__'s configuration context:
58
-
59
- use Rack::Cache do
60
- set :verbose, true
61
- set :metastore, 'memcached://localhost:11211/'
62
- set :entitystore, 'file:/var/cache/rack'
63
- end
64
-
65
23
  Using __Rack__'s __Environment__:
66
24
 
67
25
  env.merge!(
@@ -123,110 +81,6 @@ If any of these headers are present in the request, the response is considered
123
81
  private and will not be cached _unless_ the response is explicitly marked public
124
82
  (e.g., `Cache-Control: public`).
125
83
 
126
- <a id='machinery'></a>
127
-
128
- Configuration Machinery - Events and Transitions
129
- ------------------------------------------------
130
-
131
- The configuration machinery is built around a series of interceptable events and
132
- transitions controlled by a simple configuration language. The following diagram
133
- shows each state (interceptable event) along with their possible transitions:
134
-
135
- <p class='center'>
136
- <img src='events.png' alt='Events and Transitions Diagram' />
137
- </p>
138
-
139
- Custom logic can be layered onto the `receive`, `hit`, `miss`, `fetch`, `store`,
140
- `deliver`, and `pass` events by passing a block to the `on` method:
141
-
142
- on :fetch do
143
- trace 'fetched %p from backend application', request.url
144
- end
145
-
146
- Here, the `trace` method writes a message to the `rack.errors` stream when a
147
- response is fetched from the backend application. The `request` object is a
148
- [__Rack::Cache::Request__](./api/classes/Rack/Cache/Request) that can be
149
- inspected (and modified) to determine what action should be taken next.
150
-
151
- Event blocks are capable of performing more interesting operations:
152
-
153
- * Transition to a different event or override default caching logic.
154
- * Modify the request, response, cache entry, or Rack environment options.
155
- * Set the `metastore` or `entitystore` options to select a different storage
156
- mechanism / location dynamically.
157
- * Collect statistics or log request/response/cache information.
158
-
159
- When an event is triggered, the blocks associated with the event are executed in
160
- reverse/FILO order (i.e., the block declared last runs first) until a
161
- _transitioning statement_ is encountered. Transitioning statements are suffixed
162
- with a bang character (e.g, `pass!`, `store!`, `error!`) and cause the current
163
- event to halt and the machine to transition to the subsequent event; control is
164
- not returned to the original event. The [default configuration](#default)
165
- includes documentation on available transitions for each event.
166
-
167
- The `next` statement can be used to exit an event block without transitioning
168
- to another event. Subsequent event blocks are executed until a transitioning
169
- statement is encountered:
170
-
171
- on :fetch do
172
- next if response.freshness_information?
173
-
174
- if request.url =~ /\/feed$/
175
- trace 'feed will expire in fifteen minutes'
176
- response.ttl = 15 * 60
177
- end
178
- end
179
-
180
- <a id='import'></a>
181
-
182
- Importing Configuration
183
- -----------------------
184
-
185
- Since caching logic can be layered, it's possible to separate various bits of
186
- cache policy into files for organization and reuse.
187
-
188
- use Rack::Cache do
189
- import 'rack/cache/config/busters'
190
- import 'mycacheconfig'
191
-
192
- # more stuff here
193
- end
194
-
195
- The `busters` and `mycacheconfig` configuration files are normal Ruby source
196
- files (i.e., they have a `.rb` extension) situated on the `$LOAD_PATH` - the
197
- `import` statement works like Ruby's `require` statement but the contents of the
198
- files are evaluated in the context of the configuration machinery, as if
199
- specified directly in the configuration block.
200
-
201
- The `rack/cache/config/busters.rb` file makes a good example. It hooks into the
202
- `fetch` event and adds an impractically long expiration lifetime to any response
203
- that includes a cache busting query string:
204
-
205
- <%= File.read('lib/rack/cache/config/busters.rb').gsub(/^/, ' ') %>
206
-
207
-
208
- <a id='default'></a>
209
-
210
- Default Configuration Machinery
211
- -------------------------------
212
-
213
- The `rack/cache/config/default.rb` file is imported when the __Rack::Cache__
214
- object is instantiated and before any custom configuration code is executed.
215
- It's useful to understand this configuration because it drives the default
216
- transitioning logic.
217
-
218
- <%= File.read('lib/rack/cache/config/default.rb').gsub(/^/, ' ') %>
219
-
220
- <a id='notes'></a>
221
-
222
- Notes
223
- -----
224
-
225
- The configuration language was inspired by [Varnish][var]'s
226
- [VCL configuration language][vcl].
227
-
228
- [var]: http://varnish.projects.linpro.no/
229
- "Varnish HTTP accelerator"
84
+ ### `cache_key`
230
85
 
231
- [vcl]: http://tomayko.com/man/vcl
232
- "VCL(7) -- Varnish Configuration Language Manual Page"
86
+ TODO: Document custom cache keys
data/doc/index.markdown CHANGED
@@ -7,7 +7,6 @@ for [Rack][]-based applications that produce freshness (`Expires`,
7
7
  * Validation
8
8
  * Vary support
9
9
  * Portable: 100% Ruby / works with any [Rack][]-enabled framework.
10
- * [Configuration language][config] for advanced caching policies.
11
10
  * Disk, memcached, and heap memory [storage backends][storage].
12
11
 
13
12
  Status
@@ -52,8 +51,7 @@ caching.
52
51
  More
53
52
  ----
54
53
 
55
- * [Configuration Language Documentation][config] - how to customize cache
56
- policy using the simple event-based configuration system.
54
+ * [Configuration and Options][config] - how to set cache options.
57
55
 
58
56
  * [Cache Storage Documentation][storage] - detailed information on the various
59
57
  storage implementations available in __Rack::Cache__ and how to choose the one
@@ -0,0 +1,25 @@
1
+ require 'sinatra'
2
+ require 'rack/cache'
3
+
4
+ use Rack::Cache do
5
+ set :verbose, true
6
+ set :metastore, 'heap:/'
7
+ set :entitystore, 'heap:/'
8
+
9
+ on :receive do
10
+ pass! if request.url =~ /favicon/
11
+ end
12
+ end
13
+
14
+ before do
15
+ last_modified $updated_at ||= Time.now
16
+ end
17
+
18
+ get '/' do
19
+ erb :index
20
+ end
21
+
22
+ put '/' do
23
+ $updated_at = nil
24
+ redirect '/'
25
+ end
@@ -0,0 +1,44 @@
1
+ <html>
2
+ <head>
3
+ <title>Sample Rack::Cache Sinatra app</title>
4
+ <style type="text/css" media="screen">
5
+ body {
6
+ font-family: Georgia;
7
+ font-size: 24px;
8
+ text-align: center;
9
+ }
10
+
11
+ #headers {
12
+ font-size: 16px;
13
+ }
14
+
15
+ input {
16
+ font-size: 24px;
17
+ cursor: pointer;
18
+ }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <h1>Last updated at: <%= $updated_at.strftime('%l:%m:%S%P') %></h1>
23
+
24
+ <p>
25
+ <form action="/" method="post">
26
+ <input type="hidden" name="_method" value="PUT">
27
+ <input type="submit" value="Expire the cache.">
28
+ </form>
29
+ </p>
30
+
31
+ <div id="headers">
32
+ <h3>Headers:</h3>
33
+
34
+ <% response.headers.each do |key, value| %>
35
+ <p><%= key %>: <%= value %></p>
36
+ <% end %>
37
+
38
+ <h3>Params:</h3>
39
+ <% params.each do |key, value| %>
40
+ <p><%= key %>: <%= value || '(blank)' %></p>
41
+ <% end %>
42
+ </div>
43
+ </body>
44
+ </html>
data/lib/rack/cache.rb CHANGED
@@ -1,10 +1,5 @@
1
- require 'fileutils'
2
- require 'time'
3
1
  require 'rack'
4
2
 
5
- module Rack #:nodoc:
6
- end
7
-
8
3
  # = HTTP Caching For Rack
9
4
  #
10
5
  # Rack::Cache is suitable as a quick, drop-in component to enable HTTP caching
@@ -15,7 +10,6 @@ end
15
10
  # * Freshness/expiration based caching and validation
16
11
  # * Supports HTTP Vary
17
12
  # * Portable: 100% Ruby / works with any Rack-enabled framework
18
- # * VCL-like configuration language for advanced caching policies
19
13
  # * Disk, memcached, and heap memory storage backends
20
14
  #
21
15
  # === Usage
@@ -32,12 +26,12 @@ end
32
26
  # set :entitystore, 'file:/var/cache/rack'
33
27
  # end
34
28
  # run app
35
- #
36
29
  module Rack::Cache
37
- require 'rack/cache/request'
38
- require 'rack/cache/response'
39
- require 'rack/cache/context'
40
- require 'rack/cache/storage'
30
+ autoload :Request, 'rack/cache/request'
31
+ autoload :Response, 'rack/cache/response'
32
+ autoload :Context, 'rack/cache/context'
33
+ autoload :Storage, 'rack/cache/storage'
34
+ autoload :CacheControl, 'rack/cache/cachecontrol'
41
35
 
42
36
  # Create a new Rack::Cache middleware component that fetches resources from
43
37
  # the specified backend application. The +options+ Hash can be used to
@@ -0,0 +1,193 @@
1
+ module Rack
2
+ module Cache
3
+
4
+ # Parses a Cache-Control header and exposes the directives as a Hash.
5
+ # Directives that do not have values are set to +true+.
6
+ class CacheControl < Hash
7
+ def initialize(value=nil)
8
+ parse(value)
9
+ end
10
+
11
+ # Indicates that the response MAY be cached by any cache, even if it
12
+ # would normally be non-cacheable or cacheable only within a non-
13
+ # shared cache.
14
+ #
15
+ # A response may be considered public without this directive if the
16
+ # private directive is not set and the request does not include an
17
+ # Authorization header.
18
+ def public?
19
+ self['public']
20
+ end
21
+
22
+ # Indicates that all or part of the response message is intended for
23
+ # a single user and MUST NOT be cached by a shared cache. This
24
+ # allows an origin server to state that the specified parts of the
25
+ # response are intended for only one user and are not a valid
26
+ # response for requests by other users. A private (non-shared) cache
27
+ # MAY cache the response.
28
+ #
29
+ # Note: This usage of the word private only controls where the
30
+ # response may be cached, and cannot ensure the privacy of the
31
+ # message content.
32
+ def private?
33
+ self['private']
34
+ end
35
+
36
+ # When set in a response, a cache MUST NOT use the response to satisfy a
37
+ # subsequent request without successful revalidation with the origin
38
+ # server. This allows an origin server to prevent caching even by caches
39
+ # that have been configured to return stale responses to client requests.
40
+ #
41
+ # Note that this does not necessary imply that the response may not be
42
+ # stored by the cache, only that the cache cannot serve it without first
43
+ # making a conditional GET request with the origin server.
44
+ #
45
+ # When set in a request, the server MUST NOT use a cached copy for its
46
+ # response. This has quite different semantics compared to the no-cache
47
+ # directive on responses. When the client specifies no-cache, it causes
48
+ # an end-to-end reload, forcing each cache to update their cached copies.
49
+ def no_cache?
50
+ self['no-cache']
51
+ end
52
+
53
+ # Indicates that the response MUST NOT be stored under any circumstances.
54
+ #
55
+ # The purpose of the no-store directive is to prevent the
56
+ # inadvertent release or retention of sensitive information (for
57
+ # example, on backup tapes). The no-store directive applies to the
58
+ # entire message, and MAY be sent either in a response or in a
59
+ # request. If sent in a request, a cache MUST NOT store any part of
60
+ # either this request or any response to it. If sent in a response,
61
+ # a cache MUST NOT store any part of either this response or the
62
+ # request that elicited it. This directive applies to both non-
63
+ # shared and shared caches. "MUST NOT store" in this context means
64
+ # that the cache MUST NOT intentionally store the information in
65
+ # non-volatile storage, and MUST make a best-effort attempt to
66
+ # remove the information from volatile storage as promptly as
67
+ # possible after forwarding it.
68
+ #
69
+ # The purpose of this directive is to meet the stated requirements
70
+ # of certain users and service authors who are concerned about
71
+ # accidental releases of information via unanticipated accesses to
72
+ # cache data structures. While the use of this directive might
73
+ # improve privacy in some cases, we caution that it is NOT in any
74
+ # way a reliable or sufficient mechanism for ensuring privacy. In
75
+ # particular, malicious or compromised caches might not recognize or
76
+ # obey this directive, and communications networks might be
77
+ # vulnerable to eavesdropping.
78
+ def no_store?
79
+ self['no-store']
80
+ end
81
+
82
+ # The expiration time of an entity MAY be specified by the origin
83
+ # server using the Expires header (see section 14.21). Alternatively,
84
+ # it MAY be specified using the max-age directive in a response. When
85
+ # the max-age cache-control directive is present in a cached response,
86
+ # the response is stale if its current age is greater than the age
87
+ # value given (in seconds) at the time of a new request for that
88
+ # resource. The max-age directive on a response implies that the
89
+ # response is cacheable (i.e., "public") unless some other, more
90
+ # restrictive cache directive is also present.
91
+ #
92
+ # If a response includes both an Expires header and a max-age
93
+ # directive, the max-age directive overrides the Expires header, even
94
+ # if the Expires header is more restrictive. This rule allows an origin
95
+ # server to provide, for a given response, a longer expiration time to
96
+ # an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache. This might be
97
+ # useful if certain HTTP/1.0 caches improperly calculate ages or
98
+ # expiration times, perhaps due to desynchronized clocks.
99
+ #
100
+ # Many HTTP/1.0 cache implementations will treat an Expires value that
101
+ # is less than or equal to the response Date value as being equivalent
102
+ # to the Cache-Control response directive "no-cache". If an HTTP/1.1
103
+ # cache receives such a response, and the response does not include a
104
+ # Cache-Control header field, it SHOULD consider the response to be
105
+ # non-cacheable in order to retain compatibility with HTTP/1.0 servers.
106
+ #
107
+ # When the max-age directive is included in the request, it indicates
108
+ # that the client is willing to accept a response whose age is no
109
+ # greater than the specified time in seconds.
110
+ def max_age
111
+ self['max-age'].to_i if key?('max-age')
112
+ end
113
+
114
+ # If a response includes an s-maxage directive, then for a shared
115
+ # cache (but not for a private cache), the maximum age specified by
116
+ # this directive overrides the maximum age specified by either the
117
+ # max-age directive or the Expires header. The s-maxage directive
118
+ # also implies the semantics of the proxy-revalidate directive. i.e.,
119
+ # that the shared cache must not use the entry after it becomes stale
120
+ # to respond to a subsequent request without first revalidating it with
121
+ # the origin server. The s-maxage directive is always ignored by a
122
+ # private cache.
123
+ def shared_max_age
124
+ self['s-maxage'].to_i if key?('s-maxage')
125
+ end
126
+ alias_method :s_maxage, :shared_max_age
127
+
128
+ # Because a cache MAY be configured to ignore a server's specified
129
+ # expiration time, and because a client request MAY include a max-
130
+ # stale directive (which has a similar effect), the protocol also
131
+ # includes a mechanism for the origin server to require revalidation
132
+ # of a cache entry on any subsequent use. When the must-revalidate
133
+ # directive is present in a response received by a cache, that cache
134
+ # MUST NOT use the entry after it becomes stale to respond to a
135
+ # subsequent request without first revalidating it with the origin
136
+ # server. (I.e., the cache MUST do an end-to-end revalidation every
137
+ # time, if, based solely on the origin server's Expires or max-age
138
+ # value, the cached response is stale.)
139
+ #
140
+ # The must-revalidate directive is necessary to support reliable
141
+ # operation for certain protocol features. In all circumstances an
142
+ # HTTP/1.1 cache MUST obey the must-revalidate directive; in
143
+ # particular, if the cache cannot reach the origin server for any
144
+ # reason, it MUST generate a 504 (Gateway Timeout) response.
145
+ #
146
+ # Servers SHOULD send the must-revalidate directive if and only if
147
+ # failure to revalidate a request on the entity could result in
148
+ # incorrect operation, such as a silently unexecuted financial
149
+ # transaction. Recipients MUST NOT take any automated action that
150
+ # violates this directive, and MUST NOT automatically provide an
151
+ # unvalidated copy of the entity if revalidation fails.
152
+ def must_revalidate?
153
+ self['must-revalidate']
154
+ end
155
+
156
+ # The proxy-revalidate directive has the same meaning as the must-
157
+ # revalidate directive, except that it does not apply to non-shared
158
+ # user agent caches. It can be used on a response to an
159
+ # authenticated request to permit the user's cache to store and
160
+ # later return the response without needing to revalidate it (since
161
+ # it has already been authenticated once by that user), while still
162
+ # requiring proxies that service many users to revalidate each time
163
+ # (in order to make sure that each user has been authenticated).
164
+ # Note that such authenticated responses also need the public cache
165
+ # control directive in order to allow them to be cached at all.
166
+ def proxy_revalidate?
167
+ self['proxy-revalidate']
168
+ end
169
+
170
+ def to_s
171
+ bools, vals = [], []
172
+ each do |key,value|
173
+ if value == true
174
+ bools << key
175
+ elsif value
176
+ vals << "#{key}=#{value}"
177
+ end
178
+ end
179
+ (bools.sort + vals.sort).join(', ')
180
+ end
181
+
182
+ private
183
+ def parse(value)
184
+ return if value.nil? || value.empty?
185
+ value.delete(' ').split(',').inject(self) do |hash,part|
186
+ name, value = part.split('=', 2)
187
+ hash[name.downcase] = (value || true) unless name.empty?
188
+ hash
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end