diff options
Diffstat (limited to 'modules/cache/mod_cache.c')
-rw-r--r-- | modules/cache/mod_cache.c | 393 |
1 files changed, 297 insertions, 96 deletions
diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c index 4f2d3e04..06bdf460 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 @@ -75,11 +76,6 @@ static int cache_quick_handler(request_rec *r, int lookup) ap_filter_rec_t *cache_out_handle; cache_server_conf *conf; - /* Delay initialization until we know we are handling a GET */ - if (r->method_number != M_GET) { - return DECLINED; - } - conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, &cache_module); @@ -106,6 +102,9 @@ static int cache_quick_handler(request_rec *r, int lookup) /* * Are we allowed to serve cached info at all? */ + if (!ap_cache_check_no_store(cache, r)) { + return DECLINED; + } /* find certain cache controlling headers */ auth = apr_table_get(r->headers_in, "Authorization"); @@ -117,6 +116,40 @@ static int cache_quick_handler(request_rec *r, int lookup) return DECLINED; } + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. + */ + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02461) + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + 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); + + return DECLINED; + } + case M_GET: { + break; + } + default : { + + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02462) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); + + return DECLINED; + } + } + /* * Try to serve this request from the cache. * @@ -176,9 +209,10 @@ static int cache_quick_handler(request_rec *r, int lookup) * is available later during running the filter may be * different due to an internal redirect. */ - cache->remove_url_filter = - ap_add_output_filter_handle(cache_remove_url_filter_handle, - cache, r, r->connection); + cache->remove_url_filter = ap_add_output_filter_handle( + cache_remove_url_filter_handle, cache, r, + r->connection); + } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, @@ -347,11 +381,6 @@ static int cache_handler(request_rec *r) ap_filter_rec_t *cache_save_handle; cache_server_conf *conf; - /* Delay initialization until we know we are handling a GET */ - if (r->method_number != M_GET) { - return DECLINED; - } - conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, &cache_module); @@ -376,6 +405,47 @@ static int cache_handler(request_rec *r) cache->providers = providers; /* + * Are we allowed to serve cached info at all? + */ + if (!ap_cache_check_no_store(cache, r)) { + return DECLINED; + } + + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. + */ + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02463) + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + 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); + + return DECLINED; + } + case M_GET: { + break; + } + default : { + + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02464) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); + + return DECLINED; + } + } + + /* * Try to serve this request from the cache. * * If no existing cache file (DECLINED) @@ -455,9 +525,10 @@ static int cache_handler(request_rec *r) * is available later during running the filter may be * different due to an internal redirect. */ - cache->remove_url_filter = - ap_add_output_filter_handle(cache_remove_url_filter_handle, - cache, r, r->connection); + cache->remove_url_filter + = ap_add_output_filter_handle( + cache_remove_url_filter_handle, cache, r, + r->connection); } else { @@ -665,7 +736,7 @@ static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in, */ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(00766) "cache: Cache provider's store_body returned an " - "empty brigade, but didn't consume all of the" + "empty brigade, but didn't consume all of the " "input brigade, standing down to prevent a spin"); ap_remove_output_filter(f); @@ -682,6 +753,22 @@ static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in, return rv; } +/** + * Sanity check for 304 Not Modified responses, as per RFC2616 Section 10.3.5. + */ +static const char *cache_header_cmp(apr_pool_t *pool, apr_table_t *left, + apr_table_t *right, const char *key) +{ + const char *h1, *h2; + + if ((h1 = cache_table_getm(pool, left, key)) + && (h2 = cache_table_getm(pool, right, key)) && (strcmp(h1, h2))) { + return apr_pstrcat(pool, "contradiction: 304 Not Modified, but ", key, + " modified", NULL); + } + return NULL; +} + /* * CACHE_SAVE filter * --------------- @@ -715,7 +802,7 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) apr_time_t exp, date, lastmod, now; apr_off_t size = -1; cache_info *info = NULL; - char *reason; + const char *reason; apr_pool_t *p; apr_bucket *e; apr_table_t *headers; @@ -857,12 +944,12 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) if (etag == NULL) { etag = apr_table_get(r->headers_out, "Etag"); } - cc_out = apr_table_get(r->err_headers_out, "Cache-Control"); - pragma = apr_table_get(r->err_headers_out, "Pragma"); + cc_out = cache_table_getm(r->pool, r->err_headers_out, "Cache-Control"); + pragma = cache_table_getm(r->pool, r->err_headers_out, "Pragma"); headers = r->err_headers_out; if (!cc_out && !pragma) { - cc_out = apr_table_get(r->headers_out, "Cache-Control"); - pragma = apr_table_get(r->headers_out, "Pragma"); + cc_out = cache_table_getm(r->pool, r->headers_out, "Cache-Control"); + pragma = cache_table_getm(r->pool, r->headers_out, "Pragma"); headers = r->headers_out; } @@ -871,8 +958,10 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) */ if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle && !cc_out && !pragma) { - cc_out = apr_table_get(cache->stale_handle->resp_hdrs, "Cache-Control"); - pragma = apr_table_get(cache->stale_handle->resp_hdrs, "Pragma"); + cc_out = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs, + "Cache-Control"); + pragma = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs, + "Pragma"); } /* Parse the cache control header */ @@ -1000,78 +1089,117 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) /* or we've been asked not to cache it above */ reason = "r->no_cache present"; } + else if (cache->stale_handle + && APR_DATE_BAD + != (date = apr_date_parse_http( + apr_table_get(r->headers_out, "Date"))) + && date < cache->stale_handle->cache_obj->info.date) { - /* Hold the phone. Some servers might allow us to cache a 2xx, but - * then make their 304 responses non cacheable. This leaves us in a - * sticky position. If the 304 is in answer to our own conditional - * request, we cannot send this 304 back to the client because the - * client isn't expecting it. Instead, our only option is to respect - * the answer to the question we asked (has it changed, answer was - * no) and return the cached item to the client, and then respect - * the uncacheable nature of this 304 by allowing the remove_url - * filter to kick in and remove the cached entity. - */ - if (reason && r->status == HTTP_NOT_MODIFIED && - cache->stale_handle) { - apr_bucket_brigade *bb; - apr_bucket *bkt; - int status; - - cache->handle = cache->stale_handle; - info = &cache->handle->cache_obj->info; - - /* Load in the saved status and clear the status line. */ - r->status = info->status; - r->status_line = NULL; - - bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + /** + * 13.12 Cache Replacement: + * + * Note: a new response that has an older Date header value than + * existing cached responses is not cacheable. + */ + reason = "updated entity is older than cached entity"; - r->headers_in = cache->stale_headers; - status = ap_meets_conditions(r); - if (status != OK) { - r->status = status; + /* while this response is not cacheable, the previous response still is */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00770) + "cache: Removing CACHE_REMOVE_URL filter."); + ap_remove_output_filter(cache->remove_url_filter); + } + else if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) { + apr_table_t *left = cache->stale_handle->resp_hdrs; + apr_table_t *right = r->headers_out; - bkt = apr_bucket_flush_create(bb->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, bkt); + /* and lastly, contradiction checks for revalidated responses + * as per RFC2616 Section 10.3.5 + */ + if (((reason = cache_header_cmp(r->pool, left, right, "Allow"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Encoding"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Language"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Length"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Location"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-MD5"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Range"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Content-Type"))) + || ((reason = cache_header_cmp(r->pool, left, right, "Expires"))) + || ((reason = cache_header_cmp(r->pool, left, right, "ETag"))) + || ((reason = cache_header_cmp(r->pool, left, right, + "Last-Modified")))) { + /* contradiction: 304 Not Modified, but entity header modified */ } - else { - /* RFC 2616 10.3.5 states that entity headers are not supposed - * to be in the 304 response. Therefore, we need to combine the - * response headers with the cached headers *before* we update - * the cached headers. - * - * However, before doing that, we need to first merge in - * err_headers_out and we also need to strip any hop-by-hop - * headers that might have snuck in. - */ - r->headers_out = ap_cache_cacheable_headers_out(r); - - /* Merge in our cached headers. However, keep any updated values. */ - cache_accept_headers(cache->handle, r, 1); + } - cache->provider->recall_body(cache->handle, r->pool, bb); + /** + * Enforce RFC2616 Section 10.3.5, just in case. We caught any + * inconsistencies above. + * + * If the conditional GET used a strong cache validator (see section + * 13.3.3), the response SHOULD NOT include other entity-headers. + * Otherwise (i.e., the conditional GET used a weak validator), the + * response MUST NOT include other entity-headers; this prevents + * inconsistencies between cached entity-bodies and updated headers. + */ + if (r->status == HTTP_NOT_MODIFIED) { + apr_table_unset(r->headers_out, "Allow"); + apr_table_unset(r->headers_out, "Content-Encoding"); + apr_table_unset(r->headers_out, "Content-Language"); + apr_table_unset(r->headers_out, "Content-Length"); + apr_table_unset(r->headers_out, "Content-MD5"); + apr_table_unset(r->headers_out, "Content-Range"); + apr_table_unset(r->headers_out, "Content-Type"); + apr_table_unset(r->headers_out, "Last-Modified"); + } - bkt = apr_bucket_eos_create(bb->bucket_alloc); - APR_BRIGADE_INSERT_TAIL(bb, bkt); - } + /* Hold the phone. Some servers might allow us to cache a 2xx, but + * then make their 304 responses non cacheable. RFC2616 says this: + * + * If a 304 response indicates an entity not currently cached, then + * the cache MUST disregard the response and repeat the request + * without the conditional. + * + * A 304 response with contradictory headers is technically a + * different entity, to be safe, we remove the entity from the cache. + */ + if (reason && r->status == HTTP_NOT_MODIFIED && cache->stale_handle) { - cache->block_response = 1; + ap_log_rerror( + APLOG_MARK, APLOG_INFO, 0, r, APLOGNO() "cache: %s responded with an uncacheable 304, retrying the request. Reason: %s", r->unparsed_uri, reason); - /* we've got a cache conditional hit! tell anyone who cares */ - cache_run_cache_status( - cache->handle, - r, - r->headers_out, - AP_CACHE_REVALIDATE, - apr_psprintf( - r->pool, - "conditional cache hit: 304 was uncacheable though (%s); entity removed", + /* we've got a cache conditional miss! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, + apr_psprintf(r->pool, + "conditional cache miss: 304 was uncacheable, entity removed: %s", reason)); + /* remove the cached entity immediately, we might cache it again */ + ap_remove_output_filter(cache->remove_url_filter); + cache_remove_url(cache, r); + /* let someone else attempt to cache */ cache_remove_lock(conf, cache, r, NULL); - return ap_pass_brigade(f->next, bb); + /* remove this filter from the chain */ + ap_remove_output_filter(f); + + /* retry without the conditionals */ + apr_table_unset(r->headers_in, "If-Match"); + apr_table_unset(r->headers_in, "If-Modified-Since"); + apr_table_unset(r->headers_in, "If-None-Match"); + apr_table_unset(r->headers_in, "If-Range"); + apr_table_unset(r->headers_in, "If-Unmodified-Since"); + + ap_internal_redirect(r->uri, r); + + return APR_SUCCESS; } if (reason) { @@ -1186,7 +1314,7 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) if (rv != OK) { /* we've got a cache miss! tell anyone who cares */ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, - "cache miss: create_entity failed"); + "cache miss: cache unwilling to store response"); /* Caching layer declined the opportunity to cache the response */ ap_remove_output_filter(f); @@ -1303,9 +1431,6 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) /* We found a stale entry which wasn't really stale. */ if (cache->stale_handle) { - /* Load in the saved status and clear the status line. */ - r->status = info->status; - r->status_line = NULL; /* RFC 2616 10.3.5 states that entity headers are not supposed * to be in the 304 response. Therefore, we need to combine the @@ -1319,7 +1444,9 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) r->headers_out = ap_cache_cacheable_headers_out(r); /* Merge in our cached headers. However, keep any updated values. */ - cache_accept_headers(cache->handle, r, 1); + /* take output, overlay on top of cached */ + cache_accept_headers(cache->handle, r, r->headers_out, + cache->handle->resp_hdrs, 1); } /* Write away header information to cache. It is possible that we are @@ -1342,6 +1469,10 @@ static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) apr_bucket *bkt; int status; + /* Load in the saved status and clear the status line. */ + r->status = info->status; + r->status_line = NULL; + /* We're just saving response headers, so we are done. Commit * the response at this point, unless there was a previous error. */ @@ -1483,6 +1614,70 @@ static apr_status_t cache_remove_url_filter(ap_filter_t *f, } /* + * 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(02465) + "cache: CACHE_INVALIDATE enabled unexpectedly: %s", r->uri); + } + else { + + if (r->status > 299) { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02466) + "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(02467) + "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 * ------------ * @@ -1579,11 +1774,11 @@ static int cache_status(cache_handle_t *h, request_rec *r, x_cache = conf->x_cache; } if (x_cache) { - apr_table_setn(headers, "X-Cache", - apr_psprintf(r->pool, "%s from %s", - status == AP_CACHE_HIT ? "HIT" : status - == AP_CACHE_REVALIDATE ? "REVALIDATE" : "MISS", - r->server->server_hostname)); + apr_table_setn(headers, "X-Cache", apr_psprintf(r->pool, "%s from %s", + status == AP_CACHE_HIT ? "HIT" + : status == AP_CACHE_REVALIDATE ? "REVALIDATE" : status + == AP_CACHE_INVALIDATE ? "INVALIDATE" : "MISS", + r->server->server_hostname)); } if (dconf && dconf->x_cache_detail_set) { @@ -1640,7 +1835,8 @@ static void cache_insert_error_filter(request_rec *r) if (cache->stale_handle && cache->save_filter && !cache->stale_handle->cache_obj->info.control.must_revalidate - && !cache->stale_handle->cache_obj->info.control.proxy_revalidate) { + && !cache->stale_handle->cache_obj->info.control.proxy_revalidate + && !cache->stale_handle->cache_obj->info.control.s_maxage) { const char *warn_head; cache_server_conf *conf = @@ -1773,7 +1969,7 @@ static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) { static void * create_cache_config(apr_pool_t *p, server_rec *s) { - const char *tmppath; + const char *tmppath = NULL; cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf)); /* array of URL prefixes for which caching is enabled */ @@ -2068,7 +2264,7 @@ static const char *add_cache_disable(cmd_parms *parms, void *dummy, &cache_module); if (parms->path) { - if (!strcmp(url, "on")) { + if (!strcasecmp(url, "on")) { dconf->disable = 1; dconf->disable_set = 1; return NULL; @@ -2454,6 +2650,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); } |