Parent

Included Modules

Rack::Cache::Context

Implements Rack’s middleware interface and provides the context for all cache logic, including the core logic engine.

Attributes

trace[R]

Array of trace Symbols

backend[R]

The Rack application object immediately downstream.

Public Class Methods

new(backend, options={}) click to toggle source
    # File lib/rack/cache/context.rb, line 18
18:     def initialize(backend, options={})
19:       @backend = backend
20:       @trace = []
21:       @env = nil
22: 
23:       initialize_options options
24:       yield self if block_given?
25: 
26:       @private_header_keys =
27:         private_headers.map { |name| "HTTP_#{name.upcase.tr('-', '_')}" }
28:     end

Public Instance Methods

call(env) click to toggle source

The Rack call interface. The receiver acts as a prototype and runs each request in a dup object unless the rack.run_once variable is set in the environment.

    # File lib/rack/cache/context.rb, line 47
47:     def call(env)
48:       if env['rack.run_once']
49:         call! env
50:       else
51:         clone.call! env
52:       end
53:     end
call!(env) click to toggle source

The real Rack call interface. The caching logic is performed within the context of the receiver.

    # File lib/rack/cache/context.rb, line 57
57:     def call!(env)
58:       @trace = []
59:       @default_options.each { |k,v| env[k] ||= v }
60:       @env = env
61:       @request = Request.new(@env.dup.freeze)
62: 
63:       response =
64:         if @request.get? || @request.head?
65:           if !@env['HTTP_EXPECT'] && !@env['rack-cache.force-pass']
66:             lookup
67:           else
68:             pass
69:           end
70:         else
71:           invalidate
72:         end
73: 
74:       # log trace and set X-Rack-Cache tracing header
75:       trace = @trace.join(', ')
76:       response.headers['X-Rack-Cache'] = trace
77: 
78:       # write log message to rack.errors
79:       if verbose?
80:         message = "cache: [%s %s] %s\n" %
81:           [@request.request_method, @request.fullpath, trace]
82:         @env['rack.errors'].write(message)
83:       end
84: 
85:       # tidy up response a bit
86:       if (@request.get? || @request.head?) && not_modified?(response)
87:         response.not_modified!
88:       end
89: 
90:       if @request.head?
91:         response.body.close if response.body.respond_to?(:close)
92:         response.body = []
93:       end
94:       response.to_a
95:     end
entitystore() click to toggle source

The configured EntityStore instance. Changing the rack-cache.entitystore value effects the result of this method immediately.

    # File lib/rack/cache/context.rb, line 39
39:     def entitystore
40:       uri = options['rack-cache.entitystore']
41:       storage.resolve_entitystore_uri(uri)
42:     end
metastore() click to toggle source

The configured MetaStore instance. Changing the rack-cache.metastore value effects the result of this method immediately.

    # File lib/rack/cache/context.rb, line 32
32:     def metastore
33:       uri = options['rack-cache.metastore']
34:       storage.resolve_metastore_uri(uri)
35:     end

Private Instance Methods

fetch() click to toggle source

The cache missed or a reload is required. Forward the request to the backend and determine whether the response should be stored. This allows conditional / validation requests through to the backend but performs no caching of the response when the backend returns a 304.

     # File lib/rack/cache/context.rb, line 241
241:     def fetch
242:       # send no head requests because we want content
243:       @env['REQUEST_METHOD'] = 'GET'
244: 
245:       response = forward
246: 
247:       # Mark the response as explicitly private if any of the private
248:       # request headers are present and the response was not explicitly
249:       # declared public.
250:       if private_request? && !response.cache_control.public?
251:         response.private = true
252:       elsif default_ttl > 0 && response.ttl.nil? && !response.cache_control.must_revalidate?
253:         # assign a default TTL for the cache entry if none was specified in
254:         # the response; the must-revalidate cache control directive disables
255:         # default ttl assigment.
256:         response.ttl = default_ttl
257:       end
258: 
259:       store(response) if response.cacheable?
260: 
261:       response
262:     end
forward() click to toggle source

Delegate the request to the backend and create the response.

     # File lib/rack/cache/context.rb, line 135
135:     def forward
136:       Response.new(*backend.call(@env))
137:     end
fresh_enough?(entry) click to toggle source

Whether the cache entry is “fresh enough” to satisfy the request.

     # File lib/rack/cache/context.rb, line 124
124:     def fresh_enough?(entry)
125:       if entry.fresh?
126:         if allow_revalidate? && max_age = @request.cache_control.max_age
127:           max_age > 0 && max_age >= entry.age
128:         else
129:           true
130:         end
131:       end
132:     end
invalidate() click to toggle source

Invalidate POST, PUT, DELETE and all methods not understood by this cache See RFC2616 13.10

     # File lib/rack/cache/context.rb, line 148
148:     def invalidate
149:       metastore.invalidate(@request, entitystore)
150:     rescue Exception => e
151:       log_error(e)
152:       pass
153:     else
154:       record :invalidate
155:       pass
156:     end
log_error(exception) click to toggle source
     # File lib/rack/cache/context.rb, line 282
