josh-rack-cache 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +167 -0
- data/COPYING +18 -0
- data/README +110 -0
- data/Rakefile +137 -0
- data/TODO +27 -0
- data/doc/configuration.markdown +112 -0
- data/doc/faq.markdown +141 -0
- data/doc/index.markdown +121 -0
- data/doc/layout.html.erb +34 -0
- data/doc/license.markdown +24 -0
- data/doc/rack-cache.css +362 -0
- data/doc/server.ru +34 -0
- data/doc/storage.markdown +164 -0
- data/example/sinatra/app.rb +25 -0
- data/example/sinatra/views/index.erb +44 -0
- data/lib/rack/cache.rb +45 -0
- data/lib/rack/cache/appengine.rb +52 -0
- data/lib/rack/cache/cachecontrol.rb +193 -0
- data/lib/rack/cache/context.rb +253 -0
- data/lib/rack/cache/entitystore.rb +339 -0
- data/lib/rack/cache/key.rb +52 -0
- data/lib/rack/cache/metastore.rb +407 -0
- data/lib/rack/cache/options.rb +150 -0
- data/lib/rack/cache/request.rb +33 -0
- data/lib/rack/cache/response.rb +267 -0
- data/lib/rack/cache/storage.rb +62 -0
- data/rack-cache.gemspec +70 -0
- data/test/cache_test.rb +38 -0
- data/test/cachecontrol_test.rb +139 -0
- data/test/context_test.rb +774 -0
- data/test/entitystore_test.rb +230 -0
- data/test/key_test.rb +50 -0
- data/test/metastore_test.rb +302 -0
- data/test/options_test.rb +77 -0
- data/test/pony.jpg +0 -0
- data/test/request_test.rb +19 -0
- data/test/response_test.rb +178 -0
- data/test/spec_setup.rb +237 -0
- data/test/storage_test.rb +94 -0
- metadata +118 -0
data/doc/server.ru
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Rackup config that serves the contents of Rack::Cache's
|
2
|
+
# doc directory. The documentation is rebuilt on each request.
|
3
|
+
|
4
|
+
# Rewrites URLs like conventional web server configs.
|
5
|
+
class Rewriter < Struct.new(:app)
|
6
|
+
def call(env)
|
7
|
+
if env['PATH_INFO'] =~ /\/$/
|
8
|
+
env['PATH_INFO'] += 'index.html'
|
9
|
+
elsif env['PATH_INFO'] !~ /\.\w+$/
|
10
|
+
env['PATH_INFO'] += '.html'
|
11
|
+
end
|
12
|
+
app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Rebuilds documentation on each request.
|
17
|
+
class DocBuilder < Struct.new(:app)
|
18
|
+
def call(env)
|
19
|
+
if env['PATH_INFO'] !~ /\.(css|js|gif|jpg|png|ico)$/
|
20
|
+
env['rack.errors'] << "*** rebuilding documentation (rake -s doc)\n"
|
21
|
+
system "rake -s doc"
|
22
|
+
end
|
23
|
+
app.call(env)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
use Rack::CommonLogger
|
28
|
+
use DocBuilder
|
29
|
+
use Rewriter
|
30
|
+
use Rack::Static, :root => File.dirname(__FILE__), :urls => ["/"]
|
31
|
+
|
32
|
+
run(lambda{|env| [404,{},'<h1>Not Found</h1>']})
|
33
|
+
|
34
|
+
# vim: ft=ruby
|
@@ -0,0 +1,164 @@
|
|
1
|
+
Storage
|
2
|
+
=======
|
3
|
+
|
4
|
+
__Rack::Cache__ runs within each of your backend application processes and does not
|
5
|
+
rely on a single intermediary process like most types of proxy cache
|
6
|
+
implementations. Because of this, the storage subsystem has implications on not
|
7
|
+
only where cache data is stored but whether the cache is properly distributed
|
8
|
+
between multiple backend processes. It is highly recommended that you read and
|
9
|
+
understand the following before choosing a storage implementation.
|
10
|
+
|
11
|
+
Storage Areas
|
12
|
+
-------------
|
13
|
+
|
14
|
+
__Rack::Cache__ stores cache entries in two separate configurable storage
|
15
|
+
areas: a _MetaStore_ and an _EntityStore_.
|
16
|
+
|
17
|
+
The _MetaStore_ keeps high level information about each cache entry, including
|
18
|
+
the request/response headers and other status information. When a request is
|
19
|
+
received, the core caching logic uses this meta information to determine whether
|
20
|
+
a fresh cache entry exists that can satisfy the request.
|
21
|
+
|
22
|
+
The _EntityStore_ is where the actual response body content is stored. When a
|
23
|
+
response is entered into the cache, a SHA1 digest of the response body content
|
24
|
+
is calculated and used as a key. The entries stored in the MetaStore reference
|
25
|
+
their response bodies using this SHA1 key.
|
26
|
+
|
27
|
+
Separating request/response meta-data from response content has a few important
|
28
|
+
advantages:
|
29
|
+
|
30
|
+
* Different storage types can be used for meta and entity storage. For
|
31
|
+
example, it may be desirable to use memcached to store meta information
|
32
|
+
while using the filesystem for entity storage.
|
33
|
+
|
34
|
+
* Cache entry meta-data may be retrieved quickly without also retrieving
|
35
|
+
response bodies. This avoids significant overhead when the cache misses
|
36
|
+
or only requires validation.
|
37
|
+
|
38
|
+
* Multiple different responses may include the same exact response body. In
|
39
|
+
these cases, the actual body content is stored once and referenced from
|
40
|
+
each of the meta store entries.
|
41
|
+
|
42
|
+
You should consider how the meta and entity stores differ when choosing a storage
|
43
|
+
implementation. The MetaStore does not require nearly as much memory as the
|
44
|
+
EntityStore and is accessed much more frequently. The EntityStore can grow quite
|
45
|
+
large and raw performance is less of a concern. Using a memory based storage
|
46
|
+
implementation (`heap` or `memcached`) for the MetaStore is strongly advised,
|
47
|
+
while a disk based storage implementation (`file`) is often satisfactory for
|
48
|
+
the EntityStore and uses much less memory.
|
49
|
+
|
50
|
+
Storage Configuration
|
51
|
+
---------------------
|
52
|
+
|
53
|
+
The MetaStore and EntityStore used for a particular request is determined by
|
54
|
+
inspecting the `rack-cache.metastore` and `rack-cache.entitystore` Rack env
|
55
|
+
variables. The value of these variables is a URI that identifies the storage
|
56
|
+
type and location (URI formats are documented in the following section).
|
57
|
+
|
58
|
+
The `heap:/` storage is assumed if either storage type is not explicitly
|
59
|
+
provided. This storage type has significant drawbacks for most types of
|
60
|
+
deployments so explicit configuration is advised.
|
61
|
+
|
62
|
+
The default metastore and entitystore values can be specified when the
|
63
|
+
__Rack::Cache__ object is added to the Rack middleware pipeline as follows:
|
64
|
+
|
65
|
+
use Rack::Cache,
|
66
|
+
:metastore => 'file:/var/cache/rack/meta',
|
67
|
+
:entitystore => 'file:/var/cache/rack/body'
|
68
|
+
|
69
|
+
Alternatively, the `rack-cache.metastore` and `rack-cache.entitystore`
|
70
|
+
variables may be set in the Rack environment by an upstream component.
|
71
|
+
|
72
|
+
Storage Implementations
|
73
|
+
-----------------------
|
74
|
+
|
75
|
+
__Rack::Cache__ includes meta and entity storage implementations backed by local
|
76
|
+
process memory ("heap storage"), the file system ("disk storage"), and
|
77
|
+
memcached. This section includes information on configuring __Rack::Cache__ to
|
78
|
+
use a specific storage implementation as well as pros and cons of each.
|
79
|
+
|
80
|
+
### Heap Storage
|
81
|
+
|
82
|
+
Uses local process memory to store cached entries.
|
83
|
+
|
84
|
+
use Rack::Cache,
|
85
|
+
:metastore => 'heap:/',
|
86
|
+
:entitystore => 'heap:/'
|
87
|
+
|
88
|
+
The heap storage backend is simple, fast, and mostly useless. All cache
|
89
|
+
information is stored in each backend application's local process memory (using
|
90
|
+
a normal Hash, in fact), which means that data cached under one backend is
|
91
|
+
invisible to all other backends. This leads to low cache hit rates and excessive
|
92
|
+
memory use, the magnitude of which is a function of the number of backends in
|
93
|
+
use. Further, the heap storage provides no mechanism for purging unused entries
|
94
|
+
so memory use is guaranteed to exceed that available, given enough time and
|
95
|
+
utilization.
|
96
|
+
|
97
|
+
Use of heap storage is recommended only for testing purposes or for very
|
98
|
+
simple/single-backend deployment scenarios where the number of resources served
|
99
|
+
is small and well understood.
|
100
|
+
|
101
|
+
### Disk Storage
|
102
|
+
|
103
|
+
Stores cached entries on the filesystem.
|
104
|
+
|
105
|
+
use Rack::Cache,
|
106
|
+
:metastore => 'file:/var/cache/rack/meta',
|
107
|
+
:entitystore => 'file:/var/cache/rack/body'
|
108
|
+
|
109
|
+
The URI may specify an absolute, relative, or home-rooted path:
|
110
|
+
|
111
|
+
* `file:/storage/path` - absolute path to storage directory.
|
112
|
+
* `file:storage/path` - relative path to storage directory, rooted at the
|
113
|
+
process's current working directory (`Dir.pwd`).
|
114
|
+
* `file:~user/storage/path` - path to storage directory, rooted at the
|
115
|
+
specified user's home directory.
|
116
|
+
* `file:~/storage/path` - path to storage directory, rooted at the current
|
117
|
+
user's home directory.
|
118
|
+
|
119
|
+
File system storage is simple, requires no special daemons or libraries, has a
|
120
|
+
tiny memory footprint, and allows multiple backends to share a single cache; it
|
121
|
+
is one of the slower storage implementations, however. Its use is recommended in
|
122
|
+
cases where memory is limited or in environments where more complex storage
|
123
|
+
backends (i.e., memcached) are not available. In many cases, it may be
|
124
|
+
acceptable (and even optimal) to use file system storage for the entitystore and
|
125
|
+
a more performant storage implementation (i.e. memcached) for the metastore.
|
126
|
+
|
127
|
+
__NOTE:__ When both the metastore and entitystore are configured to use file
|
128
|
+
system storage, they should be set to different paths to prevent any chance of
|
129
|
+
collision.
|
130
|
+
|
131
|
+
### Memcached Storage
|
132
|
+
|
133
|
+
Stores cached entries in a remote [memcached](http://www.danga.com/memcached/)
|
134
|
+
instance.
|
135
|
+
|
136
|
+
use Rack::Cache,
|
137
|
+
:metastore => 'memcached://localhost:11211/meta',
|
138
|
+
:entitystore => 'memcached://localhost:11211/body'
|
139
|
+
|
140
|
+
The URI must specify the host and port of a remote memcached daemon. The path
|
141
|
+
portion is an optional (but recommended) namespace that is prepended to each
|
142
|
+
cache key.
|
143
|
+
|
144
|
+
The memcached storage backend requires either the `memcache-client` or
|
145
|
+
`memcached` libraries. By default, the `memcache-client` library is used;
|
146
|
+
require the `memcached` library explicitly to use it instead.
|
147
|
+
|
148
|
+
gem install memcache-client
|
149
|
+
|
150
|
+
Memcached storage is reasonably fast and allows multiple backends to share a
|
151
|
+
single cache. It is also the only storage implementation that allows the cache
|
152
|
+
to reside somewhere other than the local machine. The memcached daemon stores
|
153
|
+
all data in local process memory so using it for the entitystore can result in
|
154
|
+
heavy memory usage. It is by far the best option for the metastore in
|
155
|
+
deployments with multiple backend application processes since it allows the
|
156
|
+
cache to be properly distributed and provides fast access to the
|
157
|
+
meta-information required to perform cache logic. Memcached is considerably more
|
158
|
+
complex than the other storage implementations, requiring a separate daemon
|
159
|
+
process and extra libraries. Still, its use is recommended in all cases where
|
160
|
+
you can get away with it.
|
161
|
+
|
162
|
+
[e]: http://blog.evanweaver.com/files/doc/fauna/memcached/files/README.html
|
163
|
+
[f]: http://blog.evanweaver.com/articles/2008/01/21/b-the-fastest-u-can-b-memcached/
|
164
|
+
[l]: http://tangent.org/552/libmemcached.html
|
@@ -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
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
# = HTTP Caching For Rack
|
4
|
+
#
|
5
|
+
# Rack::Cache is suitable as a quick, drop-in component to enable HTTP caching
|
6
|
+
# for Rack-enabled applications that produce freshness (+Expires+, +Cache-Control+)
|
7
|
+
# and/or validation (+Last-Modified+, +ETag+) information.
|
8
|
+
#
|
9
|
+
# * Standards-based (RFC 2616 compliance)
|
10
|
+
# * Freshness/expiration based caching and validation
|
11
|
+
# * Supports HTTP Vary
|
12
|
+
# * Portable: 100% Ruby / works with any Rack-enabled framework
|
13
|
+
# * Disk, memcached, and heap memory storage backends
|
14
|
+
#
|
15
|
+
# === Usage
|
16
|
+
#
|
17
|
+
# Create with default options:
|
18
|
+
# require 'rack/cache'
|
19
|
+
# Rack::Cache.new(app, :verbose => true, :entitystore => 'file:cache')
|
20
|
+
#
|
21
|
+
# Within a rackup file (or with Rack::Builder):
|
22
|
+
# require 'rack/cache'
|
23
|
+
# use Rack::Cache do
|
24
|
+
# set :verbose, true
|
25
|
+
# set :metastore, 'memcached://localhost:11211/meta'
|
26
|
+
# set :entitystore, 'file:/var/cache/rack'
|
27
|
+
# end
|
28
|
+
# run app
|
29
|
+
module Rack::Cache
|
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'
|
35
|
+
|
36
|
+
# Create a new Rack::Cache middleware component that fetches resources from
|
37
|
+
# the specified backend application. The +options+ Hash can be used to
|
38
|
+
# specify default configuration values (see attributes defined in
|
39
|
+
# Rack::Cache::Options for possible key/values). When a block is given, it
|
40
|
+
# is executed within the context of the newly create Rack::Cache::Context
|
41
|
+
# object.
|
42
|
+
def self.new(backend, options={}, &b)
|
43
|
+
Context.new(backend, options, &b)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Rack::Cache::AppEngine
|
4
|
+
|
5
|
+
module MC
|
6
|
+
require 'java'
|
7
|
+
|
8
|
+
import com.google.appengine.api.memcache.Expiration;
|
9
|
+
import com.google.appengine.api.memcache.MemcacheService;
|
10
|
+
import com.google.appengine.api.memcache.MemcacheServiceFactory;
|
11
|
+
import com.google.appengine.api.memcache.Stats;
|
12
|
+
|
13
|
+
Service = MemcacheServiceFactory.getMemcacheService
|
14
|
+
end unless defined?(Rack::Cache::AppEngine::MC)
|
15
|
+
|
16
|
+
class MemCache
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
@cache = MC::Service
|
20
|
+
@cache.namespace = options[:namespace] if options[:namespace]
|
21
|
+
end
|
22
|
+
|
23
|
+
def contains?(key)
|
24
|
+
MC::Service.contains(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(key)
|
28
|
+
value = MC::Service.get(key)
|
29
|
+
Marshal.load(Base64.decode64(value)) if value
|
30
|
+
end
|
31
|
+
|
32
|
+
def put(key, value, ttl = nil)
|
33
|
+
expiration = ttl ? MC::Expiration.byDeltaSeconds(ttl) : nil
|
34
|
+
value = Base64.encode64(Marshal.dump(value)).gsub(/\n/, '')
|
35
|
+
MC::Service.put(key, value, expiration)
|
36
|
+
end
|
37
|
+
|
38
|
+
def namespace
|
39
|
+
MC::Service.getNamespace
|
40
|
+
end
|
41
|
+
|
42
|
+
def namespace=(value)
|
43
|
+
MC::Service.setNamespace(value.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(key)
|
47
|
+
MC::Service.delete(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -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
|