hi all

this is something I've been meaning to do for a while.  as mod_include
demonstrates, output filters will sometimes be required to remove the ETag
header generated by content handlers, depending on how much they alter the
content.  the current methodology is to set a "no-etag" note, which is
recognized by the header filter.

what I wanted to do was expand this a bit and offer an ETag weakening API -
it should be easy to see that while some (most) output filters alter content
dramatically, others might only alter content bitwise but not semantically.
 in these cases a weak etag is acceptable (and probably preferred to no etag
at all, from my reading of the specs).  under the current state of affairs,
there is no way for an output filter to intercept ETag generation other than
to specify its removal.

the attached patch (etag_api.patch) offers two new functions.
ap_weaken_etag() guarantees a weak ETag header will be generated if one is
generated at all.  the decision to generate one is still given to
ap_set_etag(). ap_supress_etag() is just a formalization of the "no-etag"
idiom into a real API.

fwiw, I've also included patches against mod_case_filter and the
perl-framework, which is what I used for my testing (so you can try it
yourself).

--Geoff
Index: include/http_protocol.h
===================================================================
RCS file: /home/cvspublic/httpd-2.0/include/http_protocol.h,v
retrieving revision 1.84
diff -u -r1.84 http_protocol.h
--- include/http_protocol.h     3 Feb 2003 17:52:53 -0000       1.84
+++ include/http_protocol.h     12 Dec 2003 16:57:17 -0000
@@ -196,11 +196,25 @@
 AP_DECLARE(char *) ap_make_etag(request_rec *r, int force_weak);
 
 /**
- * Set the E-tag outgoing header
+ * Set the ETag outgoing header
  * @param The current request
  * @deffunc void ap_set_etag(request_rec *r)
  */
 AP_DECLARE(void) ap_set_etag(request_rec *r);
+
+/**
+ * Supress the ETag outgoing header
+ * @param The current request
+ * @deffunc void ap_supress_etag(request_rec *r)
+ */
+AP_DECLARE(void) ap_supress_etag(request_rec *r);
+
+/**
+ * Force generation of a weak ETag header
+ * @param The current request
+ * @deffunc void ap_weaken_etag(request_rec *r)
+ */
+AP_DECLARE(void) ap_weaken_etag(request_rec *r);
 
 /**
  * Set the last modified time for the file being sent
Index: modules/filters/mod_include.c
===================================================================
RCS file: /home/cvspublic/httpd-2.0/modules/filters/mod_include.c,v
retrieving revision 1.292
diff -u -r1.292 mod_include.c
--- modules/filters/mod_include.c       10 Dec 2003 02:30:20 -0000      1.292
+++ modules/filters/mod_include.c       12 Dec 2003 16:57:33 -0000
@@ -3584,7 +3584,7 @@
      * We don't know if we are going to be including a file or executing
      * a program - in either case a strong ETag header will likely be invalid.
      */
-    apr_table_setn(f->r->notes, "no-etag", "");
+    ap_supress_etag(f->r);
 
     return OK;
 }
Index: modules/http/http_protocol.c
===================================================================
RCS file: /home/cvspublic/httpd-2.0/modules/http/http_protocol.c,v
retrieving revision 1.473
diff -u -r1.473 http_protocol.c
--- modules/http/http_protocol.c        16 Nov 2003 02:09:13 -0000      1.473
+++ modules/http/http_protocol.c        12 Dec 2003 16:57:37 -0000
@@ -2693,7 +2693,7 @@
      * note it for the header-sender to ignore.
      */
     if (etag_bits & ETAG_NONE) {
-        apr_table_setn(r->notes, "no-etag", "omit");
+        ap_supress_etag(r);
         return "";
     }
 
@@ -2711,7 +2711,13 @@
      * we send a weak tag instead of a strong one, since it could
      * be modified again later in the second, and the validation
      * would be incorrect.
+     * 
+     * additional criteria for a weak tag is if force_weak is true
+     * or ap_weaken_etag() was called
      */