282:     def log_error(exception)
283:       @env['rack.errors'].write("cache error: #{exception.message}\n#{exception.backtrace.join("\n")}\n")
284:     end
lookup() click to toggle source

Try to serve the response from cache. When a matching cache entry is found and is fresh, use it as the response without forwarding any request to the backend. When a matching cache entry is found but is stale, attempt to # the entry with the backend using conditional GET. When no matching cache entry is found, trigger # processing.

     # File lib/rack/cache/context.rb, line 163
163:     def lookup
164:       if @request.no_cache? && allow_reload?
165:         record :reload
166:         fetch
167:       else
168:         begin
169:           entry = metastore.lookup(@request, entitystore)
170:         rescue Exception => e
171:           log_error(e)
172:           return pass
173:         end
174:         if entry
175:           if fresh_enough?(entry)
176:             record :fresh
177:             entry.headers['Age'] = entry.age.to_s
178:             entry
179:           else
180:             record :stale
181:             validate(entry)
182:           end
183:         else
184:           record :miss
185:           fetch
186:         end
187:       end
188:     end
not_modified?(response) click to toggle source

Determine if the # validators (ETag, Last-Modified) matches a conditional value specified in #.

     # File lib/rack/cache/context.rb, line 113
113:     def not_modified?(response)
114:       last_modified = @request.env['HTTP_IF_MODIFIED_SINCE']
115:       if etags = @request.env['HTTP_IF_NONE_MATCH']
116:         etags = etags.split(/\s*,\s*/)
117:         (etags.include?(response.etag) || etags.include?('*')) && (!last_modified || response.last_modified == last_modified)
118:       elsif last_modified
119:         response.last_modified == last_modified
120:       end
121:     end
pass() click to toggle source

The request is sent to the backend, and the backend’s response is sent to the client, but is not entered into the cache.

     # File lib/rack/cache/context.rb, line 141
141:     def pass
142:       record :pass
143:       forward
144:     end
private_request?() click to toggle source

Does the request include authorization or other sensitive information that should cause the response to be considered private by default? Private responses are not stored in the cache.

     # File lib/rack/cache/context.rb, line 107
107:     def private_request?
108:       @private_header_keys.any? { |key| @env.key?(key) }
109:     end
record(event) click to toggle source

Record that an event took place.

     # File lib/rack/cache/context.rb, line 100
100:     def record(event)
101:       @trace << event
102:     end
store(response) click to toggle source

Write the response to the cache.

     # File lib/rack/cache/context.rb, line 265
265:     def store(response)
266:       strip_ignore_headers(response)
267:       metastore.store(@request, response, entitystore)
268:       response.headers['Age'] = response.age.to_s
269:     rescue Exception => e
270:       log_error(e)
271:       nil
272:     else
273:       record :store
274:     end
strip_ignore_headers(response) click to toggle source

Remove all ignored response headers before writing to the cache.

     # File lib/rack/cache/context.rb, line 277
277:     def strip_ignore_headers(response)
278:       stripped_values = ignore_headers.map { |name| response.headers.delete(name) }
279:       record :ignore if stripped_values.any?
280:     end
validate(entry) click to toggle source

Validate that the cache entry is fresh. The original request is used as a template for a conditional GET request with the backend.

     # File lib/rack/cache/context.rb, line 192
192:     def validate(entry)
193:       # send no head requests because we want content
194:       @env['REQUEST_METHOD'] = 'GET'
195: 
196:       # add our cached last-modified validator to the environment
197:       @env['HTTP_IF_MODIFIED_SINCE'] = entry.last_modified
198: 
199:       # Add our cached etag validator to the environment.
200:       # We keep the etags from the client to handle the case when the client
201:       # has a different private valid entry which is not cached here.
202:       cached_etags = entry.etag.to_s.split(/\s*,\s*/)
203:       request_etags = @request.env['HTTP_IF_NONE_MATCH'].to_s.split(/\s*,\s*/)
204:       etags = (cached_etags + request_etags).uniq
205:       @env['HTTP_IF_NONE_MATCH'] = etags.empty? ? nil : etags.join(', ')
206: 
207:       response = forward
208: 
209:       if response.status == 304
210:         record :valid
211: 
212:         # Check if the response validated which is not cached here
213:         etag = response.headers['ETag']
214:         return response if etag && request_etags.include?(etag) && !cached_etags.include?(etag)
215: 
216:         entry = entry.dup
217:         entry.headers.delete('Date')
218:         ]Date Expires Cache-Control ETag Last-Modified].each do |name|
219:           next unless value = response.headers[name]
220:           entry.headers[name] = value
221:         end
222: 
223:         # even though it's empty, be sure to close the response body from upstream
224:         # because middleware use close to signal end of response
225:         response.body.close if response.body.respond_to?(:close)
226: 
227:         response = entry
228:       else
229:         record :invalid
230:       end
231: 
232:       store(response) if response.cacheable?
233: 
234:       response
235:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.