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