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 */

Reply via email to