Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 373
- Log:
Initial import of Radiant 0.9.1, which is now packaged as a gem. This is an
import of the tagged 0.9.1 source checked out from GitHub, which isn't quite
the same as the gem distribution - but it doesn't seem to be available in an
archived form and the installed gem already has modifications, so this is
the closest I can get.
- Author:
- rool
- Date:
- Mon Mar 21 13:40:05 +0000 2011
- Size:
- 8829 Bytes
1 | require 'time' |
2 | require 'set' |
3 | require 'rack/response' |
4 | require 'rack/utils' |
5 | require 'rack/cache/cachecontrol' |
6 | |
7 | module Rack::Cache |
8 | |
9 | # Provides access to the response generated by the downstream application. The |
10 | # +response+, +original_response+, and +entry+ objects exposed by the Core |
11 | # caching engine are instances of this class. |
12 | # |
13 | # Response objects respond to a variety of convenience methods, including |
14 | # those defined in Rack::Response::Helpers, Rack::Cache::Headers, |
15 | # and Rack::Cache::ResponseHeaders. |
16 | # |
17 | # Note that Rack::Cache::Response is not a subclass of Rack::Response and does |
18 | # not perform many of the same initialization and finalization tasks. For |
19 | # example, the body is not slurped during initialization and there are no |
20 | # facilities for generating response output. |
21 | class Response |
22 | include Rack::Response::Helpers |
23 | |
24 | # Rack response tuple accessors. |
25 | attr_accessor :status, :headers, :body |
26 | |
27 | # The time when the Response object was instantiated. |
28 | attr_reader :now |
29 | |
30 | # Create a Response instance given the response status code, header hash, |
31 | # and body. |
32 | def initialize(status, headers, body) |
33 | @status = status |
34 | @headers = Rack::Utils::HeaderHash.new(headers) |
35 | @body = body |
36 | @now = Time.now |
37 | @headers['Date'] ||= @now.httpdate |
38 | end |
39 | |
40 | def initialize_copy(other) |
41 | super |
42 | @headers = other.headers.dup |
43 | end |
44 | |
45 | # Return the status, headers, and body in a three-tuple. |
46 | def to_a |
47 | [status, headers.to_hash, body] |
48 | end |
49 | |
50 | # Status codes of responses that MAY be stored by a cache or used in reply |
51 | # to a subsequent request. |
52 | # |
53 | # http://tools.ietf.org/html/rfc2616#section-13.4 |
54 | CACHEABLE_RESPONSE_CODES = [ |
55 | 200, # OK |
56 | 203, # Non-Authoritative Information |
57 | 300, # Multiple Choices |
58 | 301, # Moved Permanently |
59 | 302, # Found |
60 | 404, # Not Found |
61 | 410 # Gone |
62 | ].to_set |
63 | |
64 | # A Hash of name=value pairs that correspond to the Cache-Control header. |
65 | # Valueless parameters (e.g., must-revalidate, no-store) have a Hash value |
66 | # of true. This method always returns a Hash, empty if no Cache-Control |
67 | # header is present. |
68 | def cache_control |
69 | @cache_control ||= CacheControl.new(headers['Cache-Control']) |
70 | end |
71 | |
72 | # Set the Cache-Control header to the values specified by the Hash. See |
73 | # the #cache_control method for information on expected Hash structure. |
74 | def cache_control=(value) |
75 | if value.respond_to? :to_hash |
76 | cache_control.clear |
77 | cache_control.merge!(value) |
78 | value = cache_control.to_s |
79 | end |
80 | |
81 | if value.nil? || value.empty? |
82 | headers.delete('Cache-Control') |
83 | else |
84 | headers['Cache-Control'] = value |
85 | end |
86 | end |
87 | |
88 | # Determine if the response is "fresh". Fresh responses may be served from |
89 | # cache without any interaction with the origin. A response is considered |
90 | # fresh when it includes a Cache-Control/max-age indicator or Expiration |
91 | # header and the calculated age is less than the freshness lifetime. |
92 | def fresh? |
93 | ttl && ttl > 0 |
94 | end |
95 | |
96 | # Determine if the response is worth caching under any circumstance. Responses |
97 | # marked "private" with an explicit Cache-Control directive are considered |
98 | # uncacheable |
99 | # |
100 | # Responses with neither a freshness lifetime (Expires, max-age) nor cache |
101 | # validator (Last-Modified, ETag) are considered uncacheable. |
102 | def cacheable? |
103 | return false unless CACHEABLE_RESPONSE_CODES.include?(status) |
104 | return false if cache_control.no_store? || cache_control.private? |
105 | validateable? || fresh? |
106 | end |
107 | |
108 | # Determine if the response includes headers that can be used to validate |
109 | # the response with the origin using a conditional GET request. |
110 | def validateable? |
111 | headers.key?('Last-Modified') || headers.key?('ETag') |
112 | end |
113 | |
114 | # Mark the response "private", making it ineligible for serving other |
115 | # clients. |
116 | def private=(value) |
117 | value = value ? true : nil |
118 | self.cache_control = cache_control. |
119 | merge('public' => !value, 'private' => value) |
120 | end |
121 | |
122 | # Indicates that the cache must not serve a stale response in any |
123 | # circumstance without first revalidating with the origin. When present, |
124 | # the TTL of the response should not be overriden to be greater than the |
125 | # value provided by the origin. |
126 | def must_revalidate? |
127 | cache_control.must_revalidate || cache_control.proxy_revalidate |
128 | end |
129 | |
130 | # Mark the response stale by setting the Age header to be equal to the |
131 | # maximum age of the response. |
132 | def expire! |
133 | headers['Age'] = max_age.to_s if fresh? |
134 | end |
135 | |
136 | # The date, as specified by the Date header. When no Date header is present, |
137 | # set the Date header to Time.now and return. |
138 | def date |
139 | if date = headers['Date'] |
140 | Time.httpdate(date) |
141 | else |
142 | headers['Date'] = now.httpdate unless headers.frozen? |
143 | now |
144 | end |
145 | end |
146 | |
147 | # The age of the response. |
148 | def age |
149 | (headers['Age'] || [(now - date).to_i, 0].max).to_i |
150 | end |
151 | |
152 | # The number of seconds after the time specified in the response's Date |
153 | # header when the the response should no longer be considered fresh. First |
154 | # check for a s-maxage directive, then a max-age directive, and then fall |
155 | # back on an expires header; return nil when no maximum age can be |
156 | # established. |
157 | def max_age |
158 | cache_control.shared_max_age || |
159 | cache_control.max_age || |
160 | (expires && (expires - date)) |
161 | end |
162 | |
163 | # The value of the Expires header as a Time object. |
164 | def expires |
165 | headers['Expires'] && Time.httpdate(headers['Expires']) |
166 | end |
167 | |
168 | # The number of seconds after which the response should no longer |
169 | # be considered fresh. Sets the Cache-Control max-age directive. |
170 | def max_age=(value) |
171 | self.cache_control = cache_control.merge('max-age' => value.to_s) |
172 | end |
173 | |
174 | # Like #max_age= but sets the s-maxage directive, which applies only |
175 | # to shared caches. |
176 | def shared_max_age=(value) |
177 | self.cache_control = cache_control.merge('s-maxage' => value.to_s) |
178 | end |
179 | |
180 | # The response's time-to-live in seconds, or nil when no freshness |
181 | # information is present in the response. When the responses #ttl |
182 | # is <= 0, the response may not be served from cache without first |
183 | # revalidating with the origin. |
184 | def ttl |
185 | max_age - age if max_age |
186 | end |
187 | |
188 | # Set the response's time-to-live for shared caches to the specified number |
189 | # of seconds. This adjusts the Cache-Control/s-maxage directive. |
190 | def ttl=(seconds) |
191 | self.shared_max_age = age + seconds |
192 | end |
193 | |
194 | # Set the response's time-to-live for private/client caches. This adjusts |
195 | # the Cache-Control/max-age directive. |
196 | def client_ttl=(seconds) |
197 | self.max_age = age + seconds |
198 | end |
199 | |
200 | # The String value of the Last-Modified header exactly as it appears |
201 | # in the response (i.e., no date parsing / conversion is performed). |
202 | def last_modified |
203 | headers['Last-Modified'] |
204 | end |
205 | |
206 | # The literal value of ETag HTTP header or nil if no ETag is specified. |
207 | def etag |
208 | headers['ETag'] |
209 | end |
210 | |
211 | # Determine if the response was last modified at the time provided. |
212 | # time_value is the exact string provided in an origin response's |
213 | # Last-Modified header. |
214 | def last_modified_at?(time_value) |
215 | time_value && last_modified == time_value |
216 | end |
217 | |
218 | # Determine if response's ETag matches the etag value provided. Return |
219 | # false when either value is nil. |
220 | def etag_matches?(etag) |
221 | etag && self.etag == etag |
222 | end |
223 | |
224 | # Headers that MUST NOT be included with 304 Not Modified responses. |
225 | # |
226 | # http://tools.ietf.org/html/rfc2616#section-10.3.5 |
227 | NOT_MODIFIED_OMIT_HEADERS = %w[ |
228 | Allow |
229 | Content-Encoding |
230 | Content-Language |
231 | Content-Length |
232 | Content-MD5 |
233 | Content-Type |
234 | Last-Modified |
235 | ].to_set |
236 | |
237 | # Modify the response so that it conforms to the rules defined for |
238 | # '304 Not Modified'. This sets the status, removes the body, and |
239 | # discards any headers that MUST NOT be included in 304 responses. |
240 | # |
241 | # http://tools.ietf.org/html/rfc2616#section-10.3.5 |
242 | def not_modified! |
243 | self.status = 304 |
244 | self.body = [] |
245 | NOT_MODIFIED_OMIT_HEADERS.each { |name| headers.delete(name) } |
246 | nil |
247 | end |
248 | |
249 | # The literal value of the Vary header, or nil when no header is present. |
250 | def vary |
251 | headers['Vary'] |
252 | end |
253 | |
254 | # Does the response include a Vary header? |
255 | def vary? |
256 | ! vary.nil? |
257 | end |
258 | |
259 | # An array of header names given in the Vary header or an empty |
260 | # array when no Vary header is present. |
261 | def vary_header_names |
262 | return [] unless vary = headers['Vary'] |
263 | vary.split(/[\s,]+/) |
264 | end |
265 | |
266 | end |
267 | end |