From 6e77e5b20f77caf50cf2ddf6a69fea583c4a03f5 Mon Sep 17 00:00:00 2001 From: Graham Leggett Date: Wed, 1 May 2013 18:49:04 +0000 Subject: [PATCH] mod_cache: Invalidate cached entities in response to RFC2616 Section 13.10 Invalidation After Updates or Deletions. PR 15868 Resolves outstanding issue with r1070179 as per http://www.gossamer-threads.com/lists/apache/dev/395830?do=post_view_threaded#395830 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1478140 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 4 + include/ap_mmn.h | 1 + modules/cache/cache_common.h | 1 + modules/cache/cache_storage.c | 216 +++++++++++++++++++----------- modules/cache/cache_util.c | 6 +- modules/cache/mod_cache.c | 145 ++++++++++++++++---- modules/cache/mod_cache_disk.c | 12 +- modules/cache/mod_cache_socache.c | 5 +- 8 files changed, 287 insertions(+), 103 deletions(-) diff --git a/CHANGES b/CHANGES index 3a09209b88..9cccdc3247 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) mod_cache: Invalidate cached entities in response to RFC2616 Section + 13.10 Invalidation After Updates or Deletions. PR 15868 [Graham + Leggett] + *) mod_dav: mod_dav overrides dav_fs response on PUT failure. PR 35981 [Basant Kumar Kukreja , Alejandro Alvarez ] diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 56203997d6..091ac57511 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -421,6 +421,7 @@ * 20121222.7 (2.5.0-dev) Add ap_remove_input|output_filter_byhandle() * 20121222.8 (2.5.0-dev) Add dav_join_error() * 20121222.9 (2.5.0-dev) Add conn_sense_e + * 20121222.10 (2.5.0-dev) Add cache_control_t.invalidated */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */ diff --git a/modules/cache/cache_common.h b/modules/cache/cache_common.h index cedce0767d..9d56d28b66 100644 --- a/modules/cache/cache_common.h +++ b/modules/cache/cache_common.h @@ -45,6 +45,7 @@ typedef struct cache_control { unsigned int must_revalidate:1; unsigned int proxy_revalidate:1; unsigned int s_maxage:1; + unsigned int invalidated:1; /* has this entity been invalidated? */ apr_int64_t max_age_value; /* if positive, then set */ apr_int64_t max_stale_value; /* if positive, then set */ apr_int64_t min_fresh_value; /* if positive, then set */ diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c index 179c79e69c..a68d860a61 100644 --- a/modules/cache/cache_storage.c +++ b/modules/cache/cache_storage.c @@ -390,61 +390,15 @@ int cache_select(cache_request_rec *cache, request_rec *r) return DECLINED; } -/* - * invalidate a specific URL entity in all caches - * - * All cached entities for this URL are removed, usually in - * response to a POST/PUT or DELETE. - * - * This function returns OK if at least one entity was found and - * removed, and DECLINED if no cached entities were removed. - */ -int cache_invalidate(cache_request_rec *cache, request_rec *r) -{ - cache_provider_list *list; - apr_status_t rv, status = DECLINED; - cache_handle_t *h; - - if (!cache) { - /* This should never happen */ - ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) - "cache: No cache request information available for key" - " generation"); - return DECLINED; - } - - if (!cache->key) { - rv = cache_generate_key(r, r->pool, &cache->key); - if (rv != APR_SUCCESS) { - return DECLINED; - } - } - - /* go through the cache types */ - h = apr_palloc(r->pool, sizeof(cache_handle_t)); - - list = cache->providers; - - while (list) { - rv = list->provider->open_entity(h, r, cache->key); - if (OK == rv) { - list->provider->remove_url(h, r); - status = OK; - } - list = list->next; - } - - return status; -} - -apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, - const char **key) +apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p, + const char *uri, apr_uri_t *parsed_uri, const char **key) { cache_server_conf *conf; char *port_str, *hn, *lcs; const char *hostname, *scheme; int i; - char *path, *querystring; + const char *path; + char *querystring; if (*key) { /* @@ -458,7 +412,7 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * option below. */ conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, - &cache_module); + &cache_module); /* * Use the canonical name to improve cache hit rate, but only if this is @@ -484,15 +438,15 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, } else { /* Use _default_ as the hostname if none present, as in mod_vhost */ - hostname = ap_get_server_name(r); + hostname = ap_get_server_name(r); if (!hostname) { hostname = "_default_"; } } } - else if(r->parsed_uri.hostname) { + else if (parsed_uri->hostname) { /* Copy the parsed uri hostname */ - hn = apr_pstrdup(p, r->parsed_uri.hostname); + hn = apr_pstrdup(p, parsed_uri->hostname); ap_str_tolower(hn); /* const work-around */ hostname = hn; @@ -511,9 +465,9 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * "no proxy request" and "reverse proxy request" are handled in the same * manner (see above why this is needed). */ - if (r->proxyreq && r->parsed_uri.scheme) { + if (r->proxyreq && parsed_uri->scheme) { /* Copy the scheme and lower-case it */ - lcs = apr_pstrdup(p, r->parsed_uri.scheme); + lcs = apr_pstrdup(p, parsed_uri->scheme); ap_str_tolower(lcs); /* const work-around */ scheme = lcs; @@ -536,11 +490,11 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * server. */ if (r->proxyreq && (r->proxyreq != PROXYREQ_REVERSE)) { - if (r->parsed_uri.port_str) { - port_str = apr_pcalloc(p, strlen(r->parsed_uri.port_str) + 2); + if (parsed_uri->port_str) { + port_str = apr_pcalloc(p, strlen(parsed_uri->port_str) + 2); port_str[0] = ':'; - for (i = 0; r->parsed_uri.port_str[i]; i++) { - port_str[i + 1] = apr_tolower(r->parsed_uri.port_str[i]); + for (i = 0; parsed_uri->port_str[i]; i++) { + port_str[i + 1] = apr_tolower(parsed_uri->port_str[i]); } } else if (apr_uri_port_of_scheme(scheme)) { @@ -572,13 +526,13 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * Check if we need to ignore session identifiers in the URL and do so * if needed. */ - path = r->uri; - querystring = r->parsed_uri.query; + path = uri; + querystring = parsed_uri->query; if (conf->ignore_session_id->nelts) { int i; char **identifier; - identifier = (char **)conf->ignore_session_id->elts; + identifier = (char **) conf->ignore_session_id->elts; for (i = 0; i < conf->ignore_session_id->nelts; i++, identifier++) { int len; char *param; @@ -589,9 +543,9 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * of the path and that the parameter matches our identifier */ if ((param = strrchr(path, ';')) - && !strncmp(param + 1, *identifier, len) - && (*(param + len + 1) == '=') - && !strchr(param + len + 2, '/')) { + && !strncmp(param + 1, *identifier, len) + && (*(param + len + 1) == '=') + && !strchr(param + len + 2, '/')) { path = apr_pstrndup(p, path, param - path); continue; } @@ -604,7 +558,7 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * querystring and followed by a '=' */ if (!strncmp(querystring, *identifier, len) - && (*(querystring + len) == '=')) { + && (*(querystring + len) == '=')) { param = querystring; } else { @@ -626,14 +580,15 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, if (querystring != param) { querystring = apr_pstrndup(p, querystring, - param - querystring); + param - querystring); } else { querystring = ""; } if ((amp = strchr(param + len + 1, '&'))) { - querystring = apr_pstrcat(p, querystring, amp + 1, NULL); + querystring = apr_pstrcat(p, querystring, amp + 1, + NULL); } else { /* @@ -653,12 +608,12 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, /* Key format is a URI, optionally without the query-string */ if (conf->ignorequerystring) { - *key = apr_pstrcat(p, scheme, "://", hostname, port_str, - path, "?", NULL); + *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?", + NULL); } else { - *key = apr_pstrcat(p, scheme, "://", hostname, port_str, - path, "?", querystring, NULL); + *key = apr_pstrcat(p, scheme, "://", hostname, port_str, path, "?", + querystring, NULL); } /* @@ -669,9 +624,118 @@ apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, * resource in the cache under a key where it is never found by the quick * handler during following requests. */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698) - "cache: Key for entity %s?%s is %s", r->uri, - r->parsed_uri.query, *key); + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698) "cache: Key for entity %s?%s is %s", uri, parsed_uri->query, *key); return APR_SUCCESS; } + +apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, + const char **key) +{ + return cache_canonicalise_key(r, p, r->uri, &r->parsed_uri, key); +} + +/* + * Invalidate a specific URL entity in all caches + * + * All cached entities for this URL are removed, usually in + * response to a POST/PUT or DELETE. + * + * This function returns OK if at least one entity was found and + * removed, and DECLINED if no cached entities were removed. + */ +int cache_invalidate(cache_request_rec *cache, request_rec *r) +{ + cache_provider_list *list; + apr_status_t rv, status = DECLINED; + cache_handle_t *h; + apr_uri_t location_uri; + apr_uri_t content_location_uri; + + const char *location, *location_key = NULL; + const char *content_location, *content_location_key = NULL; + + if (!cache) { + /* This should never happen */ + ap_log_rerror( + APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) "cache: No cache request information available for key" + " generation"); + return DECLINED; + } + + if (!cache->key) { + rv = cache_generate_key(r, r->pool, &cache->key); + if (rv != APR_SUCCESS) { + return DECLINED; + } + } + + location = apr_table_get(r->headers_out, "Location"); + if (location) { + if (APR_SUCCESS != apr_uri_parse(r->pool, location, &location_uri) + || APR_SUCCESS + != cache_canonicalise_key(r, r->pool, location, + &location_uri, &location_key) + || strcmp(r->parsed_uri.hostname, location_uri.hostname)) { + location_key = NULL; + } + } + + content_location = apr_table_get(r->headers_out, "Content-Location"); + if (content_location) { + if (APR_SUCCESS + != apr_uri_parse(r->pool, content_location, + &content_location_uri) + || APR_SUCCESS + != cache_canonicalise_key(r, r->pool, content_location, + &content_location_uri, &content_location_key) + || strcmp(r->parsed_uri.hostname, + content_location_uri.hostname)) { + content_location_key = NULL; + } + } + + /* go through the cache types */ + h = apr_palloc(r->pool, sizeof(cache_handle_t)); + + list = cache->providers; + + while (list) { + + /* invalidate the request uri */ + rv = list->provider->open_entity(h, r, cache->key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO() "cache: Attempted to invalidate cached entity with key: %s", cache->key); + + /* invalidate the Location */ + if (location_key) { + rv = list->provider->open_entity(h, r, location_key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO() "cache: Attempted to invalidate cached entity with key: %s", location_key); + } + + /* invalidate the Content-Location */ + if (content_location_key) { + rv = list->provider->open_entity(h, r, content_location_key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO() "cache: Attempted to invalidate cached entity with key: %s", content_location_key); + } + + list = list->next; + } + + return status; +} diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c index 1e5098d537..cdabe8ff82 100644 --- a/modules/cache/cache_util.c +++ b/modules/cache/cache_util.c @@ -544,11 +544,13 @@ int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache, /* These come from the cached entity. */ if (h->cache_obj->info.control.no_cache || h->cache_obj->info.control.no_cache_header - || h->cache_obj->info.control.private_header) { + || h->cache_obj->info.control.private_header + || h->cache_obj->info.control.invalidated) { /* * The cached entity contained Cache-Control: no-cache, or a * no-cache with a header present, or a private with a header - * present, so treat as stale causing revalidation. + * present, or the cached entity has been invalidated in the + * past, so treat as stale causing revalidation. */ return 0; } diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c index 173602fb56..3e29340d4f 100644 --- a/modules/cache/mod_cache.c +++ b/modules/cache/mod_cache.c @@ -34,6 +34,7 @@ static ap_filter_rec_t *cache_save_subreq_filter_handle; static ap_filter_rec_t *cache_out_filter_handle; static ap_filter_rec_t *cache_out_subreq_filter_handle; static ap_filter_rec_t *cache_remove_url_filter_handle; +static ap_filter_rec_t *cache_invalidate_filter_handle; /* * CACHE handler @@ -112,24 +113,39 @@ static int cache_quick_handler(request_rec *r, int lookup) return DECLINED; } - /* Are we something other than GET or HEAD? If so, invalidate - * the cached entities. + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. */ - if (r->method_number != M_GET) { + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO() + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + r->uri); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00748) - "Invalidating all cached entities in response to '%s' request for %s", - r->method, r->uri); + /* Add cache_invalidate filter to this request to force a + * cache entry to be invalidated if the response is + * ultimately successful (2xx). + */ + ap_add_output_filter_handle( + cache_invalidate_filter_handle, cache, r, + r->connection); - cache_invalidate(cache, r); + return DECLINED; + } + case M_GET: { + break; + } + default : { - /* we've got a cache invalidate! tell everyone who cares */ - cache_run_cache_status(cache->handle, r, r->headers_out, - AP_CACHE_INVALIDATE, apr_psprintf(r->pool, - "cache invalidated by %s", r->method)); + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO() "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); return DECLINED; } + } /* * Try to serve this request from the cache. @@ -385,24 +401,38 @@ static int cache_handler(request_rec *r) /* save away the possible providers */ cache->providers = providers; - /* Are we something other than GET or HEAD? If so, invalidate - * the cached entities. + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. */ - if (r->method_number != M_GET) { - - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00755) - "Invalidating all cached entities in response to '%s' request for %s", - r->method, r->uri); + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { - cache_invalidate(cache, r); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO() + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + r->uri); - /* we've got a cache invalidate! tell everyone who cares */ - cache_run_cache_status(cache->handle, r, r->headers_out, - AP_CACHE_INVALIDATE, apr_psprintf(r->pool, - "cache invalidated by %s", r->method)); + /* Add cache_invalidate filter to this request to force a + * cache entry to be invalidated if the response is + * ultimately successful (2xx). + */ + ap_add_output_filter_handle( + cache_invalidate_filter_handle, cache, r, + r->connection); return DECLINED; + } + case M_GET: { + break; + } + default : { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO() "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); + + return DECLINED; + } } /* @@ -1513,6 +1543,70 @@ static apr_status_t cache_remove_url_filter(ap_filter_t *f, return ap_pass_brigade(f->next, in); } +/* + * CACHE_INVALIDATE filter + * ----------------------- + * + * This filter gets added in the quick handler should a PUT, POST or DELETE + * method be detected. If the response is successful, we must invalidate any + * cached entity as per RFC2616 section 13.10. + * + * CACHE_INVALIDATE has to be a protocol filter to ensure that is run even if + * the response is a canned error message, which removes the content filters + * from the chain. + * + * CACHE_INVALIDATE expects cache request rec within its context because the + * request this filter runs on can be different from the one whose cache entry + * should be removed, due to internal redirects. + */ +static apr_status_t cache_invalidate_filter(ap_filter_t *f, + apr_bucket_brigade *in) +{ + request_rec *r = f->r; + cache_request_rec *cache; + + /* Setup cache_request_rec */ + cache = (cache_request_rec *) f->ctx; + + if (!cache) { + /* user likely configured CACHE_INVALIDATE manually; they should really + * use mod_cache configuration to do that. So: + * 1. Remove ourselves + * 2. Do nothing and bail out + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "cache: CACHE_INVALIDATE enabled unexpectedly: %s", r->uri); + } + else { + + if (r->status > 299) { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO() + "cache: response status to '%s' method is %d (>299), not invalidating cached entity: %s", r->method, r->status, r->uri); + + } + else { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO() + "cache: Invalidating all cached entities in response to '%s' request for %s", + r->method, r->uri); + + cache_invalidate(cache, r); + + /* we've got a cache invalidate! tell everyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, + AP_CACHE_INVALIDATE, apr_psprintf(r->pool, + "cache invalidated by %s", r->method)); + + } + + } + + /* remove ourselves */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); +} + /* * CACHE filter * ------------ @@ -2481,6 +2575,11 @@ static void register_hooks(apr_pool_t *p) cache_remove_url_filter, NULL, AP_FTYPE_PROTOCOL); + cache_invalidate_filter_handle = + ap_register_output_filter("CACHE_INVALIDATE", + cache_invalidate_filter, + NULL, + AP_FTYPE_PROTOCOL); ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); } diff --git a/modules/cache/mod_cache_disk.c b/modules/cache/mod_cache_disk.c index 919613ca51..7f116b838a 100644 --- a/modules/cache/mod_cache_disk.c +++ b/modules/cache/mod_cache_disk.c @@ -1344,7 +1344,17 @@ static apr_status_t commit_entity(cache_handle_t *h, request_rec *r) static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r) { - return APR_ENOTIMPL; + apr_status_t rv; + + rv = recall_headers(h, r); + if (rv != APR_SUCCESS) { + return rv; + } + + /* mark the entity as invalidated */ + h->cache_obj->info.control.invalidated = 1; + + return commit_entity(h, r); } static void *create_dir_config(apr_pool_t *p, char *dummy) diff --git a/modules/cache/mod_cache_socache.c b/modules/cache/mod_cache_socache.c index 9632db123e..fb3fea948a 100644 --- a/modules/cache/mod_cache_socache.c +++ b/modules/cache/mod_cache_socache.c @@ -1182,7 +1182,10 @@ fail: static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r) { - return APR_ENOTIMPL; + /* mark the entity as invalidated */ + h->cache_obj->info.control.invalidated = 1; + + return commit_entity(h, r); } static void *create_dir_config(apr_pool_t *p, char *dummy) -- 2.40.0