]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Replace automatic ETag generation with configurable algorithm
authorKen Coar <coar@apache.org>
Sat, 5 Jan 2002 17:13:03 +0000 (17:13 +0000)
committerKen Coar <coar@apache.org>
Sat, 5 Jan 2002 17:13:03 +0000 (17:13 +0000)
defined by FileETag directive.  Keywords are All, None,
[+|-]INode, [+|-]Size, [+|-]MTime.  Server farms with content
fanout may want to use 'FileETag MTime Size' to keep the system-
variant inode from being included and busting caches.
PR: 7010
Submitted by: Based on an idea proposed by Phil Dietz

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x@92738 13f79535-47bb-0310-9956-ffa450edef68

src/CHANGES
src/include/http_core.h
src/main/http_core.c
src/main/http_protocol.c

index 7d91c1ff096e10b0720897fdbc4197e4c719c1cc..3556cf12e919dfbe31ca87fbbd5764476e66d475 100644 (file)
@@ -1,5 +1,13 @@
 Changes with Apache 1.3.23
 
+  *) Add FileETag directive to control fields used when constructing
+     an ETag for a file-based resource.  Historically the inode,
+     size, and mtimehave been used, but the inode factor broke
+     caching for systems with content fan-out across multiple
+     back-end servers.  Now the fields used in the construction
+     can be controlled by configuration directives.
+     [Ken Coar, from a patch by Phil Dietz]
+
   *) NetWare: Fixed the access forbidden problem when requesting an
      empty directory rather than showing the empty listing.  
      [Charles Goldman, Guenter Knauf <gk@gknw.de>]
index 1635c9ee759a84792dbcd928e20727b093945ac8..83586de961d5861590a651fa80fdcc19920efe0c 100644 (file)
@@ -180,6 +180,21 @@ extern API_VAR_EXPORT module core_module;
 
 typedef unsigned char allow_options_t;
 typedef unsigned char overrides_t;
+/*
+ * Bits of info that go into making an ETag for a file
+ * document.  Why a long?  Because char historically
+ * proved too short for Options, and int can be different
+ * sizes on different platforms.
+ */
+typedef unsigned long etag_components_t;
+
+#define ETAG_UNSET 0
+#define ETAG_NONE  (1 << 0)
+#define ETAG_MTIME (1 << 1)
+#define ETAG_INODE (1 << 2)
+#define ETAG_SIZE  (1 << 3)
+#define ETAG_BACKWARD (ETAG_MTIME | ETAG_INODE | ETAG_SIZE)
+#define ETAG_ALL   (ETAG_MTIME | ETAG_INODE | ETAG_SIZE)
 
 typedef struct {
     /* path of the directory/regex/etc.  see also d_is_fnmatch below */
@@ -309,6 +324,13 @@ typedef struct {
 #endif
 #endif /* CHARSET_EBCDIC */
 
+    /*
+     * What attributes/data should be included in ETag generation?
+     */
+    etag_components_t etag_bits;
+    etag_components_t etag_add;
+    etag_components_t etag_remove;
+
 } core_dir_config;
 
 /* Per-server core configuration */
index b35880c617b785a2218dcfebc90fc0c8f4da21dd..bd2e3e3eb18ee124e5bbca0c4b55c82236086d60 100644 (file)
@@ -170,6 +170,13 @@ static void *create_core_dir_config(pool *a, char *dir)
 #endif
 #endif /* CHARSET_EBCDIC */
 
+    /*
+     * Flag for use of inodes in ETags.
+     */
+    conf->etag_bits = ETAG_UNSET;
+    conf->etag_add = ETAG_UNSET;
+    conf->etag_remove = ETAG_UNSET;
+
     return (void *)conf;
 }
 
@@ -194,7 +201,7 @@ static void *merge_core_dir_configs(pool *a, void *basev, void *newv)
     conf->d_is_fnmatch = new->d_is_fnmatch;
     conf->d_components = new->d_components;
     conf->r = new->r;
