Okay, because of the quirky behavior of a 'sometimes' cached page, this
one had me going in circles for a little while.
What this patch does, is add a new command to mod_cache,
'CacheRunAfterOthers_RenameThisCmd'[1]
The effect this has is to run mod_cache as a normal handler, instead of
a Quick Handler. This means all the other Translate Name, Map to
Storage, and Fixup hooks are ran. These include mod_rewrite and
mod_alias, among others.
What this allows, is configurations like the following:
CacheEnable disk /
CacheRoot /tmp/cacheroot
RewriteEngine On
CacheIgnoreNoLastMod on
CacheDefaultExpire 90
CacheMaxExpire 90
RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*\sFirefox/1\.0\.[1234]\b
RewriteRule .* http://www.mozilla.org/products/firefox/upgrade/ [R=302,L]
Since mod_cache is ran as a quick handler, once the / page was cached,
the RewriteRule would never be ran. So, after the first non-Firefox
Client connected, all clients, even those that are Firefox, would get a
cached page, not the redirect.
Add this:
CacheRunAfterOthers_RenameThisCmd on
And now it works. It does take a slight performance hit, but this is
nothing compared to using a mod_proxy backend.
Moving the Cache handler to be ran as an APR_HOOK_REALLY_FIRST means
mod_rewrite will get it's chance to rewrite it, but it will still run
before a really expensive handler, like a Proxy Backend or a PHP Script.
This won't be useful to everyone, but for most configurations, the
initial hooks are insignificant compared to the Content Handler, so
allowing mod_cache to be called like this still makes sense.
I have lightly tested the attached patch, and it fixes my test cases.
Some details on this are also in PR 34885:
http://issues.apache.org/bugzilla/show_bug.cgi?id=34885
Thoughts/Comments/Code Review... all welcome.
-Paul
[1] Yes, I did name it 'CacheRunAfterOthers_RenameThisCmd'. I hope that
before this patch is committed, someone thinks of a good name for this
behavior, because I can't think of one.
Index: modules/cache/mod_cache.c
===================================================================
--- modules/cache/mod_cache.c (revision 169788)
+++ modules/cache/mod_cache.c (working copy)
@@ -45,7 +45,8 @@
* oh well.
*/
-static int cache_url_handler(request_rec *r, int lookup)
+static int cache_url_real_handler(request_rec *r, int lookup,
+ cache_server_conf *conf, int is_quick)
{
apr_status_t rv;
const char *auth;
@@ -55,22 +56,13 @@
cache_provider_list *providers;
cache_info *info;
cache_request_rec *cache;
- cache_server_conf *conf;
apr_bucket_brigade *out;
- /* Delay initialization until we know we are handling a GET */
- if (r->method_number != M_GET) {
- return DECLINED;
- }
-
uri = r->parsed_uri;
url = r->unparsed_uri;
path = uri.path;
info = NULL;
- conf = (cache_server_conf *) ap_get_module_config(r->server->module_config,
- &cache_module);
-
/*
* Which cache module (if any) should handle this request?
*/
@@ -156,9 +148,12 @@
/* We are in the quick handler hook, which means that no output
* filters have been set. So lets run the insert_filter hook.
*/
- ap_run_insert_filter(r);
+ if (is_quick) {
+ ap_run_insert_filter(r);
+ }
+
ap_add_output_filter_handle(cache_out_filter_handle, NULL,
- r, r->connection);
+ r, r->connection);
/* kick off the filter stack */
out = apr_brigade_create(r->pool, r->connection->bucket_alloc);
@@ -174,6 +169,45 @@
return OK;
}
+static int cache_url_quick_handler(request_rec *r, int lookup)
+{
+ 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);
+
+ if (conf->run_handler_later) {
+ return DECLINED;
+ }
+
+ return cache_url_real_handler(r, lookup, conf, 1);
+}
+
+static int cache_url_late_handler(request_rec *r)
+{
+ 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);
+
+ if (conf->run_handler_later) {
+ return cache_url_real_handler(r, 0, conf, 0);
+ }
+
+ return DECLINED;
+}
+
+
/*
* CACHE_OUT filter
* ----------------
@@ -742,6 +776,7 @@
/* 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;
+ ps->run_handler_later = 0;
return ps;
}
@@ -787,6 +822,10 @@
(overrides->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET)
? base->ignore_headers
: overrides->ignore_headers;
+ ps->run_handler_later =
+ (overrides->run_handler_later == 0)
+ ? base->run_handler_later
+ : overrides->run_handler_later;
return ps;
}
static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy,
@@ -829,6 +868,18 @@
return NULL;
}
+static const char *set_cache_run_later(cmd_parms *parms, void *dummy,
+ int flag)
+{
+ cache_server_conf *conf;
+
+ conf =
+ (cache_server_conf *)ap_get_module_config(parms->server->module_config,
+ &cache_module);
+ conf->run_handler_later = flag;
+ return NULL;
+}
+
static const char *set_cache_store_nostore(cmd_parms *parms, void *dummy,
int flag)
{
@@ -993,6 +1044,9 @@
AP_INIT_FLAG("CacheStorePrivate", set_cache_store_private,
NULL, RSRC_CONF,
"Ignore 'Cache-Control: private' and store private content"),
+ AP_INIT_FLAG("CacheRunAfterOthers_RenameThisCmd", set_cache_run_later,
+ NULL, RSRC_CONF,
+ "Run the Cache Handler as a Normal Handler."),
AP_INIT_FLAG("CacheStoreNoStore", set_cache_store_nostore,
NULL, RSRC_CONF,
"Ignore 'Cache-Control: no-store' and store sensitive
content"),
@@ -1007,9 +1061,12 @@
static void register_hooks(apr_pool_t *p)
{
- /* cache initializer */
- /* cache handler */
- ap_hook_quick_handler(cache_url_handler, NULL, NULL, APR_HOOK_FIRST);
+ /* cache handlers */
+ ap_hook_quick_handler(cache_url_quick_handler, NULL, NULL,
+ APR_HOOK_FIRST);
+ ap_hook_handler(cache_url_late_handler, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+
/* 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.
@@ -1030,6 +1087,8 @@
cache_out_filter,
NULL,
AP_FTYPE_CONTENT_SET-1);
+
+ /* cache initializer */
ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
}
Index: modules/cache/mod_cache.h
===================================================================
--- modules/cache/mod_cache.h (revision 169788)
+++ modules/cache/mod_cache.h (working copy)
@@ -145,6 +145,8 @@
#define CACHE_IGNORE_HEADERS_SET 1
#define CACHE_IGNORE_HEADERS_UNSET 0
int ignore_headers_set;
+ /** Flag to disable the Quick Handler, and run after meta-hooks */
+ int run_handler_later;
} cache_server_conf;
/* cache info information */