cache_util.c revision e659202cdee59ca56484d1e0d0d7d68864fc60ca
842ae4bd224140319ae7feec1872b93dfd491143fielding/* Licensed to the Apache Software Foundation (ASF) under one or more
842ae4bd224140319ae7feec1872b93dfd491143fielding * contributor license agreements. See the NOTICE file distributed with
842ae4bd224140319ae7feec1872b93dfd491143fielding * this work for additional information regarding copyright ownership.
842ae4bd224140319ae7feec1872b93dfd491143fielding * The ASF licenses this file to You under the Apache License, Version 2.0
842ae4bd224140319ae7feec1872b93dfd491143fielding * (the "License"); you may not use this file except in compliance with
842ae4bd224140319ae7feec1872b93dfd491143fielding * the License. You may obtain a copy of the License at
ce9621257ef9e54c1bbe5ad8a5f445a1f211c2dcnd * Unless required by applicable law or agreed to in writing, software
ce9621257ef9e54c1bbe5ad8a5f445a1f211c2dcnd * distributed under the License is distributed on an "AS IS" BASIS,
ce9621257ef9e54c1bbe5ad8a5f445a1f211c2dcnd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
ce9621257ef9e54c1bbe5ad8a5f445a1f211c2dcnd * See the License for the specific language governing permissions and
ce9621257ef9e54c1bbe5ad8a5f445a1f211c2dcnd * limitations under the License.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding/* -------------------------------------------------------------- */
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingextern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
dc80439e9fba60c753cd145cb6799409ffea9b71ronald/* Determine if "url" matches the hostname, scheme and port and path
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * in "filter". All but the path comparisons are case-insensitive.
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingstatic int uri_meets_conditions(apr_uri_t filter, int pathlen, apr_uri_t url)
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* Compare the hostnames */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding else if (strcasecmp(filter.hostname, url.hostname)) {
dc80439e9fba60c753cd145cb6799409ffea9b71ronald /* Compare the schemes */
dc80439e9fba60c753cd145cb6799409ffea9b71ronald /* Compare the ports */
b980ad7fdc218b4855cde9f75a747527f50c554dwrowe /* NOTE: ap_port_of_scheme will return 0 if given NULL input */
b980ad7fdc218b4855cde9f75a747527f50c554dwrowe else if (filter.port != apr_uri_port_of_scheme(url.scheme)) {
032b8a34c3911bbc5ad5385ca40af65af273bff9wrowe if (apr_uri_port_of_scheme(filter.scheme) == url.port) {
e33b627b40578d0166fdb79ce0487f9e46586befgstein /* For HTTP caching purposes, an empty (NULL) path is equivalent to
e33b627b40578d0166fdb79ce0487f9e46586befgstein * a single "/" path. RFCs 3986/2396
e33b627b40578d0166fdb79ce0487f9e46586befgstein /* Url has met all of the filter conditions so far, determine
322b350d0f1ac750b112ec15481a33efc92d182cjerenkrantz * if the paths match.
8ec8f1c8f0f37ca3f5ebb0e0b491dd07481dccbfronaldCACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r,
9db34d31d4df0b29d128ecd724cd177905108b0frbb /* loop through all the cacheenable entries */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* Fetch from global config and add to the list. */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding provider = ap_lookup_provider(CACHE_PROVIDER_GROUP, ent[i].type,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* Log an error! */
e0fe4de2016336428729a620ac0034cd1198ad7awrowe newp = apr_pcalloc(r->pool, sizeof(cache_provider_list));
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* then loop through all the cachedisable entries
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * Looking for urls that contain the full cachedisable url and possibly
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * This means we are disabling cachedisable url and below...
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* Stop searching now. */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding/* do a HTTP/1.1 age calculation */
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingCACHE_DECLARE(apr_int64_t) ap_cache_current_age(cache_info *info,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding apr_time_t apparent_age, corrected_received_age, response_delay,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding apparent_age = MAX(0, info->response_time - info->date);
dc80439e9fba60c753cd145cb6799409ffea9b71ronald corrected_received_age = MAX(apparent_age, age_value_usec);
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron response_delay = info->response_time - info->request_time;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding corrected_initial_age = corrected_received_age + response_delay;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding current_age = corrected_initial_age + resident_time;
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * Try obtain a cache wide lock on the given cache key.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * If we return APR_SUCCESS, we obtained the lock, and we are clear to
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * proceed to the backend. If we return APR_EEXISTS, then the lock is
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * already locked, someone else has gone to refresh the backend data
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * already, so we must return stale data with a warning in the mean
dc80439e9fba60c753cd145cb6799409ffea9b71ronald * time. If we return anything else, then something has gone pear
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * shaped, and we allow the request through to the backend regardless.
26dfa083a1662d57ba7cc410eec4e0696b9be469wrowe * This lock is created from the request pool, meaning that should
26dfa083a1662d57ba7cc410eec4e0696b9be469wrowe * something go wrong and the lock isn't deleted on return of the
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * request headers from the backend for whatever reason, at worst the
29c30db45f6a469017e16b606611e460cc1a1f2caaron * lock will be cleaned up when the request dies or finishes.
97d20d37d21b8d427a920e211858172f0a82427epoirier * If something goes truly bananas and the lock isn't deleted when the
97d20d37d21b8d427a920e211858172f0a82427epoirier * request dies, the lock will be trashed when its max-age is reached,
97d20d37d21b8d427a920e211858172f0a82427epoirier * or when a request arrives containing a Cache-Control: no-cache. At
dc80439e9fba60c753cd145cb6799409ffea9b71ronald * no point is it possible for this lock to permanently deny access to
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * the backend.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaronCACHE_DECLARE(apr_status_t) ap_cache_try_lock(cache_server_conf *conf,
dc80439e9fba60c753cd145cb6799409ffea9b71ronald const char *lockname;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding const char *path;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* no locks configured, leave */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* lock already obtained earlier? if so, success */
97d20d37d21b8d427a920e211858172f0a82427epoirier apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool);
dc80439e9fba60c753cd145cb6799409ffea9b71ronald /* create the key if it doesn't exist */
dc80439e9fba60c753cd145cb6799409ffea9b71ronald /* create a hashed filename from the key, and save it for later */
29c30db45f6a469017e16b606611e460cc1a1f2caaron lockname = ap_cache_generate_name(r->pool, 0, 0, key);
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* lock files represent discrete just-went-stale URLs "in flight", so
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * we support a simple two level directory structure, more is overkill.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* make the directories */
e1753aabf5df187b5b04e72a958af4b65b1a125daaron path = apr_pstrcat(r->pool, conf->lockpath, dir, NULL);
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (APR_SUCCESS != (status = apr_dir_make_recursive(path,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server,
12901074f5d6b36d08be84d8637b6f2c21e0da26trawick "Could not create a cache lock directory: %s",
1ccd992d37d62c8cb2056126f2234f64ec189bfddougm lockname = apr_pstrcat(r->pool, path, "/", lockname, NULL);
afd0a335375c636605c8625b0d5755dd2408be2btrawick apr_pool_userdata_set(lockname, CACHE_LOCKNAME_KEY, NULL, r->pool);
afd0a335375c636605c8625b0d5755dd2408be2btrawick /* is an existing lock file too old? */
e4c4fcc82268e0192db234c74a6db784b879fffdrbb if (!APR_STATUS_IS_ENOENT(status) && APR_SUCCESS != status) {
9d75c00eea5e840de5ab662f1163014932d9ee78wrowe ap_log_error(APLOG_MARK, APLOG_ERR, APR_EEXIST, r->server,
e8f95a682820a599fe41b22977010636be5c2717jim "Could not stat a cache lock file: %s",
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (APR_SUCCESS == status && ((now - finfo.mtime) > conf->lockmaxage)
e1753aabf5df187b5b04e72a958af4b65b1a125daaron ap_log_error(APLOG_MARK, APLOG_INFO, status, r->server,
e1753aabf5df187b5b04e72a958af4b65b1a125daaron "Cache lock file for '%s' too old, removing: %s",
dc80439e9fba60c753cd145cb6799409ffea9b71ronald /* try obtain a lock on the file */
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron if (APR_SUCCESS == (status = apr_file_open(&lockfile, lockname,
dc80439e9fba60c753cd145cb6799409ffea9b71ronald apr_pool_userdata_set(lockfile, CACHE_LOCKFILE_KEY, NULL, r->pool);
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * Remove the cache lock, if present.
97d20d37d21b8d427a920e211858172f0a82427epoirier * First, try to close the file handle, whose delete-on-close should
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * kill the file. Otherwise, just delete the file by name.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * If no lock name has yet been calculated, do the calculation of the
97d20d37d21b8d427a920e211858172f0a82427epoirier * lock name first before trying to delete the file.
97d20d37d21b8d427a920e211858172f0a82427epoirier * If an optional bucket brigade is passed, the lock will only be
97d20d37d21b8d427a920e211858172f0a82427epoirier * removed if the bucket brigade contains an EOS bucket.
97d20d37d21b8d427a920e211858172f0a82427epoirierCACHE_DECLARE(apr_status_t) ap_cache_remove_lock(cache_server_conf *conf,
97d20d37d21b8d427a920e211858172f0a82427epoirier request_rec *r, char *key, apr_bucket_brigade *bb) {
97d20d37d21b8d427a920e211858172f0a82427epoirier const char *lockname;
97d20d37d21b8d427a920e211858172f0a82427epoirier /* no locks configured, leave */
97d20d37d21b8d427a920e211858172f0a82427epoirier /* no eos found in brigade, don't delete anything just yet,
97d20d37d21b8d427a920e211858172f0a82427epoirier * we are not done.
97d20d37d21b8d427a920e211858172f0a82427epoirier apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool);
97d20d37d21b8d427a920e211858172f0a82427epoirier apr_pool_userdata_get(&dummy, CACHE_LOCKNAME_KEY, r->pool);
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron const char *path;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* create the key if it doesn't exist */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* create a hashed filename from the key, and save it for later */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding lockname = ap_cache_generate_name(r->pool, 0, 0, key);
97d20d37d21b8d427a920e211858172f0a82427epoirier /* lock files represent discrete just-went-stale URLs "in flight", so
29c30db45f6a469017e16b606611e460cc1a1f2caaron * we support a simple two level directory structure, more is overkill.
dc80439e9fba60c753cd145cb6799409ffea9b71ronald lockname = apr_pstrcat(r->pool, conf->lockpath, dir, "/", lockname, NULL);
dc80439e9fba60c753cd145cb6799409ffea9b71ronaldCACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale;
97d20d37d21b8d427a920e211858172f0a82427epoirier const char *pragma;
dc80439e9fba60c753cd145cb6799409ffea9b71ronald (cache_server_conf *)ap_get_module_config(r->server->module_config,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * We now want to check if our cached data is still fresh. This depends
97d20d37d21b8d427a920e211858172f0a82427epoirier * on a few things, in this order:
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache in
97d20d37d21b8d427a920e211858172f0a82427epoirier * either the request or the cached response means that we must
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * revalidate the request unconditionally, overriding any expiration
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * mechanism. It's equivalent to max-age=0,must-revalidate.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * - RFC2616 14.32 Pragma: no-cache This is treated the same as
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * Cache-Control: no-cache.
97d20d37d21b8d427a920e211858172f0a82427epoirier * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * proxy-revalidate if the max-stale request header exists, modify the
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * stale calculations below so that an object can be at most <max-stale>
a6b9ed64fdf548c61de9714e2cfb999ec59d149cgstein * seconds stale before we request a revalidation, _UNLESS_ a
a6b9ed64fdf548c61de9714e2cfb999ec59d149cgstein * must-revalidate or proxy-revalidate cached response header exists to
a6b9ed64fdf548c61de9714e2cfb999ec59d149cgstein * stop us doing this.
e1753aabf5df187b5b04e72a958af4b65b1a125daaron * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * maximum age an object can be before it is considered stale. This
e1753aabf5df187b5b04e72a958af4b65b1a125daaron * directive has the effect of proxy|must revalidate, which in turn means
e1753aabf5df187b5b04e72a958af4b65b1a125daaron * simple ignore any max-stale setting.
e1753aabf5df187b5b04e72a958af4b65b1a125daaron * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both
e1753aabf5df187b5b04e72a958af4b65b1a125daaron * requests and responses. If both are specified, the smaller of the two
e1753aabf5df187b5b04e72a958af4b65b1a125daaron * takes priority.
e1753aabf5df187b5b04e72a958af4b65b1a125daaron * - RFC2616 14.21 Expires: if this request header exists in the cached
03a0fe8ed13b5883d43e40ad99c877a24cb97bfbbnicholes * entity, and it's value is in the past, it has expired.
e1753aabf5df187b5b04e72a958af4b65b1a125daaron /* This value comes from the client's initial request. */
e1753aabf5df187b5b04e72a958af4b65b1a125daaron cc_req = apr_table_get(r->headers_in, "Cache-Control");
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding || ap_cache_liststr(NULL, cc_req, "no-cache", NULL)) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* Treat as stale, causing revalidation */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding "Incoming request is asking for a uncached version of "
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding "%s, but we know better and are ignoring it",
97d20d37d21b8d427a920e211858172f0a82427epoirier /* These come from the cached entity. */
066877f1a045103acfdd376d48cdd473c33f409bdougm cc_cresp = apr_table_get(h->resp_hdrs, "Cache-Control");
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (ap_cache_liststr(NULL, cc_cresp, "no-cache", NULL)) {
1ccd992d37d62c8cb2056126f2234f64ec189bfddougm * The cached entity contained Cache-Control: no-cache, so treat as
dc80439e9fba60c753cd145cb6799409ffea9b71ronald * stale causing revalidation
97d20d37d21b8d427a920e211858172f0a82427epoirier /* calculate age of object */
97d20d37d21b8d427a920e211858172f0a82427epoirier age = ap_cache_current_age(info, age_c, r->request_time);
97d20d37d21b8d427a920e211858172f0a82427epoirier /* extract s-maxage */
97d20d37d21b8d427a920e211858172f0a82427epoirier if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "s-maxage", &val)
29c30db45f6a469017e16b606611e460cc1a1f2caaron /* extract max-age from request */
29c30db45f6a469017e16b606611e460cc1a1f2caaron && cc_req && ap_cache_liststr(r->pool, cc_req, "max-age", &val)
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* extract max-age from response */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "max-age", &val)
1ccd992d37d62c8cb2056126f2234f64ec189bfddougm * if both maxage request and response, the smaller one takes priority
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* extract max-stale */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (cc_req && ap_cache_liststr(r->pool, cc_req, "max-stale", &val)) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * If no value is assigned to max-stale, then the client is willing
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * to accept a stale response of any age (RFC2616 14.9.3). We will
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * set it to one year in this case as this situation is somewhat
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * similar to a "never expires" Expires header (RFC2616 14.21)
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * which is set to a date one year from the time the response is
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * sent in this case.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* extract min-fresh */
322b350d0f1ac750b112ec15481a33efc92d182cjerenkrantz && cc_req && ap_cache_liststr(r->pool, cc_req, "min-fresh", &val)
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz /* override maxstale if must-revalidate or proxy-revalidate */
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz /* handle expiration */
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz if (((smaxage != -1) && (age < (smaxage - minfresh))) ||
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz ((maxage != -1) && (age < (maxage + maxstale - minfresh))) ||
6e45872b4a23d493887830d82a2759b4c00b10b2wsanchez (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) {
941fcca87a4607a388e88cff3fd0cdefc29bb81cjerenkrantz warn_head = apr_table_get(h->resp_hdrs, "Warning");
6e45872b4a23d493887830d82a2759b4c00b10b2wsanchez /* it's fresh darlings... */
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz /* set age header on response */
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz apr_psprintf(r->pool, "%lu", (unsigned long)age));
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz /* add warning if maxstale overrode freshness calculation */
bdfba727693ab86e9914ca90af68e62896946755jerenkrantz (apr_time_sec(info->expire - info->date)) > age))) {
322b350d0f1ac750b112ec15481a33efc92d182cjerenkrantz /* make sure we don't stomp on a previous warning */
322b350d0f1ac750b112ec15481a33efc92d182cjerenkrantz ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding "110 Response is stale");
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * If none of Expires, Cache-Control: max-age, or Cache-Control:
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * s-maxage appears in the response, and the response header age
54062d8f070826e448822c315895f1ea2b9414adwrowe * calculated is more than 24 hours add the warning 113
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron /* Make sure we don't stomp on a previous warning, and don't dup
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * a 113 marning that is already present. Also, make sure to add
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * the new warning to the correct *headers_out location.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron ((warn_head != NULL) && (ap_strstr_c(warn_head, "113") == NULL))) {
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron "113 Heuristic expiration");
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * At this point we are stale, but: if we are under load, we may let
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * a significant number of stale requests through before the first
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * stale request successfully revalidates itself, causing a sudden
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * unexpected thundering herd which in turn brings angst and drama.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * We want the first stale request to go through as normal. But the
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * second and subsequent request, we must pretend to be fresh until
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * the first request comes back with either new content or confirmation
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * that the stale content is still fresh.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * To achieve this, we create a very simple file based lock based on
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * the key of the cached object. We attempt to open the lock file with
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * exclusive write access. If we succeed, woohoo! we're first, and we
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * follow the stale path to the backend server. If we fail, oh well,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * we follow the fresh path, and avoid being a thundering herd.
e8f95a682820a599fe41b22977010636be5c2717jim * The lock lives only as long as the stale request that went on ahead.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * If the request succeeds, the lock is deleted. If the request fails,
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * the lock is deleted, and another request gets to make a new lock
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * and try again.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * At any time, a request marked "no-cache" will force a refresh,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * ignoring the lock, ensuring an extended lockout is impossible.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * A lock that exceeds a maximum age will be deleted, and another
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * request gets to make a new lock and try again.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding status = ap_cache_try_lock(conf, r, (char *)h->cache_obj->key);
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* we obtained a lock, follow the stale path */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding "Cache lock obtained for stale cached URL, "
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding "revalidating entry: %s",
12901074f5d6b36d08be84d8637b6f2c21e0da26trawick /* lock already exists, return stale data anyway, with a warning */
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron "Cache already locked for stale cached URL, "
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron "pretend it is fresh: %s",
dc80439e9fba60c753cd145cb6799409ffea9b71ronald "110 Response is stale");
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* some other error occurred, just treat the object as stale */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding ap_log_error(APLOG_MARK, APLOG_DEBUG, status, r->server,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding "Attempt to obtain a cache lock for stale "
dc80439e9fba60c753cd145cb6799409ffea9b71ronald "cached URL failed, revalidating entry anyway: %s",
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * list is a comma-separated list of case-insensitive tokens, with
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * optional whitespace around the tokens.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * The return returns 1 if the token val is found in the list, or 0
dc80439e9fba60c753cd145cb6799409ffea9b71ronald * otherwise.
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingCACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list,
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding const char *next;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* skip whitespace and commas to find the start of the next key */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding while (*next && (apr_isspace(*next) || (*next == ','))) {
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron /* this field matches the key (though it might just be
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * a prefix match, so make sure the match is followed
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * by either a space or an equals sign)
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron /* valid match */
322b350d0f1ac750b112ec15481a33efc92d182cjerenkrantz /* skip to the next field */
090a954a1fe65b29a6f4a696f0136ef12ea0f1b1rbb/* return each comma separated token, one at a time */
e8f95a682820a599fe41b22977010636be5c2717jimCACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list,
090a954a1fe65b29a6f4a696f0136ef12ea0f1b1rbb const char **str)
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding const char *s;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (s != NULL) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding ; /* noop */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * Converts apr_time_t expressed as hex digits to
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * a true apr_time_t.
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingCACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x)
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding for (i = 0, j = 0; i < sizeof(j) * 2; i++) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * Converts apr_time_t to apr_time_t expressed as hex digits.
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingCACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y)
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingstatic void cache_hash(const char *it, char *val, int ndepth, int nlength)
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding int i, k, d;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding unsigned int x;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron apr_md5_update(&context, (const unsigned char *) it, strlen(it));
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* encode 128 bits as 22 characters, using a modified uuencoding
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron /* one byte left */
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron /* now split into directory levels */
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding for (i = k = d = 0; d < ndepth; ++d) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingCACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels,
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * Create a new table consisting of those elements from an
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * headers table that are allowed to be stored in a cache.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaronCACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool,
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron /* Short circuit the common case that there are not
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * (yet) any headers populated.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding if (t == NULL) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding /* Make a copy of the headers, and remove from
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * the copy any hop-by-hop headers, as defined in Section
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * 13.5.1 of RFC 2616
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding apr_table_unset(headers_out, "Proxy-Authorization");
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding conf = (cache_server_conf *)ap_get_module_config(s->module_config,
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron /* Remove the user defined headers set with CacheIgnoreHeaders.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * This may break RFC 2616 compliance on behalf of the administrator.
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding for (i = 0; i < conf->ignore_headers->nelts; i++) {
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * Legacy call - functionally equivalent to ap_cache_cacheable_headers.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaron * @deprecated @see ap_cache_cacheable_headers
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaronCACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *p,
97d20d37d21b8d427a920e211858172f0a82427epoirier * Create a new table consisting of those elements from an input
97d20d37d21b8d427a920e211858172f0a82427epoirier * headers table that are allowed to be stored in a cache.
0ec6007a40ac877a7c8d87767ca8e306d89f6595aaronCACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_in(request_rec *r)
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server);
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * Create a new table consisting of those elements from an output
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * headers table that are allowed to be stored in a cache;
0f081398cf0eef8cc7c66a535d450110a92dc8aefielding * ensure there is a content type and capture any errors.
0f081398cf0eef8cc7c66a535d450110a92dc8aefieldingCACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r)
12901074f5d6b36d08be84d8637b6f2c21e0da26trawick headers_out = apr_table_overlay(r->pool, r->headers_out,