From 7e665b6ff73916aaab188cc9c2aa9a31fe3eedb5 Mon Sep 17 00:00:00 2001 From: Chris Meller Date: Tue, 21 Aug 2012 10:04:37 -0300 Subject: [PATCH 1/4] Make ETags properly quoted-string's, per RFC 2616. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to DaSourcer for pointing out my mistake in a line comment on commit 1dde6ea. --- classes/theme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/theme.php b/classes/theme.php index 811be0064..ec6e253b5 100644 --- a/classes/theme.php +++ b/classes/theme.php @@ -322,7 +322,7 @@ public function act_display( $paramarray = array( 'user_filters'=> array() ) ) $etag = var_export( $this, true ); $etag = sha1( $etag ); - header('ETag: ' . $etag, true); + header('ETag: "' . $etag . '"', true); if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { if ( $etag == $_SERVER['HTTP_IF_NONE_MATCH'] ) { From 5e5fd5d9603d503ea497ab975cbdbca59c38fe6f Mon Sep 17 00:00:00 2001 From: Chris Meller Date: Thu, 18 Oct 2012 21:08:15 -0500 Subject: [PATCH 2/4] Include all posts and comments on the page when calculating caching headers. This fixes habari/habari#249. --- classes/theme.php | 78 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/classes/theme.php b/classes/theme.php index d0a619655..6122a6c81 100644 --- a/classes/theme.php +++ b/classes/theme.php @@ -319,37 +319,77 @@ public function act_display( $paramarray = array( 'user_filters'=> array() ) ) // @todo probably need to make this private if the user is logged in so proxy's don't cache it? header('Pragma: public', true); header('Cache-Control: public, max-age=' . HabariDateTime::DAY * 30, true); - + $etag = var_export( $this, true ); $etag = sha1( $etag ); - + header('ETag: "' . $etag . '"', true); if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { - if ( $etag == $_SERVER['HTTP_IF_NONE_MATCH'] ) { + if ( $etag == trim( $_SERVER->raw('HTTP_IF_NONE_MATCH'), '"' ) ) { header( 'HTTP/1.1 304 Not Modified', true, 304); header( 'X-Habari-Cache-Match: ETag'); + // don't send any further content die(); } } - - if ( isset( $post ) ) { - $last_modified = $post->modified->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e' ); - $expires = HabariDateTime::date_create( '30 days' )->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e' ); - - header('Last-Modified: ' . $last_modified, true); - - if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { - $if_modified_since = HabariDateTime::date_create( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); - - if ( $post->modified <= $if_modified_since ) { - header( 'HTTP/1.1 304 Not Modified', true, 304 ); - header( 'X-Habari-Cache-Match: Modified' ); - die(); + + if ( isset( $posts ) ) { + + $last_modified = null; + + // if $posts is actually a single post (ie: we are displaying a single entry), wrap it up in an array instead + if ( $posts instanceof Post ) { + $available_posts = array( $posts ); + } + else { + $available_posts = $posts; + } + + foreach ( $available_posts as $post ) { + + // if the post was modified more recently than the last (or we don't have one, duh), use that date + if ( $last_modified == null || $post->modified > $last_modified ) { + $last_modified = $post->modified; + } + + // if there are approved comments, we want to take those into account, too + foreach ( $post->comments->moderated->comments as $comment ) { + + // if it was modified more recently than anything else we've seen, use that + if ( $last_modified == null || $comment->date > $last_modified ) { + $last_modified = $comment->date; + } + } + } - - header('Expires: ' . $expires, true); + + // assuming we got posts to find a last modified date, check its headers + if ( $last_modified != null ) { + + // go ahead and send the header to let them know when it last changed, even if we match + header( 'Last-Modified: ' . $last_modified->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e'), true ); + + // does their browser already have something cached? + if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { + $if_modified_since = HabariDateTime::date_create( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); + + // if we don't have content modified more recently, tell them just to use what they have + if ( $last_modified <= $if_modified_since ) { + header( 'HTTP/1.1 304 Not Modified', true, 304 ); + header( 'X-Habari-Cache-Match: Modified' ); + // do not send any further content + die(); + } + } + + // and tell them how long they should expect this to be valid + $expires = HabariDateTime::date_create( '30 days' )->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e' ); + header( 'Expires: ' . $expires, true ); + + } + } return $this->display_fallback( $fallback ); From 7add5a6a03e54d2e2e5687ef86add463dafcc0f3 Mon Sep 17 00:00:00 2001 From: Chris Meller Date: Thu, 18 Oct 2012 21:31:51 -0500 Subject: [PATCH 3/4] Change the way we generate ETags so it includes comments as well. --- classes/theme.php | 79 +++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/classes/theme.php b/classes/theme.php index 6122a6c81..7eab7731d 100644 --- a/classes/theme.php +++ b/classes/theme.php @@ -320,25 +320,16 @@ public function act_display( $paramarray = array( 'user_filters'=> array() ) ) header('Pragma: public', true); header('Cache-Control: public, max-age=' . HabariDateTime::DAY * 30, true); - $etag = var_export( $this, true ); - $etag = sha1( $etag ); + // we need to manually build a representation of the current content displayed to be sure comments are included + $displayed_content = array(); - header('ETag: "' . $etag . '"', true); - - if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { - if ( $etag == trim( $_SERVER->raw('HTTP_IF_NONE_MATCH'), '"' ) ) { - header( 'HTTP/1.1 304 Not Modified', true, 304); - header( 'X-Habari-Cache-Match: ETag'); - // don't send any further content - die(); - } - } + // while we're at it, we'll also look for the most recently modified post, rather than iterating back over everything again later + $last_modified = null; + // is there actually anything? if ( isset( $posts ) ) { - $last_modified = null; - - // if $posts is actually a single post (ie: we are displaying a single entry), wrap it up in an array instead + // if $posts is actually a single post (ie: we are displaying a single entry), wrap it up as an array isntead if ( $posts instanceof Post ) { $available_posts = array( $posts ); } @@ -348,6 +339,9 @@ public function act_display( $paramarray = array( 'user_filters'=> array() ) ) foreach ( $available_posts as $post ) { + // hash this post in possibly the most crude way possible + $displayed_content[] = sha1( serialize( $post ) ); + // if the post was modified more recently than the last (or we don't have one, duh), use that date if ( $last_modified == null || $post->modified > $last_modified ) { $last_modified = $post->modified; @@ -356,6 +350,9 @@ public function act_display( $paramarray = array( 'user_filters'=> array() ) ) // if there are approved comments, we want to take those into account, too foreach ( $post->comments->moderated->comments as $comment ) { + // hash the post comment too + $displayed_content[] = sha1( serialize( $comment ) ); + // if it was modified more recently than anything else we've seen, use that if ( $last_modified == null || $comment->date > $last_modified ) { $last_modified = $comment->date; @@ -365,31 +362,47 @@ public function act_display( $paramarray = array( 'user_filters'=> array() ) ) } - // assuming we got posts to find a last modified date, check its headers - if ( $last_modified != null ) { + } - // go ahead and send the header to let them know when it last changed, even if we match - header( 'Last-Modified: ' . $last_modified->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e'), true ); + // now generate an etag from the content we are displaying + $etag = sha1( serialize( $displayed_content ) ); - // does their browser already have something cached? - if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { - $if_modified_since = HabariDateTime::date_create( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); + // let the client know what content is on the page currently + header('ETag: "' . $etag . '"', true); - // if we don't have content modified more recently, tell them just to use what they have - if ( $last_modified <= $if_modified_since ) { - header( 'HTTP/1.1 304 Not Modified', true, 304 ); - header( 'X-Habari-Cache-Match: Modified' ); - // do not send any further content - die(); - } - } + // and check to see if they gave us an etag they have cached + if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { + if ( $etag == trim( $_SERVER->raw('HTTP_IF_NONE_MATCH'), '"' ) ) { + header( 'HTTP/1.1 304 Not Modified', true, 304); + header( 'X-Habari-Cache-Match: ETag'); + // don't send any further content + die(); + } + } + + // if we didn't match the etag, but we have a last modified date to check, do that as a fallback + if ( $last_modified != null ) { - // and tell them how long they should expect this to be valid - $expires = HabariDateTime::date_create( '30 days' )->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e' ); - header( 'Expires: ' . $expires, true ); + // go ahead and send the header to let them know when it last changed, even if we match + header( 'Last-Modified: ' . $last_modified->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e'), true ); + // does their browser already have something cached? + if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { + $if_modified_since = HabariDateTime::date_create( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); + + // if we don't have content modified more recently, tell them just to use what they have + if ( $last_modified <= $if_modified_since ) { + header( 'HTTP/1.1 304 Not Modified', true, 304 ); + header( 'X-Habari-Cache-Match: Modified' ); + // do not send any further content + die(); + } } + // and tell them how long they should expect this to be valid + $expires = HabariDateTime::date_create( '30 days' )->set_timezone( 'UTC' )->format( 'D, d M Y H:i:s e' ); + header( 'Expires: ' . $expires, true ); + } return $this->display_fallback( $fallback ); From e4393b2af10f907e5144ee1e2d4841dcaeda5a11 Mon Sep 17 00:00:00 2001 From: Chris Meller Date: Sat, 2 Feb 2013 13:56:38 -0600 Subject: [PATCH 4/4] Just a quick note about the caching headers vs. sessions. --- classes/theme.php | 1 + 1 file changed, 1 insertion(+) diff --git a/classes/theme.php b/classes/theme.php index 7eab7731d..7f01cfc63 100644 --- a/classes/theme.php +++ b/classes/theme.php @@ -317,6 +317,7 @@ public function act_display( $paramarray = array( 'user_filters'=> array() ) ) } // @todo probably need to make this private if the user is logged in so proxy's don't cache it? + // @todo this shouldn't be necessary at all after we sort out starting a session only for logged-in users header('Pragma: public', true); header('Cache-Control: public, max-age=' . HabariDateTime::DAY * 30, true);