-    
+
     if (new->opts & OPT_UNSET) {
        /* there was no explicit setting of new->opts, so we merge
         * preserve the invariant (opts_add & opts_remove) == 0
@@ -319,6 +326,26 @@ static void *merge_core_dir_configs(pool *a, void *basev, void *newv)
 #endif
 #endif /* CHARSET_EBCDIC */
 
+    /*
+     * Now merge the setting of the FileETag directive.
+     */
+    if (new->etag_bits == ETAG_UNSET) {
+        conf->etag_add =
+            (conf->etag_add & (~ new->etag_remove)) | new->etag_add;
+        conf->etag_remove =
+            (conf->opts_remove & (~ new->etag_add)) | new->etag_remove;
+        conf->etag_bits =
+            (conf->etag_bits & (~ conf->etag_remove)) | conf->etag_add;
+    }
+    else {
+        conf->etag_bits = new->etag_bits;
+        conf->etag_add = new->etag_add;
+        conf->etag_remove = new->etag_remove;
+    }
+    if (conf->etag_bits != ETAG_NONE) {
+        conf->etag_bits &= (~ ETAG_NONE);
+    }
+
     return (void*)conf;
 }
 
@@ -2985,6 +3012,135 @@ set_debug_header(cmd_parms *cmd, core_dir_config *m, int arg)
 #endif
 #endif /* CHARSET_EBCDIC */
 
+/*
+ * Note whether file inodes may be used when forming ETag values.
+ * It would be nicer to do this as an ITERATE, but then we couldn't
+ * remember the +/- state properly.
+ */
+static const char *set_etag_bits(cmd_parms *cmd, void *mconfig,
+                                 const char *args_p)
+{
+    core_dir_config *cfg;
+    etag_components_t bit;
+    char action;
+    char *token;
+    const char *args;
+    int valid;
+    int first;
+    int explicit;
+
+    cfg = (core_dir_config *) mconfig;
+
+    args = args_p;
+    first = 1;
+    explicit = 0;
+    while (args[0] != '\0') {
+        action = '*';
+        bit = ETAG_UNSET;
+        valid = 1;
+        token = ap_getword_conf(cmd->pool, &args);
+        if ((*token == '+') || (*token == '-')) {
+            action = *token;
+            token++;
+        }
+        else {
+            /*
+             * The occurrence of an absolute setting wipes
+             * out any previous relative ones.  The first such
+             * occurrence forgets any inherited ones, too.
+             */
+            if (first) {
+                cfg->etag_bits = ETAG_UNSET;
+                cfg->etag_add = ETAG_UNSET;
+                cfg->etag_remove = ETAG_UNSET;
+                first = 0;
+            }
+        }
+
+        if (strcasecmp(token, "None") == 0) {
+            if (action != '*') {
+                valid = 0;
+            }
+            else {
+                cfg->etag_bits = bit = ETAG_NONE;
+                explicit = 1;
+            }
+        }
+        else if (strcasecmp(token, "All") == 0) {
+            if (action != '*') {
+                valid = 0;
+            }
+            else {
+                explicit = 1;
+                cfg->etag_bits = bit = ETAG_ALL;
+            }
+        }
+        else if (strcasecmp(token, "Size") == 0) {
+            bit = ETAG_SIZE;
+        }
+        else if ((strcasecmp(token, "LMTime") == 0)
+                 || (strcasecmp(token, "MTime") == 0)
+                 || (strcasecmp(token, "LastModified") == 0)) {
+            bit = ETAG_MTIME;
+        }
+        else if (strcasecmp(token, "INode") == 0) {
+            bit = ETAG_INODE;
+        }
+        else {
+            return ap_pstrcat(cmd->pool, "Unknown keyword '",
+                              token, "' for ", cmd->cmd->name,
+                              " directive", NULL);
+        }
+
+        if (! valid) {
+            return ap_pstrcat(cmd->pool, cmd->cmd->name, " keyword '",
+                              token, "' cannot be used with '+' or '-'",
+                              NULL);
+        }
+
+        if (action == '+') {
+            /*
+             * Make sure it's in the 'add' list and absent from the
+             * 'subtract' list.
+             */
+            cfg->etag_add |= bit;
+            cfg->etag_remove &= (~ bit);
+        }
+        else if (action == '-') {
+            cfg->etag_remove |= bit;
+            cfg->etag_add &= (~ bit);
+        }
+        else {
+            /*
+             * Non-relative values wipe out any + or - values
+             * accumulated so far.
+             */
+            cfg->etag_bits |= bit;
+            cfg->etag_add = ETAG_UNSET;
+            cfg->etag_remove = ETAG_UNSET;
+            explicit = 1;
+        }
+    }
+
+    /*
+     * Any setting at all will clear the 'None' and 'Unset' bits.
+     */
+
+    if (cfg->etag_add != ETAG_UNSET) {
+        cfg->etag_add &= (~ ETAG_UNSET);
+    }
+    if (cfg->etag_remove != ETAG_UNSET) {
+        cfg->etag_remove &= (~ ETAG_UNSET);
+    }
+    if (explicit) {
+        cfg->etag_bits &= (~ ETAG_UNSET);
+        if ((cfg->etag_bits & ETAG_NONE) != ETAG_NONE) {
+            cfg->etag_bits &= (~ ETAG_NONE);
+        }
+    }
+    return NULL;
+}
+
 /* Note --- ErrorDocument will now work from .htaccess files.  
  * The AllowOverride of Fileinfo allows webmasters to turn it off
  */
