Joerg: > Such a proxy would be pretty broken. ... Again, such a client is > pretty much broken already under the caching model. ...
I agree. Writing an HTTP server in a perfect world may be easy. But I feel like a lot of programming work are efforts to make broken clients (or "components") work? My tests for the "Vary: Cookie" header were on Windows, with the Fossil built-in (`fossil ui') local web server, and the "localauth" setting enabled, so that login and logout was possible. This worked fine. But then I noticed something strange: most major web browsers (not tested with Firefox) do not seem to send If-None-Match request headers for localhost connections, but only use If-Modified-Since. So I rebuilt Fossil with the same modification to send a "Vary: Cookie" header along with ETag and Last-Modified headers, and tested it on my FreeBSD / Apache remote web server -- and unfortunately it doesn't work (again tested with most major web browsers, not tested with Firefox). Apache seems to do minimal HTTP header rearrangements compared to the Fossil local web server, such as: Vary: Accept-Encoding Vary: Cookie to: Vary: Cookie,Accept-Encoding and: Connection: close to: Connection: Keep-Alive Keep-Alive: timeout=2, max=99 but I think this should not affect HTTP caching. So it looks like "Vary: Cookie" only works for clients relying on the If-Modified-Since cache control mechanism? The only way I found to get both the Fossil built-in local web server, and the FreeBSD / Apache remote web server to work as expected, i.e. that /uv web pages are are correctly expired after a new user is logged-in, was the following combination: * Add "user.cexpire" to the ETag ingredients. * Skip If-Modified-Since handling after If-None-Match has already detected an ETag cache miss. * Send a "Vary: Cookie" header with ETag and Last-Modified headers. --Florian ===================== Patch for Fossil [04190488] ====================== Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -242,10 +242,11 @@ /* ** Do a normal HTTP reply */ void cgi_reply(void){ + int vary_cookie = 0; int total_size; if( iReplyStatus<=0 ){ iReplyStatus = 200; zReplyStatus = "OK"; } @@ -262,19 +263,24 @@ /* isConst means that the reply is guaranteed to be invariant, even ** after configuration changes and/or Fossil binary recompiles. */ fprintf(g.httpOut, "Cache-Control: max-age=31536000\r\n"); }else if( etag_tag()!=0 ){ fprintf(g.httpOut, "ETag: %s\r\n", etag_tag()); + vary_cookie = 1; fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage()); }else{ fprintf(g.httpOut, "Cache-control: no-cache\r\n"); } if( etag_mtime()>0 ){ fprintf(g.httpOut, "Last-Modified: %s\r\n", cgi_rfc822_datestamp(etag_mtime())); + vary_cookie = 1; } + if( vary_cookie ) + fprintf(g.httpOut, "Vary: Cookie\r\n"); + if( blob_size(&extraHeader)>0 ){ fprintf(g.httpOut, "%s", blob_buffer(&extraHeader)); } /* Add headers to turn on useful security options in browsers. */ Index: src/doc.c ================================================================== --- src/doc.c +++ src/doc.c @@ -641,17 +641,26 @@ } } if( isUV ){ if( db_table_exists("repository","unversioned") ){ Stmt q; + char *zHash=0; + sqlite3_int64 mtime=0; db_prepare(&q, "SELECT hash, mtime FROM unversioned" " WHERE name=%Q", zName); if( db_step(&q)==SQLITE_ROW ){ - etag_check(ETAG_HASH, db_column_text(&q,0)); - etag_last_modified(db_column_int64(&q,1)); + zHash = fossil_strdup(db_column_text(&q,0)); + mtime = db_column_int64(&q,1); } db_finalize(&q); + if( zHash==0 || etag_check(ETAG_HASH, zHash)==0 ){ + /* Prevent the If-Modified-Since cache handler to + ** undermine cache misses already cleared by the + ** If-None-Match cache handler. */ + if ( mtime ) etag_last_modified(mtime); + } + if( zHash ) free(zHash); if( unversioned_content(zName, &filebody)==0 ){ rid = 1; zDfltTitle = zName; } } Index: src/etag.c ================================================================== --- src/etag.c +++ src/etag.c @@ -20,14 +20,15 @@ ** An ETag is a hash that encodes attributes which must be the same for ** the page to continue to be valid. Attributes that might be contained ** in the ETag include: ** ** (1) The mtime on the Fossil executable -** (2) The last change to the CONFIG table -** (3) The last change to the EVENT table -** (4) The value of the display cookie -** (5) A hash value supplied by the page generator +** (2) The "user.cexpire" field for logged-in users +** (3) The last change to the CONFIG table +** (4) The last change to the EVENT table +** (5) The value of the display cookie +** (6) A hash value supplied by the page generator ** ** Item (1) is always included in the ETag. The other elements are ** optional. Because (1) is always included as part of the ETag, all ** outstanding ETags can be invalidated by touching the fossil executable. ** @@ -69,11 +70,11 @@ static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */ /* ** Generate an ETag */ -void etag_check(unsigned eFlags, const char *zHash){ +int etag_check(unsigned eFlags, const char *zHash){ sqlite3_int64 mtime; const char *zIfNoneMatch; char zBuf[50]; assert( zETag[0]==0 ); /* Only call this routine once! */ @@ -83,10 +84,22 @@ /* Always include the mtime of the executable as part of the hash */ mtime = file_mtime(g.nameOfExe, ExtFILE); sqlite3_snprintf(sizeof(zBuf),zBuf,"mtime: %lld\n", mtime); md5sum_step_text(zBuf, -1); + /* Include "user.cexpire" for logged-in users in the hash */ + if ( g.userUid ){ + char *zCExp = db_text(0, "SELECT cexpire FROM user WHERE uid=%d", + g.userUid); + if ( zCExp ){ + md5sum_step_text("cexp: ", -1); + md5sum_step_text(zCExp, -1); + md5sum_step_text("\n", 1); + fossil_free(zCExp); + } + } + if( (eFlags & ETAG_HASH)!=0 && zHash ){ md5sum_step_text("hash: ", -1); md5sum_step_text(zHash, -1); md5sum_step_text("\n", 1); iMaxAge = 0; @@ -118,19 +131,20 @@ memcpy(zETag, md5sum_finish(0), 33); /* Check to see if the generated ETag matches If-None-Match and ** generate a 304 reply if it does. */ zIfNoneMatch = P("HTTP_IF_NONE_MATCH"); - if( zIfNoneMatch==0 ) return; - if( strcmp(zIfNoneMatch,zETag)!=0 ) return; + if( zIfNoneMatch==0 ) return 0; /* No ETag */ + if( strcmp(zIfNoneMatch,zETag)!=0 ) return 1; /* ETag mismatch */ /* If we get this far, it means that the content has ** not changed and we can do a 304 reply */ cgi_reset_content(); cgi_set_status(304, "Not Modified"); cgi_reply(); fossil_exit(0); + return 1; /* ETag match */ } /* ** Accept a new Last-Modified time. This routine should be called by ** page generators that know a valid last-modified time. This routine ===================== Patch for Fossil [04190488] ====================== _______________________________________________ fossil-users mailing list fossil-users@lists.fossil-scm.org http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users