+
+    force_weak = (force_weak || apr_table_get(r->notes, "weak-etag"));
+
     if ((r->request_time - r->mtime > (1 * APR_USEC_PER_SEC)) &&
         !force_weak) {
         weak = NULL;
@@ -2831,6 +2837,37 @@
     }
 
     apr_table_setn(r->headers_out, "ETag", etag);
+}
+
+/*
+ * Take steps to ensure that current and future-generated ETag
+ * headers are marked as weak.
+ */
+AP_DECLARE(void) ap_weaken_etag(request_rec *r)
+{
+    const char *etag = apr_table_get(r->headers_out, "ETag");
+
+    /* if the ETag header exists and is not already marked
+     * as weak, mark it now
+     */
+    if ((etag = apr_table_get(r->headers_out, "ETag")) &&
+        strncmp(etag, ETAG_WEAK, strlen(ETAG_WEAK))) {
+
+       etag = apr_pstrcat(r->pool, ETAG_WEAK, etag, NULL);
+
+       apr_table_setn(r->headers_out, "ETag", etag);
+    }
+
+    /* make sure future calls to ap_make_etag() produce weak tags */
+    apr_table_setn(r->notes, "weak-etag", "yes");
+}
+
+/*
+ * Formalize ETag header supression.
+ */
+AP_DECLARE(void) ap_supress_etag(request_rec *r)
+{
+    apr_table_setn(r->notes, "no-etag", "omit");
 }
 
 static int parse_byterange(char *range, apr_off_t clength,
Index: modules/experimental/mod_case_filter.c
===================================================================
RCS file: /home/cvspublic/httpd-2.0/modules/experimental/mod_case_filter.c,v
retrieving revision 1.16
diff -u -r1.16 mod_case_filter.c
--- modules/experimental/mod_case_filter.c      16 Nov 2003 02:09:13 -0000      1.16
+++ modules/experimental/mod_case_filter.c      12 Dec 2003 16:57:28 -0000
@@ -59,6 +59,7 @@
 #include "apr_lib.h"
 #include "util_filter.h"
 #include "http_request.h"
+#include "http_protocol.h"
 
 #include <ctype.h>
 
@@ -98,6 +99,19 @@
     apr_bucket *pbktIn;
     apr_bucket_brigade *pbbOut;
 
+    if(f->r->args && strcmp(f->r->args, "weaken") == 0) {
+        ap_weaken_etag(f->r);
+    }
+    if(f->r->args && strcmp(f->r->args, "weaken-multi") == 0) {
+        ap_weaken_etag(f->r);
+        ap_weaken_etag(f->r);
+        ap_weaken_etag(f->r);
+        ap_weaken_etag(f->r);
+    }
+    if(f->r->args && strcmp(f->r->args, "supress") == 0) {
+        ap_supress_etag(f->r);
+    }
+
     pbbOut=apr_brigade_create(r->pool, c->bucket_alloc);
     for (pbktIn = APR_BRIGADE_FIRST(pbbIn);
          pbktIn != APR_BRIGADE_SENTINEL(pbbIn);
@@ -133,6 +147,24 @@
     return ap_pass_brigade(f->next,pbbOut);
     }
 
+static apr_status_t CaseFilterInit(ap_filter_t *f)
+{
+    if(f->r->args && strcmp(f->r->args, "weaken-init") == 0) {
+        ap_weaken_etag(f->r);
+    }
+    if(f->r->args && strcmp(f->r->args, "weaken-init-multi") == 0) {
+        ap_weaken_etag(f->r);
+        ap_weaken_etag(f->r);
+        ap_weaken_etag(f->r);
+        ap_weaken_etag(f->r);
+    }
+    if(f->r->args && strcmp(f->r->args, "supress-init") == 0) {
+        ap_supress_etag(f->r);
+    }
+
+    return OK;
+}
+
 static const char *CaseFilterEnable(cmd_parms *cmd, void *dummy, int arg)
     {
     CaseFilterConfig *pConfig=ap_get_module_config(cmd->server->module_config,
@@ -152,8 +184,8 @@
 static void CaseFilterRegisterHooks(apr_pool_t *p)
     {
     ap_hook_insert_filter(CaseFilterInsertFilter,NULL,NULL,APR_HOOK_MIDDLE);
-    ap_register_output_filter(s_szCaseFilterName,CaseFilterOutFilter,NULL,
-                             AP_FTYPE_RESOURCE);
+    ap_register_output_filter(s_szCaseFilterName,CaseFilterOutFilter,
+                             CaseFilterInit,AP_FTYPE_RESOURCE);
     }
 
 module AP_MODULE_DECLARE_DATA case_filter_module =
Index: t/conf/extra.conf.in
===================================================================
RCS file: /home/cvspublic/httpd-test/perl-framework/t/conf/extra.conf.in,v
retrieving revision 1.43
diff -u -r1.43 extra.conf.in
--- t/conf/extra.conf.in        13 Aug 2003 01:28:47 -0000      1.43
+++ t/conf/extra.conf.in        12 Dec 2003 17:08:07 -0000
@@ -318,6 +318,11 @@
         Options +Indexes
         AllowOverride  All
     </Directory>
+    <Directory @SERVERROOT@/htdocs/etags>
+        Options +Indexes
+        IndexOptions +TrackModified
+        AllowOverride  All
+    </Directory>
 </IfModule>
 
 ##
--- /dev/null   2003-01-30 05:24:37.000000000 -0500
+++ t/apache/etagapi.t  2003-12-12 12:03:50.000000000 -0500
@@ -0,0 +1,55 @@
+use strict;
+use warnings FATAL => 'all';
+
+use Apache::Test;
+use Apache::TestRequest;
+use Apache::TestUtil;
+
+my %tests = (
+  '/index.html'                            => qr!^"!,
+  '/index.html?weaken'                     => qr!^W/"!,
+  '/index.html?weaken-multi'               => qr!^W/"!,
+  '/index.html?weaken-init'                => qr!^W/"!,
+  '/index.html?weaken-init-multi'          => qr!^W/"!,
+  '/index.html?supress'                    => undef,
+  '/index.html?supress-init'               => undef,
+  '/etags/'                                => qr!^"!,
+  '/etags/?weaken'                         => qr!^W/"!,
+  '/etags/?weaken-multi'                   => qr!^W/"!,
+  '/etags/?weaken-init'                    => qr!^W/"!,
+  '/etags/?weaken-init-multi'              => qr!^W/"!,
+  '/etags/?supress'                        => undef,
+  '/etags/?supress-init'                   => undef,
+  '/modules/cgi/perl.pl'                   => undef,
+  '/modules/cgi/perl.pl'                   => undef,
+  '/modules/cgi/perl.pl?weaken'            => undef,
+  '/modules/cgi/perl.pl?weaken-multi'      => undef,
+  '/modules/cgi/perl.pl?weaken-init'       => undef,
+  '/modules/cgi/perl.pl?weaken-init-multi' => undef,
+  '/modules/cgi/perl.pl?supress'           => undef,
+  '/modules/cgi/perl.pl?supress-init'      => undef,
+);
+
+plan tests => (scalar keys %tests) * 3, (have_min_apache_version(2.1) &&
+                                         have_module('case_filter')   &&
+                                         have_module('autoindex')     &&
+                                         have_cgi);
+
+foreach my $uri (sort keys %tests) {
+
+    my $response = GET $uri, 'X-AddOutputFilter' => 'CaseFilter';
+
+    ok t_cmp(200,
+             $response->code,
+             "GET $uri status code");
+
+    ok t_cmp(qr/[^a-z]/,
+             $response->content,
+             "GET $uri body all uppercase");
+
+    my $etag = $response->header('ETag');
+
+    ok t_cmp($tests{$uri},
+             $etag,
+             "GET $uri ETag header");
+}

Reply via email to