3

I'm trying to reduce my server load by providing 304 Not Modified responses for content where appropriate. CloudFlare is my middle man, so they should be sending If-Modified-Since headers whenever a cached page has expired, right?

I'm receiving these responses, client-side:

  • CF-Cache-Status: MISS on first page load
  • CF-Cache-Status: Hit on page reloads for 20 seconds
  • CF-Cache-Status: EXPIRED on page reload after 20 seconds

The expired request is forwarded to my server, but doesn't include an If-Modified-Since header. How can I get this to work?

<?php
$now = time();
header( "ETag: W/\"$now\"" );
header( 'Expires: '.gmdate('D, d M Y H:i:s \G\M\T', $now + 20) );

header( 'Last-Modified: '.time() );
header( 'Cache-Control: public, max-age=20' );

print('<pre>');
print_r($_SERVER);
print('</pre>');

UPDATE: Here's working code

<?php

if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
    // $date = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
    header("HTTP/1.1 304 Not Modified");
    exit();
}

$format = 'D, d M Y H:i:s \G\M\T';
$now = time();

$date = gmdate($format, $now);
header('Date: '.$date);
header('Last-Modified: '.$date);

$date = gmdate($format, $now+30);
header('Expires: '.$date);

header('Cache-Control: public, max-age=30');

print('<pre>'); 
print_r($_SERVER); 
print('</pre>');
3
+50

From my understanding and experience it seems to me perhaps you've misunderstood how CDN caching works:

  • From the example you've given, the CDN would not ask your web-server when the file was last modified because you've already told it that the file has expired and will therefore need to be re-fetched anyway.

  • Web browsers will only send an If-Modified-Since header if the page/resource has been previously cached by the web-browser and came with a Last-Modified header when first requested.

  • If you send an E-Tag header as in your example then the handling of this is not consistent across browsers and you may end up with an If-None-Match header in the subsequent request from the browser instead of an If-Modified-Since.

CDN's however do not typically behave the same as web-browsers, in some ways more similar to proxy servers or web accelerators:

  • If a CDN-cached page has expired, when next requested by a web-browser, the CDN will simply fetch a fresh copy from the web-server, update the CDN-cached copy and forward the resource back to the web-browser in the HTTP response. There would normally be no requirement for If-Modified-Since headers reaching your web-server at all, though my testing has revealed that CloudFlare do still issue this header.

  • To reduce your server load, enable the CDN to provide a level of caching beyond that already done by the web browsers, by using the s-maxage directive. Similar to the max-age directive on your Cache-Control header, however this one will be observed by CDN's (and similar services) in precedence over any other headers, while the max-age will be observed by the web-browser clients.

    header( 'Cache-Control: public, max-age=20, s-maxage=60' );

    If you use this header, the first request from a web-browser will be a CDN MISS but be cached then at both the web-browser and CDN. After the first 20 seconds, the web-browser cached copy will expire. If the page is then reloaded, the CDN will HIT for a further 40 seconds returning a copy from CDN-cache to the web-browser in the HTTP response. Sixty seconds after the first request the CDN-cache has expired and a subsequent request will be a CDN EXPIRED but otherwise be treated the same as a CDN MISS taking a fresh copy from your web-server, and so the loop sequence would continue.

    A more production-ready header, for web-browser caching of 1 hour, and CDN caching of 6 hours might look like:

    header( 'Cache-Control: public, max-age=3600, s-maxage=21600' );

    If you ever needed to publish an update faster than the 6 hour CDN expiry you can always tell the CDN to take a fresh cache from their web-based control panel. With this header each resource would only be fetched from your web-server 4 times a day while the CDN handles all the bulk of the web traffic.


Edited on 11-Nov-2014 @ 12:45pm UTC-0:

On a side note that might also have an impact, there are issues with your PHP code that may be impacting on its correct operation - your ETag and Expires header lines of code when I test them produce the following headers:

ETag: W/""
Expires: Thu, 01 Jan 1970 00:00:20 GMT

Perhaps instead try these lines in your test so that you can see your sent headers too:

<?php
$iClientCacheSecs = 20;
$iProxyCacheSecs = 60;
$dtNow = time();
$dtExpires = strtotime( sprintf( '+%s seconds', $iClientCacheSecs ));
$aHeaders = array();
$aHeaders[] = 'ETag: ' . $dtNow;
$aHeaders[] = 'Expires: ' . date( 'r', $dtExpires );
$aHeaders[] = 'Last-Modified: ' . date( 'r', $dtNow );
$aHeaders[] = sprintf( 'Cache-Control: public, max-age=%s, s-maxage=%s',
  $iClientCacheSecs, $iProxyCacheSecs );
foreach( $aHeaders as $sHeader ) header( $sHeader );
echo( 'Now: ' . date( 'r', $dtNow ) . '<br />' );
foreach( $aHeaders as $sHeader ) echo( $sHeader . '<br />' );
echo( '<hr />' );
foreach( $_SERVER as $sParam => $sValue ) {
  if(( strpos( $sParam, 'HTTP_CF' )) !== false ) 
    echo( $sParam . ': ' . $sValue . '<br />' );
  if(( strpos( $sParam, 'HTTP_IF' )) !== false ) 
    echo( $sParam . ': ' . $sValue . '<br />' );
}
  • So basically Cloudflare doesn't support 304s? – skibulk Nov 11 '14 at 4:41
  • @skibulk For pages (i.e. not static files) a CDN wouldn't typically support 304's, no. For static files (images, scripts, etc.) CloudFlare does support 304's. – Emil Rasmussen Nov 11 '14 at 6:34
  • Great explanation on CDN and caching headers. – Emil Rasmussen Nov 11 '14 at 6:36
0

You should keep in mind that a 304 Not Modified header still requires a request going through to your server.

You properly won't want to send 304 Not Modified headers, you most like want to send a cache expire header, so that the browser doesn't even try to request the resources again. That of course only relevant for static resources (images, scripts etc.).

In my experience CloudFlare only send Last-Modified headers for static resources.

You might want to have a look at CloudFlare's page urls, and give your pages (i.e. not images, scripts, etc.) a Custom caching value other than default?

  • There is still a request, yes. But when my CMS takes up to 3 seconds to load (due to shared hosting), I'd rather send a 304. Per my example, I am already sending expires headers. – skibulk Nov 8 '14 at 0:07
  • See updated answer for the suggestion about page rules. – Emil Rasmussen Nov 11 '14 at 6:35
  • I understand that I can change the cache duration. What I'm trying to avoid is needlessly re-rendering my pages when the cache expires. – skibulk Nov 11 '14 at 17:08
  • Ahh OK, then I have run out of ideas. Good luck! – Emil Rasmussen Nov 11 '14 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.