]> granicus.if.org Git - apache/commitdiff
mod_cache: Invalidate cached entities in response to RFC2616 Section
authorGraham Leggett <minfrin@apache.org>
Wed, 1 May 2013 18:49:04 +0000 (18:49 +0000)
committerGraham Leggett <minfrin@apache.org>
Wed, 1 May 2013 18:49:04 +0000 (18:49 +0000)
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
include/ap_mmn.h
modules/cache/cache_common.h
modules/cache/cache_storage.c
modules/cache/cache_util.c
modules/cache/mod_cache.c
modules/cache/mod_cache_disk.c
modules/cache/mod_cache_socache.c

diff --git a/CHANGES b/CHANGES
index 3a09209b881e5682d88680dfd2a4e837bbca0a82..9cccdc32473ea2a8ba922c72800eab6982afa852 100644 (file)
--- 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 <basant.kukreja sun.com>, Alejandro Alvarez
      <alejandro.alvarez.ayllon cern.ch>]
index 56203997d68f9bb3571931fb7db7b02137839280..091ac57511148738010e8c3e4dd16b2e24c571db 100644 (file)
  * 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" */
index cedce0767d2a6ec743a3ddcc5f85dcce00ed3331..9d56d28b66b24224a02fe9779eb62aecc9deb502 100644 (file)
@@ -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 */
index 179c79e69c0ef6df3251993a43f77ec9d10b6be6..a68d860a613751969406a69c12376014068b31b2 100644 (file)
@@ -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;
+}
index 1e5098d5374292b2c111a4c2281cfb26b487d529..cdabe8ff820c91b671bc7fba242cec59d73ac986 100644 (file)
@@ -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;
     }
index 173602fb5691f09b7695d2662db61c1860cec428..3e29340d4fac2c1e18e84209b3a0a84c91e2f6ed 100644 (file)
@@ -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);
 }
 
index 919613ca51c7432a69d9aeab0a1ee061cf129e29..7f116b838a8a8fdce6d32f4482c91a99fd685495 100644 (file)
@@ -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)
index 9632db123e45ffb9b0cffd4422f10ae79da364b2..fb3fea948aad073224ea52130d6e3a0ebfb9aad5 100644 (file)
@@ -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)