]>
Commit | Line | Data |
---|---|---|
e4e6a8db | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
4 | * Squid software is distributed under GPLv2+ license and includes |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
e4e6a8db | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 22 Refresh Calculation */ |
10 | ||
c2afddd8 AJ |
11 | #ifndef USE_POSIX_REGEX |
12 | #define USE_POSIX_REGEX /* put before includes; always use POSIX */ | |
13 | #endif | |
14 | ||
582c2af2 | 15 | #include "squid.h" |
0fa036e3 | 16 | #include "base/PackableStream.h" |
582c2af2 | 17 | #include "HttpHdrCc.h" |
924f73bc | 18 | #include "HttpReply.h" |
602d9612 | 19 | #include "HttpRequest.h" |
582c2af2 | 20 | #include "MemObject.h" |
602d9612 | 21 | #include "mgr/Registration.h" |
8b082ed9 | 22 | #include "refresh.h" |
8d9a8184 | 23 | #include "RefreshPattern.h" |
4d5904f7 | 24 | #include "SquidConfig.h" |
582c2af2 | 25 | #include "Store.h" |
ed6e9fb9 | 26 | #include "util.h" |
e4e6a8db | 27 | |
7d47d8e6 | 28 | typedef enum { |
65fa5c61 | 29 | rcHTTP, |
30 | rcICP, | |
31 | #if USE_HTCP | |
32 | rcHTCP, | |
33 | #endif | |
34 | #if USE_CACHE_DIGESTS | |
35 | rcCDigest, | |
36 | #endif | |
37 | rcStore, | |
38 | rcCount | |
7d47d8e6 | 39 | } refreshCountsEnum; |
829a9357 | 40 | |
96151fc9 DD |
41 | /** |
42 | * Flags indicating which staleness algorithm has been applied. | |
43 | */ | |
26ac0430 | 44 | typedef struct { |
96151fc9 DD |
45 | bool expires; ///< Expires: header absolute timestamp limit |
46 | bool min; ///< Heuristic minimum age limited | |
47 | bool lmfactor; ///< Last-Modified with heuristic determines limit | |
48 | bool max; ///< Configured maximum age limit | |
2fadd50d | 49 | } stale_flags; |
65fa5c61 | 50 | |
51 | /* | |
52 | * This enumerated list assigns specific values, ala HTTP/FTP status | |
53 | * codes. All Fresh codes are in the range 100-199 and all stale | |
54 | * codes are 200-299. We might want to use these codes in logging, | |
55 | * so best to keep them consistent over time. | |
56 | */ | |
57 | enum { | |
58 | FRESH_REQUEST_MAX_STALE_ALL = 100, | |
59 | FRESH_REQUEST_MAX_STALE_VALUE, | |
60 | FRESH_EXPIRES, | |
61 | FRESH_LMFACTOR_RULE, | |
62 | FRESH_MIN_RULE, | |
63 | FRESH_OVERRIDE_EXPIRES, | |
64 | FRESH_OVERRIDE_LASTMOD, | |
65 | STALE_MUST_REVALIDATE = 200, | |
66 | STALE_RELOAD_INTO_IMS, | |
67 | STALE_FORCED_RELOAD, | |
68 | STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE, | |
69 | STALE_EXPIRES, | |
70 | STALE_MAX_RULE, | |
71 | STALE_LMFACTOR_RULE, | |
570d3f75 | 72 | STALE_MAX_STALE, |
65fa5c61 | 73 | STALE_DEFAULT = 299 |
74 | }; | |
75 | ||
26ac0430 | 76 | static struct RefreshCounts { |
829a9357 | 77 | const char *proto; |
1c3e77cd | 78 | int total; |
65fa5c61 | 79 | int status[STALE_DEFAULT + 1]; |
96151fc9 | 80 | } refreshCounts[rcCount]; |
1c3e77cd | 81 | |
1c3e77cd | 82 | static OBJH refreshStats; |
8d9a8184 | 83 | static int refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age, const RefreshPattern * R, stale_flags * sf); |
65fa5c61 | 84 | |
0fa036e3 | 85 | static RefreshPattern DefaultRefresh(nullptr); |
2b5133db | 86 | |
82b2cc12 AJ |
87 | /** Locate the first refresh_pattern rule that matches the given URL by regex. |
88 | * | |
95b8eae2 | 89 | * \return A pointer to the refresh_pattern parameters to use, or nullptr if there is no match. |
82b2cc12 | 90 | */ |
8d9a8184 | 91 | const RefreshPattern * |
6018f0de | 92 | refreshLimits(const char *url) |
93 | { | |
95b8eae2 | 94 | for (auto R = Config.Refresh; R; R = R->next) { |
a5ea7751 | 95 | ++(R->stats.matchTests); |
0fa036e3 | 96 | if (R->regex().match(url)) { |
a5ea7751 | 97 | ++(R->stats.matchCount); |
62e76326 | 98 | return R; |
a5ea7751 | 99 | } |
6018f0de | 100 | } |
62e76326 | 101 | |
95b8eae2 | 102 | return nullptr; |
6018f0de | 103 | } |
104 | ||
0fa036e3 | 105 | /// the first explicit refresh_pattern rule that uses a "." regex (or nil) |
8d9a8184 | 106 | static const RefreshPattern * |
0fa036e3 | 107 | refreshFirstDotRule() |
2b5133db | 108 | { |
95b8eae2 | 109 | for (auto R = Config.Refresh; R; R = R->next) { |
0fa036e3 | 110 | if (R->regex().isDot()) |
62e76326 | 111 | return R; |
2b5133db | 112 | } |
62e76326 | 113 | |
95b8eae2 | 114 | return nullptr; |
2b5133db | 115 | } |
116 | ||
b2f01ec3 | 117 | /** |
65fa5c61 | 118 | * Calculate how stale the response is (or will be at the check_time). |
96151fc9 DD |
119 | * |
120 | * We try the following ways until one gives a result: | |
121 | * | |
122 | * 1. response expiration time, if one was set | |
123 | * 2. age greater than configured maximum | |
124 | * 3. last-modified factor algorithm | |
125 | * 4. age less than configured minimum | |
126 | * 5. default (stale) | |
127 | * | |
128 | * \param entry the StoreEntry being examined | |
129 | * \param check_time the time (maybe future) at which we want to know whether $ | |
130 | * \param age the age of the entry at check_time | |
131 | * \param R the refresh_pattern rule that matched this entry | |
132 | * \param sf small struct to indicate reason for stale/fresh decision | |
65fa5c61 | 133 | * |
b2f01ec3 | 134 | * \retval -1 If the response is fresh. |
96151fc9 | 135 | * \retval >0 The amount of staleness. |
b2f01ec3 | 136 | * \retval 0 NOTE return value of 0 means the response is stale. |
65fa5c61 | 137 | */ |
138 | static int | |
8d9a8184 | 139 | refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age, const RefreshPattern * R, stale_flags * sf) |
65fa5c61 | 140 | { |
96151fc9 DD |
141 | // 1. If the cached object has an explicit expiration time, then we rely on this and |
142 | // completely ignore the Min, Percent and Max values in the refresh_pattern. | |
65fa5c61 | 143 | if (entry->expires > -1) { |
1f848b2c | 144 | sf->expires = true; |
62e76326 | 145 | |
146 | if (entry->expires > check_time) { | |
4a7a3d56 | 147 | debugs(22, 3, "FRESH: expires " << entry->expires << |
148 | " >= check_time " << check_time << " "); | |
bf8fe701 | 149 | |
62e76326 | 150 | return -1; |
151 | } else { | |
4a7a3d56 | 152 | debugs(22, 3, "STALE: expires " << entry->expires << |
153 | " < check_time " << check_time << " "); | |
bf8fe701 | 154 | |
62e76326 | 155 | return (check_time - entry->expires); |
156 | } | |
65fa5c61 | 157 | } |
62e76326 | 158 | |
96151fc9 DD |
159 | debugs(22, 3, "No explicit expiry given, using heuristics to determine freshness"); |
160 | ||
161 | // 2. If the entry is older than the maximum age in the refresh_pattern, it is STALE. | |
65fa5c61 | 162 | if (age > R->max) { |
4a7a3d56 | 163 | debugs(22, 3, "STALE: age " << age << " > max " << R->max << " "); |
1f848b2c | 164 | sf->max = true; |
62e76326 | 165 | return (age - R->max); |
65fa5c61 | 166 | } |
62e76326 | 167 | |
96151fc9 | 168 | // 3. If there is a Last-Modified header, try the last-modified factor algorithm. |
438b41ba EB |
169 | const time_t lastmod_delta = entry->timestamp - entry->lastModified(); |
170 | if (lastmod_delta > 0) { | |
96151fc9 DD |
171 | /* stale_age is the age of the response when it became/becomes stale according to |
172 | * the last-modified factor algorithm. It's how long we can consider the response | |
173 | * fresh from the time we cached it. | |
62e76326 | 174 | */ |
96151fc9 DD |
175 | time_t stale_age = static_cast<time_t>(lastmod_delta * R->pct); |
176 | ||
177 | debugs(22,3, "Last modified " << lastmod_delta << " sec before we cached it, L-M factor " << | |
86c63190 | 178 | (100.0 * R->pct) << "% = " << stale_age << " sec freshness lifetime"); |
1f848b2c | 179 | sf->lmfactor = true; |
62e76326 | 180 | |
181 | if (age >= stale_age) { | |
4a7a3d56 | 182 | debugs(22, 3, "STALE: age " << age << " > stale_age " << stale_age); |
62e76326 | 183 | return (age - stale_age); |
184 | } else { | |
4a7a3d56 | 185 | debugs(22, 3, "FRESH: age " << age << " <= stale_age " << stale_age); |
62e76326 | 186 | return -1; |
187 | } | |
65fa5c61 | 188 | } |
62e76326 | 189 | |
96151fc9 | 190 | // 4. If the entry is not as old as the minimum age in the refresh_pattern, it is FRESH. |
9e4b5932 | 191 | if (age < R->min) { |
96151fc9 | 192 | debugs(22, 3, "FRESH: age (" << age << " sec) is less than configured minimum (" << R->min << " sec)"); |
1f848b2c | 193 | sf->min = true; |
62e76326 | 194 | return -1; |
65fa5c61 | 195 | } |
62e76326 | 196 | |
96151fc9 DD |
197 | // 5. default is stale, by the amount we missed the minimum by |
198 | debugs(22, 3, "STALE: No explicit expiry, no last modified, and older than configured minimum."); | |
65fa5c61 | 199 | return (age - R->min); |
200 | } | |
201 | ||
96151fc9 DD |
202 | /** Checks whether a store entry is fresh or stale, and why. |
203 | * | |
204 | * This is where all aspects of request, response and squid configuration | |
205 | * meet to decide whether a response is cacheable or not: | |
206 | * | |
207 | * 1. Client request headers that affect cacheability, e.g. | |
208 | * - Cache-Control: no-cache | |
209 | * - Cache-Control: max-age=N | |
210 | * - Cache-Control: max-stale[=N] | |
211 | * - Pragma: no-cache | |
212 | * | |
213 | * 2. Server response headers that affect cacheability, e.g. | |
214 | * - Age: | |
215 | * - Cache-Control: proxy-revalidate | |
216 | * - Cache-Control: must-revalidate | |
217 | * - Cache-Control: no-cache | |
218 | * - Cache-Control: max-age=N | |
219 | * - Cache-Control: s-maxage=N | |
220 | * - Date: | |
221 | * - Expires: | |
222 | * - Last-Modified: | |
223 | * | |
224 | * 3. Configuration options, e.g. | |
225 | * - reload-into-ims (refresh_pattern) | |
226 | * - ignore-reload (refresh_pattern) | |
227 | * - refresh-ims (refresh_pattern) | |
228 | * - override-lastmod (refresh_pattern) | |
229 | * - override-expire (refresh_pattern) | |
230 | * - reload_into_ims (global option) | |
231 | * - refresh_all_ims (global option) | |
232 | * | |
233 | * \returns a status code (from enum above): | |
234 | * - FRESH_REQUEST_MAX_STALE_ALL | |
235 | * - FRESH_REQUEST_MAX_STALE_VALUE | |
236 | * - FRESH_EXPIRES | |
237 | * - FRESH_LMFACTOR_RULE | |
238 | * - FRESH_MIN_RULE | |
239 | * - FRESH_OVERRIDE_EXPIRES | |
240 | * - FRESH_OVERRIDE_LASTMOD | |
241 | * - STALE_MUST_REVALIDATE | |
242 | * - STALE_RELOAD_INTO_IMS | |
243 | * - STALE_FORCED_RELOAD | |
244 | * - STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE | |
245 | * - STALE_EXPIRES | |
246 | * - STALE_MAX_RULE | |
247 | * - STALE_LMFACTOR_RULE | |
248 | * - STALE_MAX_STALE | |
249 | * - STALE_DEFAULT | |
250 | * | |
251 | * \note request may be NULL (e.g. for cache digests build) | |
829a9357 | 252 | * |
96151fc9 DD |
253 | * \note the store entry being examined is not necessarily cached (e.g. if |
254 | * this response is being evaluated for the first time) | |
e4e6a8db | 255 | */ |
829a9357 | 256 | static int |
190154cf | 257 | refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta) |
e4e6a8db | 258 | { |
65fa5c61 | 259 | time_t age = 0; |
a207429f | 260 | time_t check_time = squid_curtime + delta; |
65fa5c61 | 261 | int staleness; |
262 | stale_flags sf; | |
62e76326 | 263 | |
96151fc9 | 264 | // get the URL of this entry, if there is one |
851feda6 AJ |
265 | static const SBuf nilUri("<none>"); |
266 | SBuf uri = nilUri; | |
9b5d1d21 | 267 | if (entry->mem_obj) |
c877c0bc | 268 | uri = entry->mem_obj->storeId(); |
7d47d8e6 | 269 | else if (request) |
851feda6 | 270 | uri = request->effectiveRequestUri(); |
7d47d8e6 | 271 | |
851feda6 | 272 | debugs(22, 3, "checking freshness of URI: " << uri); |
65fa5c61 | 273 | |
96151fc9 | 274 | // age is not necessarily the age now, but the age at the given check_time |
65fa5c61 | 275 | if (check_time > entry->timestamp) |
62e76326 | 276 | age = check_time - entry->timestamp; |
277 | ||
9837567d | 278 | // XXX: what to do when age < 0 or counter overflow? |
b2f01ec3 | 279 | assert(age >= 0); |
efd62b86 | 280 | |
96151fc9 DD |
281 | /* We need a refresh rule. In order of preference: |
282 | * | |
283 | * 1. the rule that matches this URI by regex | |
284 | * 2. the "." rule from the config file | |
285 | * 3. the default "." rule | |
286 | */ | |
851feda6 | 287 | // XXX: performance regression. c_str() reallocates |
0fa036e3 | 288 | const RefreshPattern *R = (uri != nilUri) ? refreshLimits(uri.c_str()) : refreshFirstDotRule(); |
aee3523a | 289 | if (nullptr == R) |
62e76326 | 290 | R = &DefaultRefresh; |
291 | ||
0fa036e3 | 292 | debugs(22, 3, "Matched '" << *R << '\''); |
65fa5c61 | 293 | |
64f8c2cb | 294 | debugs(22, 3, "\tage:\t" << age); |
62e76326 | 295 | |
98cacedb | 296 | debugs(22, 3, "\tcheck_time:\t" << Time::FormatRfc1123(check_time)); |
62e76326 | 297 | |
98cacedb | 298 | debugs(22, 3, "\tentry->timestamp:\t" << Time::FormatRfc1123(entry->timestamp)); |
65fa5c61 | 299 | |
450fe1cb | 300 | if (request && !request->flags.ignoreCc) { |
64f8c2cb | 301 | const HttpHdrCc *const cc = request->cache_control; |
810d879f EB |
302 | int minFresh = -1; |
303 | if (cc && cc->hasMinFresh(&minFresh)) { | |
64f8c2cb | 304 | debugs(22, 3, "\tage + min-fresh:\t" << age << " + " << |
77da1817 | 305 | minFresh << " = " << age + minFresh); |
64f8c2cb | 306 | debugs(22, 3, "\tcheck_time + min-fresh:\t" << check_time << " + " |
422acb7f | 307 | << minFresh << " = " << |
98cacedb | 308 | Time::FormatRfc1123(check_time + minFresh)); |
422acb7f FC |
309 | age += minFresh; |
310 | check_time += minFresh; | |
64f8c2cb AR |
311 | } |
312 | } | |
313 | ||
314 | memset(&sf, '\0', sizeof(sf)); | |
315 | ||
316 | staleness = refreshStaleness(entry, check_time, age, R, &sf); | |
317 | ||
318 | debugs(22, 3, "Staleness = " << staleness); | |
319 | ||
66d51f4f | 320 | const auto reply = entry->hasFreshestReply(); // may be nil |
caf65351 | 321 | |
65fd3895 | 322 | // stale-if-error requires any failure be passed thru when its period is over. |
810d879f | 323 | int staleIfError = -1; |
caf65351 | 324 | if (request && reply && reply->cache_control && |
810d879f EB |
325 | reply->cache_control->hasStaleIfError(&staleIfError) && |
326 | staleIfError < staleness) { | |
65fd3895 | 327 | |
96151fc9 | 328 | debugs(22, 3, "stale-if-error period expired. Will produce error if validation fails."); |
e857372a | 329 | request->flags.failOnValidationError = true; |
65fd3895 AJ |
330 | } |
331 | ||
96151fc9 DD |
332 | /* If the origin server specified either of: |
333 | * Cache-Control: must-revalidate | |
334 | * Cache-Control: proxy-revalidate | |
335 | * the spec says the response must always be revalidated if stale. | |
336 | */ | |
fa83b766 EB |
337 | const bool revalidateAlways = EBIT_TEST(entry->flags, ENTRY_REVALIDATE_ALWAYS); |
338 | if (revalidateAlways || (staleness > -1 && | |
d267fa9c | 339 | EBIT_TEST(entry->flags, ENTRY_REVALIDATE_STALE))) { |
fa83b766 | 340 | debugs(22, 3, "YES: Must revalidate stale object (origin set " << |
d267fa9c SM |
341 | (revalidateAlways ? "no-cache or private" : |
342 | "must-revalidate, proxy-revalidate or s-maxage") << ")"); | |
5b1d04af | 343 | if (request) |
e857372a | 344 | request->flags.failOnValidationError = true; |
62e76326 | 345 | return STALE_MUST_REVALIDATE; |
65fa5c61 | 346 | } |
62e76326 | 347 | |
829a9357 | 348 | /* request-specific checks */ |
450fe1cb | 349 | if (request && !request->flags.ignoreCc) { |
62e76326 | 350 | HttpHdrCc *cc = request->cache_control; |
4c3ef9b2 | 351 | |
96151fc9 DD |
352 | /* If the request is an IMS request, and squid is configured NOT to service this from cache |
353 | * (either by 'refresh-ims' in the refresh pattern or 'refresh_all_ims on' globally) | |
354 | * then force a reload from the origin. | |
355 | */ | |
45e5102d | 356 | if (request->flags.ims && (R->flags.refresh_ims || Config.onoff.refresh_all_ims)) { |
96151fc9 DD |
357 | // The client's no-cache header is changed into a IMS query |
358 | debugs(22, 3, "YES: Client IMS request forcing revalidation of object (refresh-ims option)"); | |
4c3ef9b2 | 359 | return STALE_FORCED_RELOAD; |
360 | } | |
361 | ||
626096be | 362 | #if USE_HTTP_VIOLATIONS |
96151fc9 | 363 | /* Normally a client reload request ("Cache-Control: no-cache" or "Pragma: no-cache") |
2f8abb64 | 364 | * means we must treat this response as STALE and fetch a new one. |
96151fc9 DD |
365 | * |
366 | * However, some options exist to override this behaviour. For example, we might just | |
367 | * revalidate our existing response, or even just serve it up without revalidating it. | |
368 | * | |
369 | * ---- Note on the meaning of nocache_hack ----- | |
370 | * | |
371 | * The nocache_hack flag has a very specific and complex meaning: | |
372 | * | |
373 | * (a) this is a reload request ("Cache-Control: no-cache" or "Pragma: no-cache" header) | |
374 | * and (b) the configuration file either has at least one refresh_pattern with | |
375 | * ignore-reload or reload-into-ims (not necessarily the rule matching this request) or | |
376 | * the global reload_into_ims is set to on | |
377 | * | |
378 | * In other words: this is a client reload, and we might need to override | |
379 | * the default behaviour (but we might not). | |
380 | * | |
381 | * "nocache_hack" is a pretty deceptive name for such a complicated meaning. | |
382 | */ | |
383 | if (request->flags.noCacheHack()) { | |
384 | ||
385 | if (R->flags.ignore_reload) { | |
386 | /* The client's no-cache header is ignored completely - we'll try to serve | |
387 | * what we have (assuming it's still fresh, etc.) | |
388 | */ | |
389 | debugs(22, 3, "MAYBE: Ignoring client reload request - trying to serve from cache (ignore-reload option)"); | |
390 | } else if (R->flags.reload_into_ims || Config.onoff.reload_into_ims) { | |
391 | /* The client's no-cache header is not honoured completely - we'll just try | |
392 | * to revalidate our cached copy (IMS to origin) instead of fetching a new | |
393 | * copy with an unconditional GET. | |
394 | */ | |
395 | debugs(22, 3, "YES: Client reload request - cheating, only revalidating with origin (reload-into-ims option)"); | |
396 | return STALE_RELOAD_INTO_IMS; | |
397 | } else { | |
398 | /* The client's no-cache header is honoured - we fetch a new copy from origin */ | |
399 | debugs(22, 3, "YES: Client reload request - fetching new copy from origin"); | |
400 | request->flags.noCache = true; | |
401 | return STALE_FORCED_RELOAD; | |
402 | } | |
62e76326 | 403 | } |
9f60cfdf | 404 | #endif |
96151fc9 DD |
405 | |
406 | // Check the Cache-Control client request header | |
aee3523a | 407 | if (nullptr != cc) { |
96151fc9 DD |
408 | |
409 | // max-age directive | |
810d879f EB |
410 | int maxAge = -1; |
411 | if (cc->hasMaxAge(&maxAge)) { | |
caf65351 | 412 | |
8311b837 | 413 | // RFC 8246: reply contains CC:immutable then ignore client CC:max-age=N |
810d879f EB |
414 | if (reply && reply->cache_control && reply->cache_control->hasImmutable()) { |
415 | debugs(22, 3, "MAYBE: Ignoring client CC:max-age=" << maxAge << " request - 'Cache-Control: immutable'"); | |
caf65351 | 416 | |
626096be | 417 | #if USE_HTTP_VIOLATIONS |
18fe96be | 418 | // Ignore of client "Cache-Control: max-age=0" header |
810d879f | 419 | } else if (R->flags.ignore_reload && maxAge == 0) { |
96151fc9 | 420 | debugs(22, 3, "MAYBE: Ignoring client reload request - trying to serve from cache (ignore-reload option)"); |
528b2c61 | 421 | #endif |
caf65351 | 422 | |
18fe96be | 423 | // Honour client "Cache-Control: max-age=x" header |
810d879f EB |
424 | } else if (age > maxAge || maxAge == 0) { |
425 | debugs(22, 3, "YES: Revalidating object - client 'Cache-Control: max-age=" << maxAge << "'"); | |
caf65351 | 426 | return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE; |
62e76326 | 427 | } |
428 | } | |
429 | ||
96151fc9 | 430 | // max-stale directive |
810d879f EB |
431 | int maxStale = -1; |
432 | if (cc->hasMaxStale(&maxStale) && staleness > -1) { | |
433 | if (maxStale==HttpHdrCc::MAX_STALE_ANY) { | |
96151fc9 | 434 | debugs(22, 3, "NO: Client accepts a stale response of any age - 'Cache-Control: max-stale'"); |
62e76326 | 435 | return FRESH_REQUEST_MAX_STALE_ALL; |
810d879f EB |
436 | } else if (staleness < maxStale) { |
437 | debugs(22, 3, "NO: Client accepts a stale response - 'Cache-Control: max-stale=" << maxStale << "'"); | |
62e76326 | 438 | return FRESH_REQUEST_MAX_STALE_VALUE; |
439 | } | |
440 | } | |
441 | } | |
48f44632 | 442 | } |
62e76326 | 443 | |
96151fc9 | 444 | // If the object is fresh, return the right FRESH_ code |
65fa5c61 | 445 | if (-1 == staleness) { |
96151fc9 | 446 | debugs(22, 3, "Object isn't stale.."); |
ed1de692 | 447 | if (sf.expires) { |
96151fc9 | 448 | debugs(22, 3, "returning FRESH_EXPIRES"); |
62e76326 | 449 | return FRESH_EXPIRES; |
26ac0430 | 450 | } |
62e76326 | 451 | |
452 | assert(!sf.max); | |
453 | ||
ed1de692 | 454 | if (sf.lmfactor) { |
96151fc9 | 455 | debugs(22, 3, "returning FRESH_LMFACTOR_RULE"); |
62e76326 | 456 | return FRESH_LMFACTOR_RULE; |
26ac0430 | 457 | } |
62e76326 | 458 | |
459 | assert(sf.min); | |
460 | ||
96151fc9 | 461 | debugs(22, 3, "returning FRESH_MIN_RULE"); |
62e76326 | 462 | return FRESH_MIN_RULE; |
1dfa1d81 | 463 | } |
62e76326 | 464 | |
65fa5c61 | 465 | /* |
466 | * At this point the response is stale, unless one of | |
542c4d60 | 467 | * the override options kicks in. |
570d3f75 | 468 | * NOTE: max-stale config blocks the overrides. |
65fa5c61 | 469 | */ |
570d3f75 | 470 | int max_stale = (R->max_stale >= 0 ? R->max_stale : Config.maxStale); |
60c3d5b7 | 471 | if ( max_stale >= 0 && staleness > max_stale) { |
96151fc9 | 472 | debugs(22, 3, "YES: refresh_pattern max-stale=N limit from squid.conf"); |
570d3f75 | 473 | if (request) |
e857372a | 474 | request->flags.failOnValidationError = true; |
570d3f75 AJ |
475 | return STALE_MAX_STALE; |
476 | } | |
477 | ||
65fa5c61 | 478 | if (sf.expires) { |
626096be | 479 | #if USE_HTTP_VIOLATIONS |
62e76326 | 480 | |
481 | if (R->flags.override_expire && age < R->min) { | |
96151fc9 | 482 | debugs(22, 3, "NO: Serving from cache - even though explicit expiry has passed, we enforce Min value (override-expire option)"); |
62e76326 | 483 | return FRESH_OVERRIDE_EXPIRES; |
484 | } | |
485 | ||
65fa5c61 | 486 | #endif |
62e76326 | 487 | return STALE_EXPIRES; |
e4e6a8db | 488 | } |
62e76326 | 489 | |
65fa5c61 | 490 | if (sf.max) |
62e76326 | 491 | return STALE_MAX_RULE; |
492 | ||
65fa5c61 | 493 | if (sf.lmfactor) { |
626096be | 494 | #if USE_HTTP_VIOLATIONS |
62e76326 | 495 | if (R->flags.override_lastmod && age < R->min) { |
96151fc9 | 496 | debugs(22, 3, "NO: Serving from cache - even though L-M factor says the object is stale, we enforce Min value (override-lastmod option)"); |
62e76326 | 497 | return FRESH_OVERRIDE_LASTMOD; |
498 | } | |
65fa5c61 | 499 | #endif |
96151fc9 | 500 | debugs(22, 3, "YES: L-M factor says the object is stale'"); |
62e76326 | 501 | return STALE_LMFACTOR_RULE; |
e4e6a8db | 502 | } |
62e76326 | 503 | |
96151fc9 | 504 | debugs(22, 3, "returning STALE_DEFAULT"); |
65fa5c61 | 505 | return STALE_DEFAULT; |
e4e6a8db | 506 | } |
48f44632 | 507 | |
96151fc9 DD |
508 | /** |
509 | * This is called by http.cc once it has received and parsed the origin server's | |
510 | * response headers. It uses the result as part of its algorithm to decide whether a | |
511 | * response should be cached. | |
512 | * | |
513 | * \retval true if the entry is cacheable, regardless of whether FRESH or STALE | |
514 | * \retval false if the entry is not cacheable | |
515 | * | |
516 | * TODO: this algorithm seems a bit odd and might not be quite right. Verify against HTTPbis. | |
517 | */ | |
518 | bool | |
cfa9f1cb | 519 | refreshIsCachable(const StoreEntry * entry) |
520 | { | |
521 | /* | |
522 | * Don't look at the request to avoid no-cache and other nuisances. | |
523 | * the object should have a mem_obj so the URL will be found there. | |
26ac0430 AJ |
524 | * minimum_expiry_time seconds delta (defaults to 60 seconds), to |
525 | * avoid objects which expire almost immediately, and which can't | |
6a2f3fcf | 526 | * be refreshed. |
cfa9f1cb | 527 | */ |
aee3523a | 528 | int reason = refreshCheck(entry, nullptr, Config.minimum_expiry_time); |
5db6bf73 FC |
529 | ++ refreshCounts[rcStore].total; |
530 | ++ refreshCounts[rcStore].status[reason]; | |
62e76326 | 531 | |
451c8350 | 532 | if (reason < STALE_MUST_REVALIDATE) |
62e76326 | 533 | /* Does not need refresh. This is certainly cachable */ |
96151fc9 | 534 | return true; |
62e76326 | 535 | |
438b41ba EB |
536 | if (entry->lastModified() < 0) |
537 | /* We should know entry's modification time to do a refresh */ | |
96151fc9 | 538 | return false; |
62e76326 | 539 | |
aee3523a | 540 | if (entry->mem_obj == nullptr) |
62e76326 | 541 | /* no mem_obj? */ |
96151fc9 | 542 | return true; |
62e76326 | 543 | |
66d51f4f | 544 | if (entry->mem_obj->baseReply().content_length == 0) |
62e76326 | 545 | /* No use refreshing (caching?) 0 byte objects */ |
96151fc9 | 546 | return false; |
62e76326 | 547 | |
cfa9f1cb | 548 | /* This seems to be refreshable. Cache it */ |
96151fc9 | 549 | return true; |
cfa9f1cb | 550 | } |
551 | ||
bcfba8bd AR |
552 | /// whether reply is stale if it is a hit |
553 | static bool | |
554 | refreshIsStaleIfHit(const int reason) | |
555 | { | |
556 | switch (reason) { | |
557 | case FRESH_MIN_RULE: | |
558 | case FRESH_LMFACTOR_RULE: | |
559 | case FRESH_EXPIRES: | |
560 | return false; | |
561 | default: | |
562 | return true; | |
563 | } | |
564 | } | |
565 | ||
96151fc9 DD |
566 | /** |
567 | * Protocol-specific wrapper around refreshCheck() function. | |
568 | * | |
569 | * Note the reason for STALE/FRESH then return true/false respectively. | |
570 | * | |
571 | * \retval 1 if STALE | |
572 | * \retval 0 if FRESH | |
573 | */ | |
829a9357 | 574 | int |
190154cf | 575 | refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request) |
7d47d8e6 | 576 | { |
65fa5c61 | 577 | int reason = refreshCheck(entry, request, 0); |
5db6bf73 FC |
578 | ++ refreshCounts[rcHTTP].total; |
579 | ++ refreshCounts[rcHTTP].status[reason]; | |
450fe1cb | 580 | request->flags.staleIfHit = refreshIsStaleIfHit(reason); |
d2a6dcba | 581 | // TODO: Treat collapsed responses as fresh but second-hand. |
bcfba8bd | 582 | return (Config.onoff.offline || reason < 200) ? 0 : 1; |
829a9357 | 583 | } |
584 | ||
96151fc9 | 585 | /// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request) |
829a9357 | 586 | int |
190154cf | 587 | refreshCheckICP(const StoreEntry * entry, HttpRequest * request) |
7d47d8e6 | 588 | { |
65fa5c61 | 589 | int reason = refreshCheck(entry, request, 30); |
5db6bf73 FC |
590 | ++ refreshCounts[rcICP].total; |
591 | ++ refreshCounts[rcICP].status[reason]; | |
65fa5c61 | 592 | return (reason < 200) ? 0 : 1; |
829a9357 | 593 | } |
594 | ||
65fa5c61 | 595 | #if USE_HTCP |
96151fc9 | 596 | /// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request) |
32b3cf93 | 597 | int |
190154cf | 598 | refreshCheckHTCP(const StoreEntry * entry, HttpRequest * request) |
32b3cf93 | 599 | { |
65fa5c61 | 600 | int reason = refreshCheck(entry, request, 10); |
5db6bf73 FC |
601 | ++ refreshCounts[rcHTCP].total; |
602 | ++ refreshCounts[rcHTCP].status[reason]; | |
65fa5c61 | 603 | return (reason < 200) ? 0 : 1; |
32b3cf93 | 604 | } |
62e76326 | 605 | |
65fa5c61 | 606 | #endif |
32b3cf93 | 607 | |
65fa5c61 | 608 | #if USE_CACHE_DIGESTS |
96151fc9 | 609 | /// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request) |
829a9357 | 610 | int |
7d47d8e6 | 611 | refreshCheckDigest(const StoreEntry * entry, time_t delta) |
612 | { | |
65fa5c61 | 613 | int reason = refreshCheck(entry, |
c43405e7 | 614 | entry->mem_obj ? entry->mem_obj->request.getRaw() : nullptr, |
62e76326 | 615 | delta); |
5db6bf73 FC |
616 | ++ refreshCounts[rcCDigest].total; |
617 | ++ refreshCounts[rcCDigest].status[reason]; | |
65fa5c61 | 618 | return (reason < 200) ? 0 : 1; |
6018f0de | 619 | } |
65fa5c61 | 620 | #endif |
6018f0de | 621 | |
96151fc9 DD |
622 | /** |
623 | * Get the configured maximum caching time for objects with this URL | |
624 | * according to refresh_pattern. | |
625 | * | |
626 | * Used by http.cc when generating a upstream requests to ensure that | |
627 | * responses it is given are fresh enough to be worth caching. | |
628 | * | |
629 | * \retval pattern-max if there is a refresh_pattern matching the URL configured. | |
630 | * \retval REFRESH_DEFAULT_MAX if there are no explicit limits configured | |
631 | */ | |
48f44632 | 632 | time_t |
633 | getMaxAge(const char *url) | |
634 | { | |
8d9a8184 | 635 | const RefreshPattern *R; |
bf8fe701 | 636 | debugs(22, 3, "getMaxAge: '" << url << "'"); |
62e76326 | 637 | |
6018f0de | 638 | if ((R = refreshLimits(url))) |
62e76326 | 639 | return R->max; |
6018f0de | 640 | else |
62e76326 | 641 | return REFRESH_DEFAULT_MAX; |
48f44632 | 642 | } |
1c3e77cd | 643 | |
4fb971a0 AJ |
644 | static int |
645 | refreshCountsStatsEntry(StoreEntry * sentry, struct RefreshCounts &rc, int code, const char *desc) | |
646 | { | |
647 | storeAppendPrintf(sentry, "%6d\t%6.2f\t%s\n", rc.status[code], xpercent(rc.status[code], rc.total), desc); | |
648 | return rc.status[code]; | |
649 | } | |
62e76326 | 650 | |
4fb971a0 AJ |
651 | static void |
652 | refreshCountsStats(StoreEntry * sentry, struct RefreshCounts &rc) | |
829a9357 | 653 | { |
4fb971a0 AJ |
654 | if (!rc.total) |
655 | return; | |
cc7cfa8e | 656 | |
4fb971a0 | 657 | storeAppendPrintf(sentry, "\n\n%s histogram:\n", rc.proto); |
65fa5c61 | 658 | storeAppendPrintf(sentry, "Count\t%%Total\tCategory\n"); |
829a9357 | 659 | |
0ba77a3c AR |
660 | refreshCountsStatsEntry(sentry, rc, FRESH_REQUEST_MAX_STALE_ALL, "Fresh: request max-stale wildcard"); |
661 | refreshCountsStatsEntry(sentry, rc, FRESH_REQUEST_MAX_STALE_VALUE, "Fresh: request max-stale value"); | |
662 | refreshCountsStatsEntry(sentry, rc, FRESH_EXPIRES, "Fresh: expires time not reached"); | |
663 | refreshCountsStatsEntry(sentry, rc, FRESH_LMFACTOR_RULE, "Fresh: refresh_pattern last-mod factor percentage"); | |
664 | refreshCountsStatsEntry(sentry, rc, FRESH_MIN_RULE, "Fresh: refresh_pattern min value"); | |
665 | refreshCountsStatsEntry(sentry, rc, FRESH_OVERRIDE_EXPIRES, "Fresh: refresh_pattern override-expires"); | |
666 | refreshCountsStatsEntry(sentry, rc, FRESH_OVERRIDE_LASTMOD, "Fresh: refresh_pattern override-lastmod"); | |
667 | refreshCountsStatsEntry(sentry, rc, STALE_MUST_REVALIDATE, "Stale: response has must-revalidate"); | |
668 | refreshCountsStatsEntry(sentry, rc, STALE_RELOAD_INTO_IMS, "Stale: changed reload into IMS"); | |
669 | refreshCountsStatsEntry(sentry, rc, STALE_FORCED_RELOAD, "Stale: request has no-cache directive"); | |
670 | refreshCountsStatsEntry(sentry, rc, STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE, "Stale: age exceeds request max-age value"); | |
671 | refreshCountsStatsEntry(sentry, rc, STALE_EXPIRES, "Stale: expires time reached"); | |
672 | refreshCountsStatsEntry(sentry, rc, STALE_MAX_RULE, "Stale: refresh_pattern max age rule"); | |
673 | refreshCountsStatsEntry(sentry, rc, STALE_LMFACTOR_RULE, "Stale: refresh_pattern last-mod factor percentage"); | |
674 | refreshCountsStatsEntry(sentry, rc, STALE_DEFAULT, "Stale: by default"); | |
62e76326 | 675 | storeAppendPrintf(sentry, "\n"); |
829a9357 | 676 | } |
677 | ||
1c3e77cd | 678 | static void |
679 | refreshStats(StoreEntry * sentry) | |
680 | { | |
a5ea7751 AJ |
681 | // display per-rule counts of usage and tests |
682 | storeAppendPrintf(sentry, "\nRefresh pattern usage:\n\n"); | |
683 | storeAppendPrintf(sentry, " Used \tChecks \t%% Matches\tPattern\n"); | |
684 | for (const RefreshPattern *R = Config.Refresh; R; R = R->next) { | |
0fa036e3 | 685 | storeAppendPrintf(sentry, " %10" PRIu64 "\t%10" PRIu64 "\t%6.2f\t", |
a5ea7751 AJ |
686 | R->stats.matchCount, |
687 | R->stats.matchTests, | |
0fa036e3 AR |
688 | xpercent(R->stats.matchCount, R->stats.matchTests)); |
689 | PackableStream os(*sentry); | |
690 | R->printPattern(os); | |
691 | os << "\n"; | |
a5ea7751 AJ |
692 | } |
693 | ||
829a9357 | 694 | int i; |
695 | int total = 0; | |
696 | ||
697 | /* get total usage count */ | |
62e76326 | 698 | |
829a9357 | 699 | for (i = 0; i < rcCount; ++i) |
62e76326 | 700 | total += refreshCounts[i].total; |
829a9357 | 701 | |
702 | /* protocol usage histogram */ | |
703 | storeAppendPrintf(sentry, "\nRefreshCheck calls per protocol\n\n"); | |
62e76326 | 704 | |
829a9357 | 705 | storeAppendPrintf(sentry, "Protocol\t#Calls\t%%Calls\n"); |
62e76326 | 706 | |
829a9357 | 707 | for (i = 0; i < rcCount; ++i) |
62e76326 | 708 | storeAppendPrintf(sentry, "%10s\t%6d\t%6.2f\n", |
709 | refreshCounts[i].proto, | |
710 | refreshCounts[i].total, | |
711 | xpercent(refreshCounts[i].total, total)); | |
829a9357 | 712 | |
713 | /* per protocol histograms */ | |
714 | storeAppendPrintf(sentry, "\n\nRefreshCheck histograms for various protocols\n"); | |
62e76326 | 715 | |
829a9357 | 716 | for (i = 0; i < rcCount; ++i) |
4fb971a0 | 717 | refreshCountsStats(sentry, refreshCounts[i]); |
1c3e77cd | 718 | } |
719 | ||
0fa036e3 AR |
720 | const RegexPattern & |
721 | RefreshPattern::regex() const | |
722 | { | |
723 | assert(regex_); | |
724 | return *regex_; | |
725 | } | |
726 | ||
727 | void | |
728 | RefreshPattern::printPattern(std::ostream &os) const | |
729 | { | |
730 | if (regex_) | |
731 | regex_->print(os, nullptr); // refresh lines do not inherit line flags | |
732 | else | |
733 | os << "<none>"; | |
734 | } | |
735 | ||
736 | void | |
737 | RefreshPattern::printHead(std::ostream &os) const | |
738 | { | |
739 | printPattern(os); | |
740 | os << | |
741 | // these adjustments are safe: raw values were configured using integers | |
742 | ' ' << intmax_t(min/60) << // to minutes | |
743 | ' ' << intmax_t(100.0 * pct + 0.5) << '%' << // to percentage points | |
744 | ' ' << intmax_t(max/60); // to minutes | |
745 | } | |
746 | ||
5f5e883f FC |
747 | static void |
748 | refreshRegisterWithCacheManager(void) | |
749 | { | |
8822ebee | 750 | Mgr::RegisterAction("refresh", "Refresh Algorithm Statistics", refreshStats, 0, 1); |
5f5e883f FC |
751 | } |
752 | ||
1c3e77cd | 753 | void |
9bc73deb | 754 | refreshInit(void) |
1c3e77cd | 755 | { |
829a9357 | 756 | memset(refreshCounts, 0, sizeof(refreshCounts)); |
757 | refreshCounts[rcHTTP].proto = "HTTP"; | |
758 | refreshCounts[rcICP].proto = "ICP"; | |
65fa5c61 | 759 | #if USE_HTCP |
62e76326 | 760 | |
32b3cf93 | 761 | refreshCounts[rcHTCP].proto = "HTCP"; |
65fa5c61 | 762 | #endif |
62e76326 | 763 | |
cfa9f1cb | 764 | refreshCounts[rcStore].proto = "On Store"; |
65fa5c61 | 765 | #if USE_CACHE_DIGESTS |
62e76326 | 766 | |
829a9357 | 767 | refreshCounts[rcCDigest].proto = "Cache Digests"; |
65fa5c61 | 768 | #endif |
62e76326 | 769 | |
d120ed12 | 770 | refreshRegisterWithCacheManager(); |
1c3e77cd | 771 | } |
f53969cc | 772 |