summaryrefslogtreecommitdiff
path: root/modules/cache/mod_cache.c
diff options
context:
space:
mode:
authorArno Töll <debian@toell.net>2012-01-08 22:53:17 +0100
committerArno Töll <debian@toell.net>2012-01-08 22:53:17 +0100
commite072a2dd866b7cb9f14319b80326a4e7fd16fcdf (patch)
treea49dfc56d94a26011fe157835ff6cbe14edbd8a9 /modules/cache/mod_cache.c
parent0890390c00801651d08d3794e13b31a5dabbf5ef (diff)
downloadapache2-e072a2dd866b7cb9f14319b80326a4e7fd16fcdf.tar.gz
Imported Upstream version 2.3.16-beta
Diffstat (limited to 'modules/cache/mod_cache.c')
-rw-r--r--modules/cache/mod_cache.c1527
1 files changed, 1224 insertions, 303 deletions
diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c
index 8aeb3f73..17fc9d3f 100644
--- a/modules/cache/mod_cache.c
+++ b/modules/cache/mod_cache.c
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-#define CORE_PRIVATE
-
#include "mod_cache.h"
+#include "cache_storage.h"
+#include "cache_util.h"
+
module AP_MODULE_DECLARE_DATA cache_module;
APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
@@ -27,6 +28,7 @@ APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
/* Handles for cache filters, resolved at startup to eliminate
* a name-to-function mapping on each request
*/
+static ap_filter_rec_t *cache_filter_handle;
static ap_filter_rec_t *cache_save_filter_handle;
static ap_filter_rec_t *cache_save_subreq_filter_handle;
static ap_filter_rec_t *cache_out_filter_handle;
@@ -46,18 +48,32 @@ static ap_filter_rec_t *cache_remove_url_filter_handle;
* add CACHE_SAVE filter
* If No:
* oh well.
+ *
+ * By default, the cache handler runs in the quick handler, bypassing
+ * virtually all server processing and offering the cache its optimal
+ * performance. In this mode, the cache bolts onto the front of the
+ * server, and behaves as a discrete RFC2616 caching proxy
+ * implementation.
+ *
+ * Under certain circumstances, an admin might want to run the cache as
+ * a normal handler instead of a quick handler, allowing the cache to
+ * run after the authorisation hooks, or by allowing fine control over
+ * the placement of the cache in the filter chain. This option comes at
+ * a performance penalty, and should only be used to achieve specific
+ * caching goals where the admin understands what they are doing.
*/
-static int cache_url_handler(request_rec *r, int lookup)
+static int cache_quick_handler(request_rec *r, int lookup)
{
apr_status_t rv;
const char *auth;
cache_provider_list *providers;
cache_request_rec *cache;
- cache_server_conf *conf;
apr_bucket_brigade *out;
+ apr_bucket *e;
ap_filter_t *next;
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) {
@@ -67,20 +83,22 @@ static int cache_url_handler(request_rec *r, int lookup)
conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
&cache_module);
+ /* only run if the quick handler is enabled */
+ if (!conf->quick) {
+ return DECLINED;
+ }
+
/*
* Which cache module (if any) should handle this request?
*/
- if (!(providers = ap_cache_get_providers(r, conf, r->parsed_uri))) {
+ if (!(providers = cache_get_providers(r, conf, r->parsed_uri))) {
return DECLINED;
}
/* make space for the per request config */
- cache = (cache_request_rec *) ap_get_module_config(r->request_config,
- &cache_module);
- if (!cache) {
- cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
- ap_set_module_config(r->request_config, &cache_module, cache);
- }
+ cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
+ cache->size = -1;
+ cache->out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
/* save away the possible providers */
cache->providers = providers;
@@ -109,7 +127,7 @@ static int cache_url_handler(request_rec *r, int lookup)
* add cache_out filter
* return OK
*/
- rv = cache_select(r);
+ rv = cache_select(cache, r);
if (rv != OK) {
if (rv == DECLINED) {
if (!lookup) {
@@ -121,7 +139,7 @@ static int cache_url_handler(request_rec *r, int lookup)
* backend without any attempt to cache. this stops
* duplicated simultaneous attempts to cache an entity.
*/
- rv = ap_cache_try_lock(conf, r, NULL);
+ rv = cache_try_lock(conf, cache, r);
if (APR_SUCCESS == rv) {
/*
@@ -130,29 +148,32 @@ static int cache_url_handler(request_rec *r, int lookup)
* or not.
*/
if (r->main) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
- r->server,
- "Adding CACHE_SAVE_SUBREQ filter for %s",
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00749) "Adding CACHE_SAVE_SUBREQ filter for %s",
r->uri);
- ap_add_output_filter_handle(cache_save_subreq_filter_handle,
- NULL, r, r->connection);
+ cache->save_filter = ap_add_output_filter_handle(
+ cache_save_subreq_filter_handle, cache, r,
+ r->connection);
}
else {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
- r->server, "Adding CACHE_SAVE filter for %s",
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00750) "Adding CACHE_SAVE filter for %s",
r->uri);
- ap_add_output_filter_handle(cache_save_filter_handle,
- NULL, r, r->connection);
+ cache->save_filter = ap_add_output_filter_handle(
+ cache_save_filter_handle, cache, r,
+ r->connection);
}
- ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
+ apr_pool_userdata_setn(cache, CACHE_CTX_KEY, NULL, r->pool);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00751)
"Adding CACHE_REMOVE_URL filter for %s",
r->uri);
/* Add cache_remove_url filter to this request to remove a
* stale cache entry if needed. Also put the current cache
* request rec in the filter context, as the request that
- * is available later during running the filter maybe
+ * is available later during running the filter may be
* different due to an internal redirect.
*/
cache->remove_url_filter =
@@ -160,44 +181,40 @@ static int cache_url_handler(request_rec *r, int lookup)
cache, r, r->connection);
}
else {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, rv,
- r->server, "Cache locked for url, not caching "
- "response: %s", r->uri);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv,
+ r, APLOGNO(00752) "Cache locked for url, not caching "
+ "response: %s", r->uri);
}
}
else {
if (cache->stale_headers) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
- r->server, "Restoring request headers for %s",
- r->uri);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00753) "Restoring request headers for %s",
+ r->uri);
r->headers_in = cache->stale_headers;
}
-
- /* Delete our per-request configuration. */
- ap_set_module_config(r->request_config, &cache_module, NULL);
}
}
else {
/* error */
- ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
- "cache: error returned while checking for cached "
- "file by '%s' cache", cache->provider_name);
+ return rv;
}
return DECLINED;
}
+ /* we've got a cache hit! tell everyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT,
+ "cache hit");
+
/* if we are a lookup, we are exiting soon one way or another; Restore
* the headers. */
if (lookup) {
if (cache->stale_headers) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
- "Restoring request headers.");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00754)
+ "Restoring request headers.");
r->headers_in = cache->stale_headers;
}
-
- /* Delete our per-request configuration. */
- ap_set_module_config(r->request_config, &cache_module, NULL);
}
rv = ap_meets_conditions(r);
@@ -236,7 +253,7 @@ static int cache_url_handler(request_rec *r, int lookup)
else {
cache_out_handle = cache_out_filter_handle;
}
- ap_add_output_filter_handle(cache_out_handle, NULL, r, r->connection);
+ ap_add_output_filter_handle(cache_out_handle, cache, r, r->connection);
/*
* Remove all filters that are before the cache_out filter. This ensures
@@ -258,18 +275,269 @@ static int cache_url_handler(request_rec *r, int lookup)
/* kick off the filter stack */
out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
- rv = ap_pass_brigade(r->output_filters, out);
- if (rv != APR_SUCCESS) {
- if (rv != AP_FILTER_ERROR) {
- ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
- "cache: error returned while trying to return %s "
- "cached data",
- cache->provider_name);
+ e = apr_bucket_eos_create(out->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+
+ return ap_pass_brigade_fchk(r, out,
+ "cache_quick_handler(%s): ap_pass_brigade returned",
+ cache->provider_name);
+}
+
+/**
+ * If the two filter handles are present within the filter chain, replace
+ * the last instance of the first filter with the last instance of the
+ * second filter, and return true. If the second filter is not present at
+ * all, the first filter is removed, and false is returned. If neither
+ * filter is present, false is returned and this function does nothing.
+ * If a stop filter is specified, processing will stop once this filter is
+ * reached.
+ */
+static int cache_replace_filter(ap_filter_t *next, ap_filter_rec_t *from,
+ ap_filter_rec_t *to, ap_filter_rec_t *stop) {
+ ap_filter_t *ffrom = NULL, *fto = NULL;
+ while (next && next->frec != stop) {
+ if (next->frec == from) {
+ ffrom = next;
+ }
+ if (next->frec == to) {
+ fto = next;
+ }
+ next = next->next;
+ }
+ if (ffrom && fto) {
+ ffrom->frec = fto->frec;
+ ffrom->ctx = fto->ctx;
+ ap_remove_output_filter(fto);
+ return 1;
+ }
+ if (ffrom) {
+ ap_remove_output_filter(ffrom);
+ }
+ return 0;
+}
+
+/**
+ * Find the given filter, and return it if found, or NULL otherwise.
+ */
+static ap_filter_t *cache_get_filter(ap_filter_t *next, ap_filter_rec_t *rec) {
+ while (next) {
+ if (next->frec == rec && next->ctx) {
+ break;
}
+ next = next->next;
+ }
+ return next;
+}
+
+/**
+ * The cache handler is functionally similar to the cache_quick_hander,
+ * however a number of steps that are required by the quick handler are
+ * not required here, as the normal httpd processing has already handled
+ * these steps.
+ */
+static int cache_handler(request_rec *r)
+{
+ apr_status_t rv;
+ cache_provider_list *providers;
+ cache_request_rec *cache;
+ apr_bucket_brigade *out;
+ apr_bucket *e;
+ ap_filter_t *next;
+ ap_filter_rec_t *cache_out_handle;
+ 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);
+
+ /* only run if the quick handler is disabled */
+ if (conf->quick) {
+ return DECLINED;
+ }
+
+ /*
+ * Which cache module (if any) should handle this request?
+ */
+ if (!(providers = cache_get_providers(r, conf, r->parsed_uri))) {
+ return DECLINED;
+ }
+
+ /* make space for the per request config */
+ cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
+ cache->size = -1;
+ cache->out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ /* save away the possible providers */
+ cache->providers = providers;
+
+ /*
+ * Try to serve this request from the cache.
+ *
+ * If no existing cache file (DECLINED)
+ * add cache_save filter
+ * If cached file (OK)
+ * clear filter stack
+ * add cache_out filter
+ * return OK
+ */
+ rv = cache_select(cache, r);
+ if (rv != OK) {
+ if (rv == DECLINED) {
+
+ /* try to obtain a cache lock at this point. if we succeed,
+ * we are the first to try and cache this url. if we fail,
+ * it means someone else is already trying to cache this
+ * url, and we should just let the request through to the
+ * backend without any attempt to cache. this stops
+ * duplicated simultaneous attempts to cache an entity.
+ */
+ rv = cache_try_lock(conf, cache, r);
+ if (APR_SUCCESS == rv) {
+
+ /*
+ * Add cache_save filter to cache this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00756) "Adding CACHE_SAVE_SUBREQ filter for %s",
+ r->uri);
+ cache_save_handle = cache_save_subreq_filter_handle;
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00757) "Adding CACHE_SAVE filter for %s",
+ r->uri);
+ cache_save_handle = cache_save_filter_handle;
+ }
+ ap_add_output_filter_handle(cache_save_handle, cache, r,
+ r->connection);
+
+ /*
+ * Did the user indicate the precise location of the
+ * CACHE_SAVE filter by inserting the CACHE filter as a
+ * marker?
+ *
+ * If so, we get cunning and replace CACHE with the
+ * CACHE_SAVE filter. This has the effect of inserting
+ * the CACHE_SAVE filter at the precise location where
+ * the admin wants to cache the content. All filters that
+ * lie before and after the original location of the CACHE
+ * filter will remain in place.
+ */
+ if (cache_replace_filter(r->output_filters,
+ cache_filter_handle, cache_save_handle,
+ ap_get_input_filter_handle("SUBREQ_CORE"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00758) "Replacing CACHE with CACHE_SAVE "
+ "filter for %s", r->uri);
+ }
+
+ /* save away the save filter stack */
+ cache->save_filter = cache_get_filter(r->output_filters,
+ cache_save_filter_handle);
+
+ apr_pool_userdata_setn(cache, CACHE_CTX_KEY, NULL, r->pool);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00759)
+ "Adding CACHE_REMOVE_URL filter for %s",
+ r->uri);
+
+ /* Add cache_remove_url filter to this request to remove a
+ * stale cache entry if needed. Also put the current cache
+ * request rec in the filter context, as the request that
+ * 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);
+
+ }
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv,
+ r, APLOGNO(00760) "Cache locked for url, not caching "
+ "response: %s", r->uri);
+ }
+ }
+ else {
+ /* error */
+ return rv;
+ }
+ return DECLINED;
+ }
+
+ /* we've got a cache hit! tell everyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT,
+ "cache hit");
+
+ rv = ap_meets_conditions(r);
+ if (rv != OK) {
return rv;
}
- return OK;
+ /* Serve up the content */
+
+ /*
+ * Add cache_out filter to serve this request. Choose
+ * the correct filter by checking if we are a subrequest
+ * or not.
+ */
+ if (r->main) {
+ cache_out_handle = cache_out_subreq_filter_handle;
+ }
+ else {
+ cache_out_handle = cache_out_filter_handle;
+ }
+ ap_add_output_filter_handle(cache_out_handle, cache, r, r->connection);
+
+ /*
+ * Did the user indicate the precise location of the CACHE_OUT filter by
+ * inserting the CACHE filter as a marker?
+ *
+ * If so, we get cunning and replace CACHE with the CACHE_OUT filters.
+ * This has the effect of inserting the CACHE_OUT filter at the precise
+ * location where the admin wants to cache the content. All filters that
+ * lie *after* the original location of the CACHE filter will remain in
+ * place.
+ */
+ if (cache_replace_filter(r->output_filters, cache_filter_handle,
+ cache_out_handle, ap_get_input_filter_handle("SUBREQ_CORE"))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
+ r, APLOGNO(00761) "Replacing CACHE with CACHE_OUT filter for %s",
+ r->uri);
+ }
+
+ /*
+ * Remove all filters that are before the cache_out filter. This ensures
+ * that we kick off the filter stack with our cache_out filter being the
+ * first in the chain. This make sense because we want to restore things
+ * in the same manner as we saved them.
+ * There may be filters before our cache_out filter, because
+ *
+ * 1. We call ap_set_content_type during cache_select. This causes
+ * Content-Type specific filters to be added.
+ * 2. We call the insert_filter hook. This causes filters e.g. like
+ * the ones set with SetOutputFilter to be added.
+ */
+ next = r->output_filters;
+ while (next && (next->frec != cache_out_handle)) {
+ ap_remove_output_filter(next);
+ next = next->next;
+ }
+
+ /* kick off the filter stack */
+ out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ e = apr_bucket_eos_create(out->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ return ap_pass_brigade_fchk(r, out, "cache(%s): ap_pass_brigade returned",
+ cache->provider_name);
}
/*
@@ -278,42 +546,134 @@ static int cache_url_handler(request_rec *r, int lookup)
*
* Deliver cached content (headers and body) up the stack.
*/
-static int cache_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+static int cache_out_filter(ap_filter_t *f, apr_bucket_brigade *in)
{
request_rec *r = f->r;
- cache_request_rec *cache;
-
- cache = (cache_request_rec *) ap_get_module_config(r->request_config,
- &cache_module);
+ apr_bucket *e;
+ cache_request_rec *cache = (cache_request_rec *)f->ctx;
if (!cache) {
/* user likely configured CACHE_OUT manually; they should use mod_cache
* configuration to do that */
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
- "CACHE_OUT enabled unexpectedly");
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00762)
+ "CACHE/CACHE_OUT filter enabled while caching is disabled, ignoring");
ap_remove_output_filter(f);
- return ap_pass_brigade(f->next, bb);
+ return ap_pass_brigade(f->next, in);
}
- ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
- "cache: running CACHE_OUT filter");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00763)
+ "cache: running CACHE_OUT filter");
+
+ /* clean out any previous response up to EOS, if any */
+ for (e = APR_BRIGADE_FIRST(in);
+ e != APR_BRIGADE_SENTINEL(in);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ apr_bucket_brigade *bb = apr_brigade_create(r->pool,
+ r->connection->bucket_alloc);
- /* restore status of cached response */
- /* XXX: This exposes a bug in mem_cache, since it does not
- * restore the status into it's handle. */
- r->status = cache->handle->cache_obj->info.status;
+ /* restore status of cached response */
+ r->status = cache->handle->cache_obj->info.status;
- /* recall_headers() was called in cache_select() */
- cache->provider->recall_body(cache->handle, r->pool, bb);
+ /* recall_headers() was called in cache_select() */
+ cache->provider->recall_body(cache->handle, r->pool, bb);
+ APR_BRIGADE_PREPEND(in, bb);
- /* This filter is done once it has served up its content */
- ap_remove_output_filter(f);
+ /* This filter is done once it has served up its content */
+ ap_remove_output_filter(f);
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00764)
+ "cache: serving %s", r->uri);
+ return ap_pass_brigade(f->next, in);
+
+ }
+ apr_bucket_delete(e);
+ }
- ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
- "cache: serving %s", r->uri);
- return ap_pass_brigade(f->next, bb);
+ return APR_SUCCESS;
}
+/*
+ * Having jumped through all the hoops and decided to cache the
+ * response, call store_body() for each brigade, handling the
+ * case where the provider can't swallow the full brigade. In this
+ * case, we write the brigade we were passed out downstream, and
+ * loop around to try and cache some more until the in brigade is
+ * completely empty. As soon as the out brigade contains eos, call
+ * commit_entity() to finalise the cached element.
+ */
+static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in,
+ cache_server_conf *conf, cache_request_rec *cache)
+{
+ int rv = APR_SUCCESS;
+ apr_bucket *e;
+
+ /* pass the brigade in into the cache provider, which is then
+ * expected to move cached buckets to the out brigade, for us
+ * to pass up the filter stack. repeat until in is empty, or
+ * we fail.
+ */
+ while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) {
+
+ rv = cache->provider->store_body(cache->handle, f->r, in, cache->out);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(00765)
+ "cache: Cache provider's store_body failed!");
+ ap_remove_output_filter(f);
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, f->r, NULL);
+
+ /* give up trying to cache, just step out the way */
+ APR_BRIGADE_PREPEND(in, cache->out);
+ return ap_pass_brigade(f->next, in);
+
+ }
+
+ /* does the out brigade contain eos? if so, we're done, commit! */
+ for (e = APR_BRIGADE_FIRST(cache->out);
+ e != APR_BRIGADE_SENTINEL(cache->out);
+ e = APR_BUCKET_NEXT(e))
+ {
+ if (APR_BUCKET_IS_EOS(e)) {
+ rv = cache->provider->commit_entity(cache->handle, f->r);
+ break;
+ }
+ }
+
+ /* conditionally remove the lock as soon as we see the eos bucket */
+ cache_remove_lock(conf, cache, f->r, cache->out);
+
+ if (APR_BRIGADE_EMPTY(cache->out)) {
+ if (APR_BRIGADE_EMPTY(in)) {
+ /* cache provider wants more data before passing the brigade
+ * upstream, oblige the provider by leaving to fetch more.
+ */
+ break;
+ }
+ else {
+ /* oops, no data out, but not all data read in either, be
+ * safe and stand down to prevent a spin.
+ */
+ 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"
+ "input brigade, standing down to prevent a spin");
+ ap_remove_output_filter(f);
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, f->r, NULL);
+
+ return ap_pass_brigade(f->next, in);
+ }
+ }
+
+ rv = ap_pass_brigade(f->next, cache->out);
+ }
+
+ return rv;
+}
/*
* CACHE_SAVE filter
@@ -339,29 +699,32 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
{
int rv = !OK;
request_rec *r = f->r;
- cache_request_rec *cache;
+ cache_request_rec *cache = (cache_request_rec *)f->ctx;
cache_server_conf *conf;
- const char *cc_out, *cl;
+ cache_dir_conf *dconf;
+ cache_control_t control;
+ const char *cc_out, *cl, *pragma;
const char *exps, *lastmods, *dates, *etag;
apr_time_t exp, date, lastmod, now;
- apr_off_t size;
+ apr_off_t size = -1;
cache_info *info = NULL;
char *reason;
apr_pool_t *p;
apr_bucket *e;
+ apr_table_t *headers;
conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
&cache_module);
/* Setup cache_request_rec */
- cache = (cache_request_rec *) ap_get_module_config(r->request_config,
- &cache_module);
if (!cache) {
/* user likely configured CACHE_SAVE manually; they should really use
* mod_cache configuration to do that
*/
- cache = apr_pcalloc(r->pool, sizeof(cache_request_rec));
- ap_set_module_config(r->request_config, &cache_module, cache);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00767)
+ "CACHE/CACHE_SAVE filter enabled while caching is disabled, ignoring");
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
}
reason = NULL;
@@ -379,32 +742,11 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
return APR_SUCCESS;
}
- /* have we already run the cachability check and set up the
+ /* have we already run the cacheability check and set up the
* cached file handle?
*/
if (cache->in_checked) {
- /* pass the brigades into the cache, then pass them
- * up the filter stack
- */
- rv = cache->provider->store_body(cache->handle, r, in);
- if (rv != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
- "cache: Cache provider's store_body failed!");
- ap_remove_output_filter(f);
-
- /* give someone else the chance to cache the file */
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, NULL);
- }
- else {
-
- /* proactively remove the lock as soon as we see the eos bucket */
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, in);
-
- }
-
- return ap_pass_brigade(f->next, in);
+ return cache_save_store(f, in, conf, cache);
}
/*
@@ -415,6 +757,65 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* all. This section is* run before the above section.
*/
+ dconf = ap_get_module_config(r->per_dir_config, &cache_module);
+
+ /* RFC2616 13.8 Errors or Incomplete Response Cache Behavior:
+ * If a cache receives a 5xx response while attempting to revalidate an
+ * entry, it MAY either forward this response to the requesting client,
+ * or act as if the server failed to respond. In the latter case, it MAY
+ * return a previously received response unless the cached entry
+ * includes the "must-revalidate" cache-control directive (see section
+ * 14.9).
+ *
+ * This covers the case where an error was generated behind us, for example
+ * by a backend server via mod_proxy.
+ */
+ if (dconf->stale_on_error && r->status >= HTTP_INTERNAL_SERVER_ERROR) {
+
+ ap_remove_output_filter(cache->remove_url_filter);
+
+ if (cache->stale_handle
+ && !cache->stale_handle->cache_obj->info.control.must_revalidate
+ && !cache->stale_handle->cache_obj->info.control.proxy_revalidate) {
+ const char *warn_head;
+
+ /* morph the current save filter into the out filter, and serve from
+ * cache.
+ */
+ cache->handle = cache->stale_handle;
+ if (r->main) {
+ f->frec = cache_out_subreq_filter_handle;
+ }
+ else {
+ f->frec = cache_out_filter_handle;
+ }
+
+ r->headers_out = cache->stale_handle->resp_hdrs;
+
+ ap_set_content_type(r, apr_table_get(
+ cache->stale_handle->resp_hdrs, "Content-Type"));
+
+ /* add a revalidation warning */
+ warn_head = apr_table_get(r->err_headers_out, "Warning");
+ if ((warn_head == NULL) || ((warn_head != NULL)
+ && (ap_strstr_c(warn_head, "111") == NULL))) {
+ apr_table_mergen(r->err_headers_out, "Warning",
+ "111 Revalidation failed");
+ }
+
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT,
+ apr_psprintf(r->pool,
+ "cache hit: %d status; stale content returned",
+ r->status));
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, f->r, NULL);
+
+ /* pass brigade to our morphed out filter */
+ return ap_pass_brigade(f, in);
+ }
+ }
+
/* read expiry date; if a bad date, then leave it so the client can
* read it
*/
@@ -423,9 +824,7 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
exps = apr_table_get(r->headers_out, "Expires");
}
if (exps != NULL) {
- if (APR_DATE_BAD == (exp = apr_date_parse_http(exps))) {
- exps = NULL;
- }
+ exp = apr_date_parse_http(exps);
}
else {
exp = APR_DATE_BAD;
@@ -452,10 +851,27 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
etag = apr_table_get(r->headers_out, "Etag");
}
cc_out = apr_table_get(r->err_headers_out, "Cache-Control");
- if (cc_out == NULL) {
+ pragma = apr_table_get(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");
+ headers = r->headers_out;
}
+ /* Have we received a 304 response without any headers at all? Fall back to
+ * the original headers in the original cached request.
+ */
+ 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");
+ }
+
+ /* Parse the cache control header */
+ memset(&control, 0, sizeof(cache_control_t));
+ ap_cache_control(r, &control, cc_out, pragma, headers);
+
/*
* what responses should we not cache?
*
@@ -465,11 +881,13 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* They are tested here one by one to be clear and unambiguous.
*/
if (r->status != HTTP_OK && r->status != HTTP_NON_AUTHORITATIVE
+ && r->status != HTTP_PARTIAL_CONTENT
&& r->status != HTTP_MULTIPLE_CHOICES
&& r->status != HTTP_MOVED_PERMANENTLY
&& r->status != HTTP_NOT_MODIFIED) {
/* RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410
- * We don't cache 206, because we don't (yet) cache partial responses.
+ * We allow the caching of 206, but a cache implementation might choose
+ * to decline to cache a 206 if it doesn't know how to.
* We include 304 Not Modified here too as this is the origin server
* telling us to serve the cached copy.
*/
@@ -500,14 +918,21 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
/* if a broken Expires header is present, don't cache it */
reason = apr_pstrcat(p, "Broken expires header: ", exps, NULL);
}
- else if (exp != APR_DATE_BAD && exp < r->request_time)
- {
+ else if (!dconf->store_expired && exp != APR_DATE_BAD
+ && exp < r->request_time) {
/* if a Expires header is in the past, don't cache it */
- reason = "Expires header already expired, not cacheable";
- }
- else if (!conf->ignorequerystring && r->parsed_uri.query && exps == NULL &&
- !ap_cache_liststr(NULL, cc_out, "max-age", NULL) &&
- !ap_cache_liststr(NULL, cc_out, "s-maxage", NULL)) {
+ reason = "Expires header already expired; not cacheable";
+ }
+ else if (!dconf->store_expired && (control.must_revalidate
+ || control.proxy_revalidate) && (!control.s_maxage_value
+ || (!control.s_maxage && !control.max_age_value)) && lastmods
+ == NULL && etag == NULL) {
+ /* if we're already stale, but can never revalidate, don't cache it */
+ reason
+ = "s-maxage or max-age zero and no Last-Modified or Etag; not cacheable";
+ }
+ else if (!conf->ignorequerystring && r->parsed_uri.query && exps == NULL
+ && !control.max_age && !control.s_maxage) {
/* if a query string is present but no explicit expiration time,
* don't cache it (RFC 2616/13.9 & 13.2.1)
*/
@@ -520,10 +945,9 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
*/
reason = "HTTP Status 304 Not Modified";
}
- else if (r->status == HTTP_OK && lastmods == NULL && etag == NULL
- && (exps == NULL) && (conf->no_last_mod_ignore ==0) &&
- !ap_cache_liststr(NULL, cc_out, "max-age", NULL) &&
- !ap_cache_liststr(NULL, cc_out, "s-maxage", NULL)) {
+ else if (r->status == HTTP_OK && lastmods == NULL && etag == NULL && (exps
+ == NULL) && (dconf->no_last_mod_ignore == 0) && !control.max_age
+ && !control.s_maxage) {
/* 200 OK response from HTTP/1.0 and up without Last-Modified,
* Etag, Expires, Cache-Control:max-age, or Cache-Control:s-maxage
* headers.
@@ -531,38 +955,25 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
/* Note: mod-include clears last_modified/expires/etags - this
* is why we have an optional function for a key-gen ;-)
*/
- reason = "No Last-Modified, Etag, Expires, Cache-Control:max-age or Cache-Control:s-maxage headers";
+ reason = "No Last-Modified; Etag; Expires; Cache-Control:max-age or Cache-Control:s-maxage headers";
}
- else if (r->header_only && !cache->stale_handle) {
- /* Forbid HEAD requests unless we have it cached already */
- reason = "HTTP HEAD request";
- }
- else if (!conf->store_nostore &&
- ap_cache_liststr(NULL, cc_out, "no-store", NULL)) {
+ else if (!dconf->store_nostore && control.no_store) {
/* RFC2616 14.9.2 Cache-Control: no-store response
* indicating do not cache, or stop now if you are
* trying to cache it.
*/
- /* FIXME: The Cache-Control: no-store could have come in on a 304,
- * FIXME: while the original request wasn't conditional. IOW, we
- * FIXME: made the the request conditional earlier to revalidate
- * FIXME: our cached response.
- */
reason = "Cache-Control: no-store present";
}
- else if (!conf->store_private &&
- ap_cache_liststr(NULL, cc_out, "private", NULL)) {
+ else if (!dconf->store_private && control.private) {
/* RFC2616 14.9.1 Cache-Control: private response
* this object is marked for this user's eyes only. Behave
* as a tunnel.
*/
- /* FIXME: See above (no-store) */
reason = "Cache-Control: private present";
}
- else if (apr_table_get(r->headers_in, "Authorization") != NULL
- && !(ap_cache_liststr(NULL, cc_out, "s-maxage", NULL)
- || ap_cache_liststr(NULL, cc_out, "must-revalidate", NULL)
- || ap_cache_liststr(NULL, cc_out, "public", NULL))) {
+ else if (apr_table_get(r->headers_in, "Authorization")
+ && !(control.s_maxage || control.must_revalidate
+ || control.proxy_revalidate || control.public)) {
/* RFC2616 14.8 Authorisation:
* if authorisation is included in the request, we don't cache,
* but we can cache if the following exceptions are true:
@@ -572,9 +983,7 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
*/
reason = "Authorization required";
}
- else if (ap_cache_liststr(NULL,
- apr_table_get(r->headers_out, "Vary"),
- "*", NULL)) {
+ else if (ap_find_token(NULL, apr_table_get(r->headers_out, "Vary"), "*")) {
reason = "Vary header contains '*'";
}
else if (apr_table_get(r->subprocess_env, "no-cache") != NULL) {
@@ -619,29 +1028,59 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
APR_BRIGADE_INSERT_TAIL(bb, bkt);
}
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);
+
+ bkt = apr_bucket_eos_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
}
cache->block_response = 1;
+ /* 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",
+ reason));
+
/* let someone else attempt to cache */
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, NULL);
+ cache_remove_lock(conf, cache, r, NULL);
return ap_pass_brigade(f->next, bb);
}
if (reason) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "cache: %s not cached. Reason: %s", r->unparsed_uri,
- reason);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00768)
+ "cache: %s not cached. Reason: %s", r->unparsed_uri,
+ reason);
+
+ /* we've got a cache miss! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS,
+ reason);
/* remove this filter from the chain */
ap_remove_output_filter(f);
/* remove the lock file unconditionally */
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, NULL);
+ cache_remove_lock(conf, cache, r, NULL);
/* ship the data up the stack */
return ap_pass_brigade(f->next, in);
@@ -668,7 +1107,6 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* buckets and use their length to calculate the size
*/
int all_buckets_here=0;
- int unresolved_length = 0;
size=0;
for (e = APR_BRIGADE_FIRST(in);
e != APR_BRIGADE_SENTINEL(in);
@@ -679,7 +1117,6 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
break;
}
if (APR_BUCKET_IS_FLUSH(e)) {
- unresolved_length = 1;
continue;
}
if (e->length == (apr_size_t)-1) {
@@ -692,6 +1129,9 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
}
}
+ /* remember content length to check response size against later */
+ cache->size = size;
+
/* It's safe to cache the response.
*
* There are two possiblities at this point:
@@ -706,10 +1146,6 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
*/
/* Did we have a stale cache entry that really is stale?
- *
- * Note that for HEAD requests, we won't get the body, so for a stale
- * HEAD request, we don't remove the entity - instead we let the
- * CACHE_REMOVE_URL filter remove the stale item from the cache.
*/
if (cache->stale_handle) {
if (r->status == HTTP_NOT_MODIFIED) {
@@ -718,7 +1154,7 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
info = &cache->handle->cache_obj->info;
rv = OK;
}
- else if (!r->header_only) {
+ else {
/* Oh, well. Toss it. */
cache->provider->remove_entity(cache->stale_handle);
/* Treat the request as if it wasn't conditional. */
@@ -732,30 +1168,33 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
}
}
- /* no cache handle, create a new entity only for non-HEAD requests */
- if (!cache->handle && !r->header_only) {
- rv = cache_create_entity(r, size);
+ /* no cache handle, create a new entity */
+ if (!cache->handle) {
+ rv = cache_create_entity(cache, r, size, in);
info = apr_pcalloc(r->pool, sizeof(cache_info));
/* We only set info->status upon the initial creation. */
info->status = r->status;
}
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");
+
/* Caching layer declined the opportunity to cache the response */
ap_remove_output_filter(f);
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, NULL);
+ cache_remove_lock(conf, cache, r, NULL);
return ap_pass_brigade(f->next, in);
}
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "cache: Caching url: %s", r->unparsed_uri);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00769)
+ "cache: Caching url: %s", r->unparsed_uri);
/* We are actually caching this response. So it does not
* make sense to remove this entity any more.
*/
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "cache: Removing CACHE_REMOVE_URL filter.");
+ 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);
/*
@@ -768,6 +1207,9 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* too.
*/
+ /* store away the previously parsed cache control headers */
+ memcpy(&info->control, &control, sizeof(cache_control_t));
+
/* Read the date. Generate one if one is not supplied */
dates = apr_table_get(r->err_headers_out, "Date");
if (dates == NULL) {
@@ -797,15 +1239,13 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
if (lastmod != APR_DATE_BAD && lastmod > date) {
/* if it's in the future, then replace by date */
lastmod = date;
- lastmods = dates;
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
- r->server,
- "cache: Last modified is in the future, "
- "replacing with now");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0,
+ r, APLOGNO(00771) "cache: Last modified is in the future, "
+ "replacing with now");
}
/* if no expiry date then
- * if Cache-Control: max-age present
+ * if Cache-Control: max-age
* expiry date = date + max-age
* else if lastmod
* expiry date = date + min((date - lastmod) * factor, maxexpire)
@@ -813,22 +1253,23 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* expire date = date + defaultexpire
*/
if (exp == APR_DATE_BAD) {
- char *max_age_val;
- if (ap_cache_liststr(r->pool, cc_out, "max-age", &max_age_val) &&
- max_age_val != NULL) {
+ if (control.max_age) {
apr_int64_t x;
errno = 0;
- x = apr_atoi64(max_age_val);
+ x = control.max_age_value;
if (errno) {
- x = conf->defex;
+ x = dconf->defex;
}
else {
x = x * MSEC_ONE_SEC;
}
- if (x > conf->maxex) {
- x = conf->maxex;
+ if (x < dconf->minex) {
+ x = dconf->minex;
+ }
+ if (x > dconf->maxex) {
+ x = dconf->maxex;
}
exp = date + x;
}
@@ -837,15 +1278,18 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* an expiration time of now. This causes some problems with
* freshness calculations, so we choose the else path...
*/
- apr_time_t x = (apr_time_t) ((date - lastmod) * conf->factor);
+ apr_time_t x = (apr_time_t) ((date - lastmod) * dconf->factor);
- if (x > conf->maxex) {
- x = conf->maxex;
+ if (x < dconf->minex) {
+ x = dconf->minex;
+ }
+ if (x > dconf->maxex) {
+ x = dconf->maxex;
}
exp = date + x;
}
else {
- exp = date + conf->defex;
+ exp = date + dconf->defex;
}
}
info->expire = exp;
@@ -865,14 +1309,10 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* err_headers_out and we also need to strip any hop-by-hop
* headers that might have snuck in.
*/
- r->headers_out = apr_table_overlay(r->pool, r->headers_out,
- r->err_headers_out);
- r->headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out,
- r->server);
- apr_table_clear(r->err_headers_out);
+ r->headers_out = ap_cache_cacheable_headers_out(r);
/* Merge in our cached headers. However, keep any updated values. */
- ap_cache_accept_headers(cache->handle, r, 1);
+ cache_accept_headers(cache->handle, r, 1);
}
/* Write away header information to cache. It is possible that we are
@@ -895,6 +1335,13 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
apr_bucket *bkt;
int status;
+ /* We're just saving response headers, so we are done. Commit
+ * the response at this point, unless there was a previous error.
+ */
+ if (rv == APR_SUCCESS) {
+ rv = cache->provider->commit_entity(cache->handle, r);
+ }
+
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
/* Restore the original request headers and see if we need to
@@ -911,6 +1358,9 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
}
else {
cache->provider->recall_body(cache->handle, r->pool, bb);
+
+ bkt = apr_bucket_eos_create(bb->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, bkt);
}
cache->block_response = 1;
@@ -921,58 +1371,63 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in)
* the body it is safe to try and remove the url from the cache.
*/
if (rv != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
- "cache: updating headers with store_headers failed. "
- "Removing cached url.");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00772)
+ "cache: updating headers with store_headers failed. "
+ "Removing cached url.");
- rv = cache->provider->remove_url(cache->stale_handle, r->pool);
+ rv = cache->provider->remove_url(cache->stale_handle, r);
if (rv != OK) {
- /* Probably a mod_disk_cache cache area has been (re)mounted
+ /* Probably a mod_cache_disk cache area has been (re)mounted
* read-only, or that there is a permissions problem.
*/
- ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
- "cache: attempt to remove url from cache unsuccessful.");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00773)
+ "cache: attempt to remove url from cache unsuccessful.");
}
+ /* we've got a cache conditional hit! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out,
+ AP_CACHE_REVALIDATE,
+ "conditional cache hit: entity refresh failed");
+
+ }
+ else {
+
+ /* we've got a cache conditional hit! tell anyone who cares */
+ cache_run_cache_status(cache->handle, r, r->headers_out,
+ AP_CACHE_REVALIDATE,
+ "conditional cache hit: entity refreshed");
+
}
/* let someone else attempt to cache */
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, NULL);
+ cache_remove_lock(conf, cache, r, NULL);
return ap_pass_brigade(f->next, bb);
}
if (rv != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
- "cache: store_headers failed");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00774)
+ "cache: store_headers failed");
- ap_remove_output_filter(f);
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, NULL);
- return ap_pass_brigade(f->next, in);
- }
+ /* 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: store_headers failed");
- rv = cache->provider->store_body(cache->handle, r, in);
- if (rv != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
- "cache: store_body failed");
ap_remove_output_filter(f);
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, NULL);
+ cache_remove_lock(conf, cache, r, NULL);
return ap_pass_brigade(f->next, in);
}
- /* proactively remove the lock as soon as we see the eos bucket */
- ap_cache_remove_lock(conf, r, cache->handle ?
- (char *)cache->handle->cache_obj->key : NULL, in);
+ /* 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: attempting entity save");
- return ap_pass_brigade(f->next, in);
+ return cache_save_store(f, in, conf, cache);
}
/*
* CACHE_REMOVE_URL filter
- * ---------------
+ * -----------------------
*
* This filter gets added in the quick handler every time the CACHE_SAVE filter
* gets inserted. Its purpose is to remove a confirmed stale cache entry from
@@ -1005,23 +1460,309 @@ static int cache_remove_url_filter(ap_filter_t *f, apr_bucket_brigade *in)
* 1. Remove ourselves
* 2. Do nothing and bail out
*/
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
- "cache: CACHE_REMOVE_URL enabled unexpectedly");
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00775)
+ "cache: CACHE_REMOVE_URL enabled unexpectedly");
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, in);
}
/* Now remove this cache entry from the cache */
- cache_remove_url(cache, r->pool);
+ cache_remove_url(cache, r);
/* remove ourselves */
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, in);
}
+/*
+ * CACHE filter
+ * ------------
+ *
+ * This filter can be optionally inserted into the filter chain by the admin as
+ * a marker representing the precise location within the filter chain where
+ * caching is to be performed.
+ *
+ * When the filter chain is set up in the non-quick version of the URL handler,
+ * the CACHE filter is replaced by the CACHE_OUT or CACHE_SAVE filter,
+ * effectively inserting the caching filters at the point indicated by the
+ * admin. The CACHE filter is then removed.
+ *
+ * This allows caching to be performed before the content is passed to the
+ * INCLUDES filter, or to a filter that might perform transformations unique
+ * to the specific request and that would otherwise be non-cacheable.
+ */
+static int cache_filter(ap_filter_t *f, apr_bucket_brigade *in)
+{
+
+ cache_server_conf
+ *conf =
+ (cache_server_conf *) ap_get_module_config(f->r->server->module_config,
+ &cache_module);
+
+ /* was the quick handler enabled */
+ if (conf->quick) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(00776)
+ "cache: CACHE filter was added in quick handler mode and "
+ "will be ignored: %s", f->r->unparsed_uri);
+ }
+ /* otherwise we may have been bypassed, nothing to see here */
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(00777)
+ "cache: CACHE filter was added twice, or was added where "
+ "the cache has been bypassed and will be ignored: %s",
+ f->r->unparsed_uri);
+ }
+
+ /* we are just a marker, so let's just remove ourselves */
+ ap_remove_output_filter(f);
+ return ap_pass_brigade(f->next, in);
+}
+
+/**
+ * If configured, add the status of the caching attempt to the subprocess
+ * environment, and if configured, to headers in the response.
+ *
+ * The status is saved below the broad category of the status (hit, miss,
+ * revalidate), as well as a single cache-status key. This can be used for
+ * conditional logging.
+ *
+ * The status is optionally saved to an X-Cache header, and the detail of
+ * why a particular cache entry was cached (or not cached) is optionally
+ * saved to an X-Cache-Detail header. This extra detail is useful for
+ * service developers who may need to know whether their Cache-Control headers
+ * are working correctly.
+ */
+static int cache_status(cache_handle_t *h, request_rec *r,
+ apr_table_t *headers, ap_cache_status_e status, const char *reason)
+{
+ cache_server_conf
+ *conf =
+ (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_module);
+ int x_cache = 0, x_cache_detail = 0;
+
+ switch (status) {
+ case AP_CACHE_HIT: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_HIT_ENV, reason);
+ break;
+ }
+ case AP_CACHE_REVALIDATE: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_REVALIDATE_ENV, reason);
+ break;
+ }
+ case AP_CACHE_MISS: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_MISS_ENV, reason);
+ break;
+ }
+ case AP_CACHE_INVALIDATE: {
+ apr_table_setn(r->subprocess_env, AP_CACHE_INVALIDATE_ENV, reason);
+ break;
+ }
+ }
+
+ apr_table_setn(r->subprocess_env, AP_CACHE_STATUS_ENV, reason);
+
+ if (dconf && dconf->x_cache_set) {
+ x_cache = dconf->x_cache;
+ }
+ else {
+ 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));
+ }
+
+ if (dconf && dconf->x_cache_detail_set) {
+ x_cache_detail = dconf->x_cache_detail;
+ }
+ else {
+ x_cache_detail = conf->x_cache_detail;
+ }
+ if (x_cache_detail) {
+ apr_table_setn(headers, "X-Cache-Detail", apr_psprintf(r->pool,
+ "\"%s\" from %s", reason, r->server->server_hostname));
+ }
+
+ return OK;
+}
+
+/**
+ * If an error has occurred, but we have a stale cached entry, restore the
+ * filter stack from the save filter onwards. The canned error message will
+ * be discarded in the process, and replaced with the cached response.
+ */
+static void cache_insert_error_filter(request_rec *r)
+{
+ void *dummy;
+ cache_dir_conf *dconf;
+
+ /* ignore everything except for 5xx errors */
+ if (r->status < HTTP_INTERNAL_SERVER_ERROR) {
+ return;
+ }
+
+ dconf = ap_get_module_config(r->per_dir_config, &cache_module);
+
+ if (!dconf->stale_on_error) {
+ return;
+ }
+
+ /* RFC2616 13.8 Errors or Incomplete Response Cache Behavior:
+ * If a cache receives a 5xx response while attempting to revalidate an
+ * entry, it MAY either forward this response to the requesting client,
+ * or act as if the server failed to respond. In the latter case, it MAY
+ * return a previously received response unless the cached entry
+ * includes the "must-revalidate" cache-control directive (see section
+ * 14.9).
+ *
+ * This covers the case where the error was generated by our server via
+ * ap_die().
+ */
+ apr_pool_userdata_get(&dummy, CACHE_CTX_KEY, r->pool);
+ if (dummy) {
+ cache_request_rec *cache = (cache_request_rec *) dummy;
+
+ ap_remove_output_filter(cache->remove_url_filter);
+
+ 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) {
+ const char *warn_head;
+ cache_server_conf
+ *conf =
+ (cache_server_conf *) ap_get_module_config(r->server->module_config,
+ &cache_module);
+
+ /* morph the current save filter into the out filter, and serve from
+ * cache.
+ */
+ cache->handle = cache->stale_handle;
+ if (r->main) {
+ cache->save_filter->frec = cache_out_subreq_filter_handle;
+ }
+ else {
+ cache->save_filter->frec = cache_out_filter_handle;
+ }
+
+ r->output_filters = cache->save_filter;
+
+ r->err_headers_out = cache->stale_handle->resp_hdrs;
+
+ /* add a revalidation warning */
+ warn_head = apr_table_get(r->err_headers_out, "Warning");
+ if ((warn_head == NULL) || ((warn_head != NULL)
+ && (ap_strstr_c(warn_head, "111") == NULL))) {
+ apr_table_mergen(r->err_headers_out, "Warning",
+ "111 Revalidation failed");
+ }
+
+ cache_run_cache_status(
+ cache->handle,
+ r,
+ r->err_headers_out,
+ AP_CACHE_HIT,
+ apr_psprintf(
+ r->pool,
+ "cache hit: %d status; stale content returned",
+ r->status));
+
+ /* give someone else the chance to cache the file */
+ cache_remove_lock(conf, cache, r, NULL);
+
+ }
+ }
+
+ return;
+}
+
/* -------------------------------------------------------------- */
/* Setup configurable data */
+static void *create_dir_config(apr_pool_t *p, char *dummy)
+{
+ cache_dir_conf *dconf = apr_pcalloc(p, sizeof(cache_dir_conf));
+
+ dconf->no_last_mod_ignore = 0;
+ dconf->store_expired = 0;
+ dconf->store_private = 0;
+ dconf->store_nostore = 0;
+
+ /* maximum time to cache a document */
+ dconf->maxex = DEFAULT_CACHE_MAXEXPIRE;
+ dconf->minex = DEFAULT_CACHE_MINEXPIRE;
+ /* default time to cache a document */
+ dconf->defex = DEFAULT_CACHE_EXPIRE;
+
+ /* factor used to estimate Expires date from LastModified date */
+ dconf->factor = DEFAULT_CACHE_LMFACTOR;
+
+ dconf->x_cache = DEFAULT_X_CACHE;
+ dconf->x_cache_detail = DEFAULT_X_CACHE_DETAIL;
+
+ dconf->stale_on_error = DEFAULT_CACHE_STALE_ON_ERROR;
+
+ /* array of providers for this URL space */
+ dconf->cacheenable = apr_array_make(p, 10, sizeof(struct cache_enable));
+
+ return dconf;
+}
+
+static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) {
+ cache_dir_conf *new = (cache_dir_conf *) apr_pcalloc(p, sizeof(cache_dir_conf));
+ cache_dir_conf *add = (cache_dir_conf *) addv;
+ cache_dir_conf *base = (cache_dir_conf *) basev;
+
+ new->no_last_mod_ignore = (add->no_last_mod_ignore_set == 0) ? base->no_last_mod_ignore : add->no_last_mod_ignore;
+ new->no_last_mod_ignore_set = add->no_last_mod_ignore_set || base->no_last_mod_ignore_set;
+
+ new->store_expired = (add->store_expired_set == 0) ? base->store_expired : add->store_expired;
+ new->store_expired_set = add->store_expired_set || base->store_expired_set;
+ new->store_private = (add->store_private_set == 0) ? base->store_private : add->store_private;
+ new->store_private_set = add->store_private_set || base->store_private_set;
+ new->store_nostore = (add->store_nostore_set == 0) ? base->store_nostore : add->store_nostore;
+ new->store_nostore_set = add->store_nostore_set || base->store_nostore_set;
+
+ /* maximum time to cache a document */
+ new->maxex = (add->maxex_set == 0) ? base->maxex : add->maxex;
+ new->maxex_set = add->maxex_set || base->maxex_set;
+ new->minex = (add->minex_set == 0) ? base->minex : add->minex;
+ new->minex_set = add->minex_set || base->minex_set;
+
+ /* default time to cache a document */
+ new->defex = (add->defex_set == 0) ? base->defex : add->defex;
+ new->defex_set = add->defex_set || base->defex_set;
+
+ /* factor used to estimate Expires date from LastModified date */
+ new->factor = (add->factor_set == 0) ? base->factor : add->factor;
+ new->factor_set = add->factor_set || base->factor_set;
+
+ new->x_cache = (add->x_cache_set == 0) ? base->x_cache : add->x_cache;
+ new->x_cache_set = add->x_cache_set || base->x_cache_set;
+ new->x_cache_detail = (add->x_cache_detail_set == 0) ? base->x_cache_detail
+ : add->x_cache_detail;
+ new->x_cache_detail_set = add->x_cache_detail_set
+ || base->x_cache_detail_set;
+
+ new->stale_on_error = (add->stale_on_error_set == 0) ? base->stale_on_error
+ : add->stale_on_error;
+ new->stale_on_error_set = add->stale_on_error_set
+ || base->stale_on_error_set;
+
+ new->cacheenable = add->enable_set ? apr_array_append(p, base->cacheenable,
+ add->cacheenable) : base->cacheenable;
+ new->enable_set = add->enable_set || base->enable_set;
+ new->disable = (add->disable_set == 0) ? base->disable : add->disable;
+ new->disable_set = add->disable_set || base->disable_set;
+
+ return new;
+}
+
static void * create_cache_config(apr_pool_t *p, server_rec *s)
{
const char *tmppath;
@@ -1031,29 +1772,17 @@ static void * create_cache_config(apr_pool_t *p, server_rec *s)
ps->cacheenable = apr_array_make(p, 10, sizeof(struct cache_enable));
/* array of URL prefixes for which caching is disabled */
ps->cachedisable = apr_array_make(p, 10, sizeof(struct cache_disable));
- /* maximum time to cache a document */
- ps->maxex = DEFAULT_CACHE_MAXEXPIRE;
- ps->maxex_set = 0;
- /* default time to cache a document */
- ps->defex = DEFAULT_CACHE_EXPIRE;
- ps->defex_set = 0;
- /* factor used to estimate Expires date from LastModified date */
- ps->factor = DEFAULT_CACHE_LMFACTOR;
- ps->factor_set = 0;
- ps->no_last_mod_ignore_set = 0;
- ps->no_last_mod_ignore = 0;
ps->ignorecachecontrol = 0;
ps->ignorecachecontrol_set = 0;
- ps->store_private = 0;
- ps->store_private_set = 0;
- ps->store_nostore = 0;
- ps->store_nostore_set = 0;
/* array of headers that should not be stored in cache */
ps->ignore_headers = apr_array_make(p, 10, sizeof(char *));
ps->ignore_headers_set = CACHE_IGNORE_HEADERS_UNSET;
/* flag indicating that query-string should be ignored when caching */
ps->ignorequerystring = 0;
ps->ignorequerystring_set = 0;
+ /* by default, run in the quick handler */
+ ps->quick = 1;
+ ps->quick_set = 0;
/* array of identifiers that should not be used for key calculation */
ps->ignore_session_id = apr_array_make(p, 10, sizeof(char *));
ps->ignore_session_id_set = CACHE_IGNORE_SESSION_ID_UNSET;
@@ -1064,6 +1793,8 @@ static void * create_cache_config(apr_pool_t *p, server_rec *s)
ps->lockpath = apr_pstrcat(p, tmppath, DEFAULT_CACHE_LOCKPATH, NULL);
}
ps->lockmaxage = apr_time_from_sec(DEFAULT_CACHE_MAXAGE);
+ ps->x_cache = DEFAULT_X_CACHE;
+ ps->x_cache_detail = DEFAULT_X_CACHE_DETAIL;
return ps;
}
@@ -1081,30 +1812,11 @@ static void * merge_cache_config(apr_pool_t *p, void *basev, void *overridesv)
ps->cacheenable = apr_array_append(p,
base->cacheenable,
overrides->cacheenable);
- /* maximum time to cache a document */
- ps->maxex = (overrides->maxex_set == 0) ? base->maxex : overrides->maxex;
- /* default time to cache a document */
- ps->defex = (overrides->defex_set == 0) ? base->defex : overrides->defex;
- /* factor used to estimate Expires date from LastModified date */
- ps->factor =
- (overrides->factor_set == 0) ? base->factor : overrides->factor;
- ps->no_last_mod_ignore =
- (overrides->no_last_mod_ignore_set == 0)
- ? base->no_last_mod_ignore
- : overrides->no_last_mod_ignore;
ps->ignorecachecontrol =
(overrides->ignorecachecontrol_set == 0)
? base->ignorecachecontrol
: overrides->ignorecachecontrol;
- ps->store_private =
- (overrides->store_private_set == 0)
- ? base->store_private
- : overrides->store_private;
- ps->store_nostore =
- (overrides->store_nostore_set == 0)
- ? base->store_nostore
- : overrides->store_nostore;
ps->ignore_headers =
(overrides->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET)
? base->ignore_headers
@@ -1129,18 +1841,47 @@ static void * merge_cache_config(apr_pool_t *p, void *basev, void *overridesv)
(overrides->lockmaxage_set == 0)
? base->lockmaxage
: overrides->lockmaxage;
+ ps->quick =
+ (overrides->quick_set == 0)
+ ? base->quick
+ : overrides->quick;
+ ps->x_cache =
+ (overrides->x_cache_set == 0)
+ ? base->x_cache
+ : overrides->x_cache;
+ ps->x_cache_detail =
+ (overrides->x_cache_detail_set == 0)
+ ? base->x_cache_detail
+ : overrides->x_cache_detail;
+ ps->base_uri =
+ (overrides->base_uri_set == 0)
+ ? base->base_uri
+ : overrides->base_uri;
return ps;
}
-static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy,
- int flag)
+
+static const char *set_cache_quick_handler(cmd_parms *parms, void *dummy,
+ int flag)
{
cache_server_conf *conf;
conf =
- (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config
+,
&cache_module);
- conf->no_last_mod_ignore = flag;
- conf->no_last_mod_ignore_set = 1;
+ conf->quick = flag;
+ conf->quick_set = 1;
+ return NULL;
+
+}
+
+static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->no_last_mod_ignore = flag;
+ dconf->no_last_mod_ignore_set = 1;
return NULL;
}
@@ -1158,29 +1899,33 @@ static const char *set_cache_ignore_cachecontrol(cmd_parms *parms,
return NULL;
}
+static const char *set_cache_store_expired(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->store_expired = flag;
+ dconf->store_expired_set = 1;
+ return NULL;
+}
+
static const char *set_cache_store_private(cmd_parms *parms, void *dummy,
int flag)
{
- cache_server_conf *conf;
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
- conf =
- (cache_server_conf *)ap_get_module_config(parms->server->module_config,
- &cache_module);
- conf->store_private = flag;
- conf->store_private_set = 1;
+ dconf->store_private = flag;
+ dconf->store_private_set = 1;
return NULL;
}
static const char *set_cache_store_nostore(cmd_parms *parms, void *dummy,
int flag)
{
- cache_server_conf *conf;
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
- conf =
- (cache_server_conf *)ap_get_module_config(parms->server->module_config,
- &cache_module);
- conf->store_nostore = flag;
- conf->store_nostore_set = 1;
+ dconf->store_nostore = flag;
+ dconf->store_nostore_set = 1;
return NULL;
}
@@ -1193,7 +1938,7 @@ static const char *add_ignore_header(cmd_parms *parms, void *dummy,
conf =
(cache_server_conf *)ap_get_module_config(parms->server->module_config,
&cache_module);
- if (!strncasecmp(header, "None", 4)) {
+ if (!strcasecmp(header, "None")) {
/* if header None is listed clear array */
conf->ignore_headers->nelts = 0;
}
@@ -1221,7 +1966,7 @@ static const char *add_ignore_session_id(cmd_parms *parms, void *dummy,
conf =
(cache_server_conf *)ap_get_module_config(parms->server->module_config,
&cache_module);
- if (!strncasecmp(identifier, "None", 4)) {
+ if (!strcasecmp(identifier, "None")) {
/* if identifier None is listed clear array */
conf->ignore_session_id->nelts = 0;
}
@@ -1244,19 +1989,46 @@ static const char *add_cache_enable(cmd_parms *parms, void *dummy,
const char *type,
const char *url)
{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
cache_server_conf *conf;
struct cache_enable *new;
+ const char *err = ap_check_cmd_context(parms,
+ NOT_IN_DIRECTORY|NOT_IN_LIMIT|NOT_IN_FILES);
+ if (err != NULL) {
+ return err;
+ }
+
if (*type == '/') {
return apr_psprintf(parms->pool,
"provider (%s) starts with a '/'. Are url and provider switched?",
type);
}
+ if (!url) {
+ url = parms->path;
+ }
+ if (!url) {
+ return apr_psprintf(parms->pool,
+ "CacheEnable provider (%s) is missing an URL.", type);
+ }
+ if (parms->path && strncmp(parms->path, url, strlen(parms->path))) {
+ return "When in a Location, CacheEnable must specify a path or an URL below "
+ "that location.";
+ }
+
conf =
(cache_server_conf *)ap_get_module_config(parms->server->module_config,
&cache_module);
- new = apr_array_push(conf->cacheenable);
+
+ if (parms->path) {
+ new = apr_array_push(dconf->cacheenable);
+ dconf->enable_set = 1;
+ }
+ else {
+ new = apr_array_push(conf->cacheenable);
+ }
+
new->type = type;
if (apr_uri_parse(parms->pool, url, &(new->url))) {
return NULL;
@@ -1273,12 +2045,35 @@ static const char *add_cache_enable(cmd_parms *parms, void *dummy,
static const char *add_cache_disable(cmd_parms *parms, void *dummy,
const char *url)
{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
cache_server_conf *conf;
struct cache_disable *new;
+ const char *err = ap_check_cmd_context(parms,
+ NOT_IN_DIRECTORY|NOT_IN_LIMIT|NOT_IN_FILES);
+ if (err != NULL) {
+ return err;
+ }
+
conf =
(cache_server_conf *)ap_get_module_config(parms->server->module_config,
&cache_module);
+
+ if (parms->path) {
+ if (!strcmp(url, "on")) {
+ dconf->disable = 1;
+ dconf->disable_set = 1;
+ return NULL;
+ }
+ else {
+ return "CacheDisable must be followed by the word 'on' when in a Location.";
+ }
+ }
+
+ if (!url || (url[0] != '/' && !ap_strchr_c(url, ':'))) {
+ return "CacheDisable must specify a path or an URL.";
+ }
+
new = apr_array_push(conf->cachedisable);
if (apr_uri_parse(parms->pool, url, &(new->url))) {
return NULL;
@@ -1295,43 +2090,44 @@ static const char *add_cache_disable(cmd_parms *parms, void *dummy,
static const char *set_cache_maxex(cmd_parms *parms, void *dummy,
const char *arg)
{
- cache_server_conf *conf;
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
- conf =
- (cache_server_conf *)ap_get_module_config(parms->server->module_config,
- &cache_module);
- conf->maxex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
- conf->maxex_set = 1;
+ dconf->maxex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ dconf->maxex_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_minex(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->minex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ dconf->minex_set = 1;
return NULL;
}
static const char *set_cache_defex(cmd_parms *parms, void *dummy,
const char *arg)
{
- cache_server_conf *conf;
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
- conf =
- (cache_server_conf *)ap_get_module_config(parms->server->module_config,
- &cache_module);
- conf->defex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
- conf->defex_set = 1;
+ dconf->defex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC);
+ dconf->defex_set = 1;
return NULL;
}
static const char *set_cache_factor(cmd_parms *parms, void *dummy,
const char *arg)
{
- cache_server_conf *conf;
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
double val;
- conf =
- (cache_server_conf *)ap_get_module_config(parms->server->module_config,
- &cache_module);
if (sscanf(arg, "%lg", &val) != 1) {
return "CacheLastModifiedFactor value must be a float";
}
- conf->factor = val;
- conf->factor_set = 1;
+ dconf->factor = val;
+ dconf->factor_set = 1;
return NULL;
}
@@ -1397,6 +2193,84 @@ static const char *set_cache_lock_maxage(cmd_parms *parms, void *dummy,
return NULL;
}
+static const char *set_cache_x_cache(cmd_parms *parms, void *dummy, int flag)
+{
+
+ if (parms->path) {
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->x_cache = flag;
+ dconf->x_cache_set = 1;
+
+ }
+ else {
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+
+ conf->x_cache = flag;
+ conf->x_cache_set = 1;
+
+ }
+
+ return NULL;
+}
+
+static const char *set_cache_x_cache_detail(cmd_parms *parms, void *dummy, int flag)
+{
+
+ if (parms->path) {
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->x_cache_detail = flag;
+ dconf->x_cache_detail_set = 1;
+
+ }
+ else {
+ cache_server_conf *conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+
+ conf->x_cache_detail = flag;
+ conf->x_cache_detail_set = 1;
+
+ }
+
+ return NULL;
+}
+
+static const char *set_cache_key_base_url(cmd_parms *parms, void *dummy,
+ const char *arg)
+{
+ cache_server_conf *conf;
+ apr_status_t rv;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->base_uri = apr_pcalloc(parms->pool, sizeof(apr_uri_t));
+ rv = apr_uri_parse(parms->pool, arg, conf->base_uri);
+ if (rv != APR_SUCCESS) {
+ return apr_psprintf(parms->pool, "Could not parse '%s' as an URL.", arg);
+ }
+ else if (!conf->base_uri->scheme && !conf->base_uri->hostname &&
+ !conf->base_uri->port_str) {
+ return apr_psprintf(parms->pool, "URL '%s' must contain at least one of a scheme, a hostname or a port.", arg);
+ }
+ conf->base_uri_set = 1;
+ return NULL;
+}
+
+static const char *set_cache_stale_on_error(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_dir_conf *dconf = (cache_dir_conf *)dummy;
+
+ dconf->stale_on_error = flag;
+ dconf->stale_on_error_set = 1;
+ return NULL;
+}
+
static int cache_post_config(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
@@ -1422,26 +2296,35 @@ static const command_rec cache_cmds[] =
* This is more intuitive that requiring a LoadModule directive.
*/
- AP_INIT_TAKE2("CacheEnable", add_cache_enable, NULL, RSRC_CONF,
- "A cache type and partial URL prefix below which "
- "caching is enabled"),
- AP_INIT_TAKE1("CacheDisable", add_cache_disable, NULL, RSRC_CONF,
+ AP_INIT_TAKE12("CacheEnable", add_cache_enable, NULL, RSRC_CONF|ACCESS_CONF,
+ "A cache type and partial URL prefix below which "
+ "caching is enabled"),
+ AP_INIT_TAKE1("CacheDisable", add_cache_disable, NULL, RSRC_CONF|ACCESS_CONF,
"A partial URL prefix below which caching is disabled"),
- AP_INIT_TAKE1("CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF,
+ AP_INIT_TAKE1("CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF|ACCESS_CONF,
"The maximum time in seconds to cache a document"),
- AP_INIT_TAKE1("CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF,
+ AP_INIT_TAKE1("CacheMinExpire", set_cache_minex, NULL, RSRC_CONF|ACCESS_CONF,
+ "The minimum time in seconds to cache a document"),
+ AP_INIT_TAKE1("CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF|ACCESS_CONF,
"The default time in seconds to cache a document"),
- AP_INIT_FLAG("CacheIgnoreNoLastMod", set_cache_ignore_no_last_mod, NULL,
+ AP_INIT_FLAG("CacheQuickHandler", set_cache_quick_handler, NULL,
RSRC_CONF,
+ "Run the cache in the quick handler, default on"),
+ AP_INIT_FLAG("CacheIgnoreNoLastMod", set_cache_ignore_no_last_mod, NULL,
+ RSRC_CONF|ACCESS_CONF,
"Ignore Responses where there is no Last Modified Header"),
AP_INIT_FLAG("CacheIgnoreCacheControl", set_cache_ignore_cachecontrol,
NULL, RSRC_CONF,
"Ignore requests from the client for uncached content"),
+ AP_INIT_FLAG("CacheStoreExpired", set_cache_store_expired,
+ NULL, RSRC_CONF|ACCESS_CONF,
+ "Ignore expiration dates when populating cache, resulting in "
+ "an If-Modified-Since request to the backend on retrieval"),
AP_INIT_FLAG("CacheStorePrivate", set_cache_store_private,
- NULL, RSRC_CONF,
+ NULL, RSRC_CONF|ACCESS_CONF,
"Ignore 'Cache-Control: private' and store private content"),
AP_INIT_FLAG("CacheStoreNoStore", set_cache_store_nostore,
- NULL, RSRC_CONF,
+ NULL, RSRC_CONF|ACCESS_CONF,
"Ignore 'Cache-Control: no-store' and store sensitive content"),
AP_INIT_ITERATE("CacheIgnoreHeaders", add_ignore_header, NULL, RSRC_CONF,
"A space separated list of headers that should not be "
@@ -1453,7 +2336,7 @@ static const command_rec cache_cmds[] =
NULL, RSRC_CONF, "A space separated list of session "
"identifiers that should be ignored for creating the key "
"of the cached entity."),
- AP_INIT_TAKE1("CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF,
+ AP_INIT_TAKE1("CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF|ACCESS_CONF,
"The factor used to estimate Expires date from "
"LastModified date"),
AP_INIT_FLAG("CacheLock", set_cache_lock,
@@ -1465,14 +2348,30 @@ static const command_rec cache_cmds[] =
"temp directory."),
AP_INIT_TAKE1("CacheLockMaxAge", set_cache_lock_maxage, NULL, RSRC_CONF,
"Maximum age of any thundering herd lock."),
+ AP_INIT_FLAG("CacheHeader", set_cache_x_cache, NULL, RSRC_CONF | ACCESS_CONF,
+ "Add a X-Cache header to responses. Default is off."),
+ AP_INIT_FLAG("CacheDetailHeader", set_cache_x_cache_detail, NULL,
+ RSRC_CONF | ACCESS_CONF,
+ "Add a X-Cache-Detail header to responses. Default is off."),
+ AP_INIT_TAKE1("CacheKeyBaseURL", set_cache_key_base_url, NULL, RSRC_CONF,
+ "Override the base URL of reverse proxied cache keys."),
+ AP_INIT_FLAG("CacheStaleOnError", set_cache_stale_on_error,
+ NULL, RSRC_CONF|ACCESS_CONF,
+ "Serve stale content on 5xx errors if present. Defaults to on."),
{NULL}
};
static void register_hooks(apr_pool_t *p)
{
/* cache initializer */
+ /* cache quick handler */
+ ap_hook_quick_handler(cache_quick_handler, NULL, NULL, APR_HOOK_FIRST);
/* cache handler */
- ap_hook_quick_handler(cache_url_handler, NULL, NULL, APR_HOOK_FIRST);
+ ap_hook_handler(cache_handler, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ /* cache status */
+ cache_hook_cache_status(cache_status, NULL, NULL, APR_HOOK_MIDDLE);
+ /* cache error handler */
+ ap_hook_insert_error_filter(cache_insert_error_filter, NULL, NULL, APR_HOOK_MIDDLE);
/* cache filters
* XXX The cache filters need to run right after the handlers and before
* any other filters. Consider creating AP_FTYPE_CACHE for this purpose.
@@ -1486,9 +2385,21 @@ static void register_hooks(apr_pool_t *p)
* to be run by subrequest
*/
/*
+ * CACHE is placed into the filter chain at an admin specified location,
+ * and when the cache_handler is run, the CACHE filter is swapped with
+ * the CACHE_OUT filter, or CACHE_SAVE filter as appropriate. This has
+ * the effect of offering optional fine control of where the cache is
+ * inserted into the filter chain.
+ */
+ cache_filter_handle =
+ ap_register_output_filter("CACHE",
+ cache_filter,
+ NULL,
+ AP_FTYPE_RESOURCE);
+ /*
* CACHE_SAVE must go into the filter chain after a possible DEFLATE
* filter to ensure that the compressed content is stored.
- * Incrementing filter type by 1 ensures his happens.
+ * Incrementing filter type by 1 ensures this happens.
*/
cache_save_filter_handle =
ap_register_output_filter("CACHE_SAVE",
@@ -1509,7 +2420,7 @@ static void register_hooks(apr_pool_t *p)
* CACHE_OUT must go into the filter chain after a possible DEFLATE
* filter to ensure that already compressed cache objects do not
* get compressed again. Incrementing filter type by 1 ensures
- * his happens.
+ * this happens.
*/
cache_out_filter_handle =
ap_register_output_filter("CACHE_OUT",
@@ -1538,13 +2449,23 @@ static void register_hooks(apr_pool_t *p)
ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
}
-module AP_MODULE_DECLARE_DATA cache_module =
+AP_DECLARE_MODULE(cache) =
{
STANDARD20_MODULE_STUFF,
- NULL, /* create per-directory config structure */
- NULL, /* merge per-directory config structures */
+ create_dir_config, /* create per-directory config structure */
+ merge_dir_config, /* merge per-directory config structures */
create_cache_config, /* create per-server config structure */
merge_cache_config, /* merge per-server config structures */
cache_cmds, /* command apr_table_t */
register_hooks
};
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(cache_status)
+)
+
+APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(cache, CACHE, int, cache_status,
+ (cache_handle_t *h, request_rec *r,
+ apr_table_t *headers, ap_cache_status_e status,
+ const char *reason), (h, r, headers, status, reason),
+ OK, DECLINED)