rtomayko-rack-cache 0.3.0 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
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