@@ -3276,6 +3432,8 @@ static const command_rec core_cmds[] = {
 #endif
 #endif /* CHARSET_EBCDIC */
 
+{ "FileETag", set_etag_bits, NULL, OR_FILEINFO, RAW_ARGS,
+  "Specify components used to construct a file's ETag"},
 { NULL }
 };
 
index 2c31d7852c2cb77fb3195f212367bc87b24ca709..a9cee504a3b308a1f7549882a80cb23a861de4cb 100644 (file)
@@ -649,7 +649,15 @@ API_EXPORT(char *) ap_make_etag(request_rec *r, int force_weak)
 {
     char *etag;
     char *weak;
+    core_dir_config *cfg;
+    etag_components_t etag_bits;
 
+    cfg = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+                                                  &core_module);
+    etag_bits = (cfg->etag_bits & (~ cfg->etag_remove)) | cfg->etag_add;
+    if (etag_bits == ETAG_UNSET) {
+        etag_bits = ETAG_BACKWARD;
+    }
     /*
      * Make an ETag header out of various pieces of information. We use
      * the last-modified date and, if we have a real file, the
@@ -666,11 +674,43 @@ API_EXPORT(char *) ap_make_etag(request_rec *r, int force_weak)
     weak = ((r->request_time - r->mtime > 1) && !force_weak) ? "" : "W/";
 
     if (r->finfo.st_mode != 0) {
-        etag = ap_psprintf(r->pool,
-                    "%s\"%lx-%lx-%lx\"", weak,
-                    (unsigned long) r->finfo.st_ino,
-                    (unsigned long) r->finfo.st_size,
-                    (unsigned long) r->mtime);
+        char **ent;
+        array_header *components;
+        int i;
+
+        /*
+         * If it's a file (or we wouldn't be here) and no ETags
+         * should be set for files, return an empty string and
+         * note it for ap_send_header_field() to ignore.
+         */
+        if (etag_bits & ETAG_NONE) {
+            ap_table_setn(r->notes, "no-etag", "omit");
+            return "";
+        }
+
+        components = ap_make_array(r->pool, 4, sizeof(char *));
+        if (etag_bits & ETAG_INODE) {
+            ent = (char **) ap_push_array(components);
+            *ent = ap_psprintf(r->pool, "%lx",
+                               (unsigned long) r->finfo.st_ino);
+        }
+        if (etag_bits & ETAG_SIZE) {
+            ent = (char **) ap_push_array(components);
+            *ent = ap_psprintf(r->pool, "%lx",
+                               (unsigned long) r->finfo.st_size);
+        }
+        if (etag_bits & ETAG_MTIME) {
+            ent = (char **) ap_push_array(components);
+            *ent = ap_psprintf(r->pool, "%lx", (unsigned long) r->mtime);
+        }
+        ent = (char **) components->elts;
+        etag = ap_pstrcat(r->pool, weak, "\"", NULL);
+        for (i = 0; i < components->nelts; ++i) {
+            etag = ap_psprintf(r->pool, "%s%s%s", etag,
+                               (i == 0 ? "" : "-"),
+                               ent[i]);
+        }
+        etag = ap_pstrcat(r->pool, etag, "\"", NULL);
     }
     else {
         etag = ap_psprintf(r->pool, "%s\"%lx\"", weak,
@@ -1458,8 +1498,14 @@ API_EXPORT(int) ap_index_of_response(int status)
  * It returns true unless there was a write error of some kind.
  */
 API_EXPORT_NONSTD(int) ap_send_header_field(request_rec *r,
-    const char *fieldname, const char *fieldval)
+                                            const char *fieldname,
+                                            const char *fieldval)
 {
+    if (strcasecmp(fieldname, "ETag") == 0) {
+        if (ap_table_get(r->notes, "no-etag") != NULL) {
+            return 1;
+        }
+    }
     return (0 < ap_rvputs(r, fieldname, ": ", fieldval, CRLF, NULL));
 }