]> git.ipfire.org Git - thirdparty/squid.git/blame - src/refresh.cc
Simplify appending SBuf to String (#2108)
[thirdparty/squid.git] / src / refresh.cc
CommitLineData
e4e6a8db 1/*
1f7b830e 2 * Copyright (C) 1996-2025 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 28typedef 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 44typedef 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 */
57enum {
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 76static 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 82static OBJH refreshStats;
8d9a8184 83static int refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age, const RefreshPattern * R, stale_flags * sf);
65fa5c61 84
0fa036e3 85static 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 91const RefreshPattern *
6018f0de 92refreshLimits(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 106static const RefreshPattern *
0fa036e3 107refreshFirstDotRule()
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 */
138static int
8d9a8184 139refreshStaleness(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) {
e806d767 147 debugs(22, 3, "FRESH: expires " << entry->expires << " > check_time " << check_time);
62e76326 148 return -1;
149 } else {
e806d767 150 debugs(22, 3, "STALE: expires " << entry->expires << " <= check_time " << check_time);
62e76326 151 return (check_time - entry->expires);
152 }
65fa5c61 153 }
62e76326 154
96151fc9
DD
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.
65fa5c61 158 if (age > R->max) {
e806d767 159 debugs(22, 3, "STALE: age " << age << " > max " << R->max);
1f848b2c 160 sf->max = true;
62e76326 161 return (age - R->max);
65fa5c61 162 }
62e76326 163
96151fc9 164 // 3. If there is a Last-Modified header, try the last-modified factor algorithm.
438b41ba
EB
165 const time_t lastmod_delta = entry->timestamp - entry->lastModified();
166 if (lastmod_delta > 0) {
96151fc9
DD
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.
62e76326 170 */
96151fc9
DD
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 " <<
86c63190 174 (100.0 * R->pct) << "% = " << stale_age << " sec freshness lifetime");
1f848b2c 175 sf->lmfactor = true;
62e76326 176
177 if (age >= stale_age) {
4a7a3d56 178 debugs(22, 3, "STALE: age " << age << " > stale_age " << stale_age);
62e76326 179 return (age - stale_age);
180 } else {
4a7a3d56 181 debugs(22, 3, "FRESH: age " << age << " <= stale_age " << stale_age);
62e76326 182 return -1;
183 }
65fa5c61 184 }
62e76326 185
96151fc9 186 // 4. If the entry is not as old as the minimum age in the refresh_pattern, it is FRESH.
9e4b5932 187 if (age < R->min) {
96151fc9 188 debugs(22, 3, "FRESH: age (" << age << " sec) is less than configured minimum (" << R->min << " sec)");
1f848b2c 189 sf->min = true;
62e76326 190 return -1;
65fa5c61 191 }
62e76326 192
96151fc9
DD
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.");
65fa5c61 195 return (age - R->min);
196}
197
96151fc9
DD
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)
829a9357 248 *
96151fc9
DD
249 * \note the store entry being examined is not necessarily cached (e.g. if
250 * this response is being evaluated for the first time)
e4e6a8db 251 */
829a9357 252static int
190154cf 253refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
e4e6a8db 254{
65fa5c61 255 time_t age = 0;
a207429f 256 time_t check_time = squid_curtime + delta;
65fa5c61 257 int staleness;
258 stale_flags sf;
62e76326 259
96151fc9 260 // get the URL of this entry, if there is one
851feda6
AJ
261 static const SBuf nilUri("<none>");
262 SBuf uri = nilUri;
9b5d1d21 263 if (entry->mem_obj)
c877c0bc 264 uri = entry->mem_obj->storeId();
7d47d8e6 265 else if (request)
851feda6 266 uri = request->effectiveRequestUri();
7d47d8e6 267
0cc6c016 268 debugs(22, 3, "checking freshness of " << *entry << " with URI: " << uri);
65fa5c61 269
96151fc9 270 // age is not necessarily the age now, but the age at the given check_time
65fa5c61 271 if (check_time > entry->timestamp)
62e76326 272 age = check_time - entry->timestamp;
273
9837567d 274 // XXX: what to do when age < 0 or counter overflow?
b2f01ec3 275 assert(age >= 0);
efd62b86 276
96151fc9
DD
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 */
851feda6 283 // XXX: performance regression. c_str() reallocates
0fa036e3 284 const RefreshPattern *R = (uri != nilUri) ? refreshLimits(uri.c_str()) : refreshFirstDotRule();
aee3523a 285 if (nullptr == R)
62e76326 286 R = &DefaultRefresh;
287
0fa036e3 288 debugs(22, 3, "Matched '" << *R << '\'');
65fa5c61 289
64f8c2cb 290 debugs(22, 3, "\tage:\t" << age);
62e76326 291
98cacedb 292 debugs(22, 3, "\tcheck_time:\t" << Time::FormatRfc1123(check_time));
62e76326 293
98cacedb 294 debugs(22, 3, "\tentry->timestamp:\t" << Time::FormatRfc1123(entry->timestamp));
65fa5c61 295
450fe1cb 296 if (request && !request->flags.ignoreCc) {
64f8c2cb 297 const HttpHdrCc *const cc = request->cache_control;
810d879f
EB
298 int minFresh = -1;
299 if (cc && cc->hasMinFresh(&minFresh)) {
64f8c2cb 300 debugs(22, 3, "\tage + min-fresh:\t" << age << " + " <<
77da1817 301 minFresh << " = " << age + minFresh);
64f8c2cb 302 debugs(22, 3, "\tcheck_time + min-fresh:\t" << check_time << " + "
422acb7f 303 << minFresh << " = " <<
98cacedb 304 Time::FormatRfc1123(check_time + minFresh));
422acb7f
FC
305 age += minFresh;
306 check_time += minFresh;
64f8c2cb
AR
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
66d51f4f 316 const auto reply = entry->hasFreshestReply(); // may be nil
caf65351 317
65fd3895 318 // stale-if-error requires any failure be passed thru when its period is over.
810d879f 319 int staleIfError = -1;
caf65351 320 if (request && reply && reply->cache_control &&
810d879f
EB
321 reply->cache_control->hasStaleIfError(&staleIfError) &&
322 staleIfError < staleness) {
65fd3895 323
96151fc9 324 debugs(22, 3, "stale-if-error period expired. Will produce error if validation fails.");
e857372a 325 request->flags.failOnValidationError = true;
65fd3895
AJ
326 }
327
96151fc9
DD
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 */
fa83b766
EB
333 const bool revalidateAlways = EBIT_TEST(entry->flags, ENTRY_REVALIDATE_ALWAYS);
334 if (revalidateAlways || (staleness > -1 &&
d267fa9c 335 EBIT_TEST(entry->flags, ENTRY_REVALIDATE_STALE))) {
fa83b766 336 debugs(22, 3, "YES: Must revalidate stale object (origin set " <<
d267fa9c
SM
337 (revalidateAlways ? "no-cache or private" :
338 "must-revalidate, proxy-revalidate or s-maxage") << ")");
5b1d04af 339 if (request)
e857372a 340 request->flags.failOnValidationError = true;
62e76326 341 return STALE_MUST_REVALIDATE;
65fa5c61 342 }
62e76326 343
829a9357 344 /* request-specific checks */
450fe1cb 345 if (request && !request->flags.ignoreCc) {
62e76326 346 HttpHdrCc *cc = request->cache_control;
4c3ef9b2 347
96151fc9
DD
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 */
45e5102d 352 if (request->flags.ims && (R->flags.refresh_ims || Config.onoff.refresh_all_ims)) {
96151fc9
DD
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)");
4c3ef9b2 355 return STALE_FORCED_RELOAD;
356 }
357
626096be 358#if USE_HTTP_VIOLATIONS
96151fc9 359 /* Normally a client reload request ("Cache-Control: no-cache" or "Pragma: no-cache")
2f8abb64 360 * means we must treat this response as STALE and fetch a new one.
96151fc9
DD
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 }
62e76326 399 }
9f60cfdf 400#endif
96151fc9
DD
401
402 // Check the Cache-Control client request header
aee3523a 403 if (nullptr != cc) {
96151fc9
DD
404
405 // max-age directive
810d879f
EB
406 int maxAge = -1;
407 if (cc->hasMaxAge(&maxAge)) {
caf65351 408
8311b837 409 // RFC 8246: reply contains CC:immutable then ignore client CC:max-age=N
810d879f
EB
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'");
caf65351 412
626096be 413#if USE_HTTP_VIOLATIONS
18fe96be 414 // Ignore of client "Cache-Control: max-age=0" header
810d879f 415 } else if (R->flags.ignore_reload && maxAge == 0) {
96151fc9 416 debugs(22, 3, "MAYBE: Ignoring client reload request - trying to serve from cache (ignore-reload option)");
528b2c61 417#endif
caf65351 418
18fe96be 419 // Honour client "Cache-Control: max-age=x" header
810d879f
EB
420 } else if (age > maxAge || maxAge == 0) {
421 debugs(22, 3, "YES: Revalidating object - client 'Cache-Control: max-age=" << maxAge << "'");
caf65351 422 return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE;
62e76326 423 }
424 }
425
96151fc9 426 // max-stale directive
810d879f
EB
427 int maxStale = -1;
428 if (cc->hasMaxStale(&maxStale) && staleness > -1) {
429 if (maxStale==HttpHdrCc::MAX_STALE_ANY) {
96151fc9 430 debugs(22, 3, "NO: Client accepts a stale response of any age - 'Cache-Control: max-stale'");
62e76326 431 return FRESH_REQUEST_MAX_STALE_ALL;
810d879f
EB
432 } else if (staleness < maxStale) {
433 debugs(22, 3, "NO: Client accepts a stale response - 'Cache-Control: max-stale=" << maxStale << "'");
62e76326 434 return FRESH_REQUEST_MAX_STALE_VALUE;
435 }
436 }
437 }
48f44632 438 }
62e76326 439
96151fc9 440 // If the object is fresh, return the right FRESH_ code
65fa5c61 441 if (-1 == staleness) {
96151fc9 442 debugs(22, 3, "Object isn't stale..");
ed1de692 443 if (sf.expires) {
96151fc9 444 debugs(22, 3, "returning FRESH_EXPIRES");
62e76326 445 return FRESH_EXPIRES;
26ac0430 446 }
62e76326 447
448 assert(!sf.max);
449
ed1de692 450 if (sf.lmfactor) {
96151fc9 451 debugs(22, 3, "returning FRESH_LMFACTOR_RULE");
62e76326 452 return FRESH_LMFACTOR_RULE;
26ac0430 453 }
62e76326 454
455 assert(sf.min);
456
96151fc9 457 debugs(22, 3, "returning FRESH_MIN_RULE");
62e76326 458 return FRESH_MIN_RULE;
1dfa1d81 459 }
62e76326 460
65fa5c61 461 /*
462 * At this point the response is stale, unless one of
542c4d60 463 * the override options kicks in.
570d3f75 464 * NOTE: max-stale config blocks the overrides.
65fa5c61 465 */
570d3f75 466 int max_stale = (R->max_stale >= 0 ? R->max_stale : Config.maxStale);
60c3d5b7 467 if ( max_stale >= 0 && staleness > max_stale) {
96151fc9 468 debugs(22, 3, "YES: refresh_pattern max-stale=N limit from squid.conf");
570d3f75 469 if (request)
e857372a 470 request->flags.failOnValidationError = true;
570d3f75
AJ
471 return STALE_MAX_STALE;
472 }
473
65fa5c61 474 if (sf.expires) {
626096be 475#if USE_HTTP_VIOLATIONS
62e76326 476
477 if (R->flags.override_expire && age < R->min) {
96151fc9 478 debugs(22, 3, "NO: Serving from cache - even though explicit expiry has passed, we enforce Min value (override-expire option)");
62e76326 479 return FRESH_OVERRIDE_EXPIRES;
480 }
481
65fa5c61 482#endif
0cc6c016 483 debugs(22, 3, "returning STALE_EXPIRES");
62e76326 484 return STALE_EXPIRES;
e4e6a8db 485 }
62e76326 486
0cc6c016
AR
487 if (sf.max) {
488 debugs(22, 3, "returning STALE_MAX_RULE");
62e76326 489 return STALE_MAX_RULE;
0cc6c016 490 }
62e76326 491
65fa5c61 492 if (sf.lmfactor) {
626096be 493#if USE_HTTP_VIOLATIONS
62e76326 494 if (R->flags.override_lastmod && age < R->min) {
96151fc9 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)");
62e76326 496 return FRESH_OVERRIDE_LASTMOD;
497 }
65fa5c61 498#endif
96151fc9 499 debugs(22, 3, "YES: L-M factor says the object is stale'");
62e76326 500 return STALE_LMFACTOR_RULE;
e4e6a8db 501 }
62e76326 502
96151fc9 503 debugs(22, 3, "returning STALE_DEFAULT");
65fa5c61 504 return STALE_DEFAULT;
e4e6a8db 505}
48f44632 506
96151fc9
DD
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 */
517bool
cfa9f1cb 518refreshIsCachable(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.
26ac0430
AJ
523 * minimum_expiry_time seconds delta (defaults to 60 seconds), to
524 * avoid objects which expire almost immediately, and which can't
6a2f3fcf 525 * be refreshed.
ee989932
AR
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.
cfa9f1cb 530 */
aee3523a 531 int reason = refreshCheck(entry, nullptr, Config.minimum_expiry_time);
5db6bf73
FC
532 ++ refreshCounts[rcStore].total;
533 ++ refreshCounts[rcStore].status[reason];
62e76326 534
451c8350 535 if (reason < STALE_MUST_REVALIDATE)
62e76326 536 /* Does not need refresh. This is certainly cachable */
96151fc9 537 return true;
62e76326 538
438b41ba
EB
539 if (entry->lastModified() < 0)
540 /* We should know entry's modification time to do a refresh */
96151fc9 541 return false;
62e76326 542
aee3523a 543 if (entry->mem_obj == nullptr)
62e76326 544 /* no mem_obj? */
96151fc9 545 return true;
62e76326 546
66d51f4f 547 if (entry->mem_obj->baseReply().content_length == 0)
62e76326 548 /* No use refreshing (caching?) 0 byte objects */
96151fc9 549 return false;
62e76326 550
cfa9f1cb 551 /* This seems to be refreshable. Cache it */
96151fc9 552 return true;
cfa9f1cb 553}
554
bcfba8bd
AR
555/// whether reply is stale if it is a hit
556static bool
557refreshIsStaleIfHit(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
96151fc9
DD
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
ee989932
AR
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?
96151fc9 580 */
829a9357 581int
190154cf 582refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
7d47d8e6 583{
65fa5c61 584 int reason = refreshCheck(entry, request, 0);
5db6bf73
FC
585 ++ refreshCounts[rcHTTP].total;
586 ++ refreshCounts[rcHTTP].status[reason];
450fe1cb 587 request->flags.staleIfHit = refreshIsStaleIfHit(reason);
bcfba8bd 588 return (Config.onoff.offline || reason < 200) ? 0 : 1;
829a9357 589}
590
96151fc9 591/// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
829a9357 592int
190154cf 593refreshCheckICP(const StoreEntry * entry, HttpRequest * request)
7d47d8e6 594{
65fa5c61 595 int reason = refreshCheck(entry, request, 30);
5db6bf73
FC
596 ++ refreshCounts[rcICP].total;
597 ++ refreshCounts[rcICP].status[reason];
65fa5c61 598 return (reason < 200) ? 0 : 1;
829a9357 599}
600
65fa5c61 601#if USE_HTCP
96151fc9 602/// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
32b3cf93 603int
190154cf 604refreshCheckHTCP(const StoreEntry * entry, HttpRequest * request)
32b3cf93 605{
65fa5c61 606 int reason = refreshCheck(entry, request, 10);
5db6bf73
FC
607 ++ refreshCounts[rcHTCP].total;
608 ++ refreshCounts[rcHTCP].status[reason];
65fa5c61 609 return (reason < 200) ? 0 : 1;
32b3cf93 610}
62e76326 611
65fa5c61 612#endif
32b3cf93 613
65fa5c61 614#if USE_CACHE_DIGESTS
96151fc9 615/// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
829a9357 616int
7d47d8e6 617refreshCheckDigest(const StoreEntry * entry, time_t delta)
618{
65fa5c61 619 int reason = refreshCheck(entry,
c43405e7 620 entry->mem_obj ? entry->mem_obj->request.getRaw() : nullptr,
62e76326 621 delta);
5db6bf73
FC
622 ++ refreshCounts[rcCDigest].total;
623 ++ refreshCounts[rcCDigest].status[reason];
65fa5c61 624 return (reason < 200) ? 0 : 1;
6018f0de 625}
65fa5c61 626#endif
6018f0de 627
96151fc9
DD
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 */
48f44632 638time_t
639getMaxAge(const char *url)
640{
8d9a8184 641 const RefreshPattern *R;
bf8fe701 642 debugs(22, 3, "getMaxAge: '" << url << "'");
62e76326 643
6018f0de 644 if ((R = refreshLimits(url)))
62e76326 645 return R->max;
6018f0de 646 else
62e76326 647 return REFRESH_DEFAULT_MAX;
48f44632 648}
1c3e77cd 649
4fb971a0
AJ
650static int
651refreshCountsStatsEntry(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}
62e76326 656
4fb971a0
AJ
657static void
658refreshCountsStats(StoreEntry * sentry, struct RefreshCounts &rc)
829a9357 659{
4fb971a0
AJ
660 if (!rc.total)
661 return;
cc7cfa8e 662
4fb971a0 663 storeAppendPrintf(sentry, "\n\n%s histogram:\n", rc.proto);
65fa5c61 664 storeAppendPrintf(sentry, "Count\t%%Total\tCategory\n");
829a9357 665
0ba77a3c
AR
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");
62e76326 681 storeAppendPrintf(sentry, "\n");
829a9357 682}
683
1c3e77cd 684static void
685refreshStats(StoreEntry * sentry)
686{
a5ea7751
AJ
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) {
0fa036e3 691 storeAppendPrintf(sentry, " %10" PRIu64 "\t%10" PRIu64 "\t%6.2f\t",
a5ea7751
AJ
692 R->stats.matchCount,
693 R->stats.matchTests,
0fa036e3
AR
694 xpercent(R->stats.matchCount, R->stats.matchTests));
695 PackableStream os(*sentry);
696 R->printPattern(os);
697 os << "\n";
a5ea7751
AJ
698 }
699
829a9357 700 int i;
701 int total = 0;
702
703 /* get total usage count */
62e76326 704
829a9357 705 for (i = 0; i < rcCount; ++i)
62e76326 706 total += refreshCounts[i].total;
829a9357 707
708 /* protocol usage histogram */
709 storeAppendPrintf(sentry, "\nRefreshCheck calls per protocol\n\n");
62e76326 710
829a9357 711 storeAppendPrintf(sentry, "Protocol\t#Calls\t%%Calls\n");
62e76326 712
829a9357 713 for (i = 0; i < rcCount; ++i)
62e76326 714 storeAppendPrintf(sentry, "%10s\t%6d\t%6.2f\n",
715 refreshCounts[i].proto,
716 refreshCounts[i].total,
717 xpercent(refreshCounts[i].total, total));
829a9357 718
719 /* per protocol histograms */
720 storeAppendPrintf(sentry, "\n\nRefreshCheck histograms for various protocols\n");
62e76326 721
829a9357 722 for (i = 0; i < rcCount; ++i)
4fb971a0 723 refreshCountsStats(sentry, refreshCounts[i]);
1c3e77cd 724}
725
0fa036e3
AR
726const RegexPattern &
727RefreshPattern::regex() const
728{
729 assert(regex_);
730 return *regex_;
731}
732
733void
734RefreshPattern::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
742void
743RefreshPattern::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
5f5e883f
FC
753static void
754refreshRegisterWithCacheManager(void)
755{
8822ebee 756 Mgr::RegisterAction("refresh", "Refresh Algorithm Statistics", refreshStats, 0, 1);
5f5e883f
FC
757}
758
1c3e77cd 759void
9bc73deb 760refreshInit(void)
1c3e77cd 761{
829a9357 762 memset(refreshCounts, 0, sizeof(refreshCounts));
763 refreshCounts[rcHTTP].proto = "HTTP";
764 refreshCounts[rcICP].proto = "ICP";
65fa5c61 765#if USE_HTCP
62e76326 766
32b3cf93 767 refreshCounts[rcHTCP].proto = "HTCP";
65fa5c61 768#endif
62e76326 769
cfa9f1cb 770 refreshCounts[rcStore].proto = "On Store";
65fa5c61 771#if USE_CACHE_DIGESTS
62e76326 772
829a9357 773 refreshCounts[rcCDigest].proto = "Cache Digests";
65fa5c61 774#endif
62e76326 775
d120ed12 776 refreshRegisterWithCacheManager();
1c3e77cd 777}
f53969cc 778