views:

740

answers:

5

I have cacheable dynamic content made in PHP 5.1.0+. I already send the correct headers (including Last-Modified and ETag) to clients.

I now want my script to be able to answer $_SERVER['HTTP_IF_MODIFIED_SINCE'] and $_SERVER['HTTP_IF_NONE_MATCH'] when present. When the conditions matches, I want to answer a HTTP 304 "Not Modified" to clients.

What are the correct conditions? When exactly I issue a 304 instead of the whole content?

The accepted answer in question http://stackoverflow.com/questions/4506/http-how-to-know-when-to-send-a-304-not-modified-response seems to issue this correctly but I have hard times to port that code to PHP 5.

Thank you!

A: 

If the client has performed a conditional GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code. The 304 response MUST NOT contain a message-body, and thus is always terminated by the first empty line after the header fields.

From - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5

So, if you send a 304 don't send the body.

Mr-sk
I know that and my question is not about this... My question is about WHEN do I send a 304...
AlexV
Do you mean, at what point in the php script?
Ah sorry, gotta slow down, = ]
Mr-sk
I mean in which cases I send a 304. Example of answer I seek: "Send 304 when HTTP_IF_MODIFIED_SINCE present and match your last modified date AND when HTTP_IF_NONE_MATCH is present and match your ETags".
AlexV
+1  A: 

The answer you're referencing seems to contain all you need. To summarize:

  • generate your own ETag and Last-Modified headers, just as if you would be sending the whole body
  • look at the If-Modified-Since header the client sent, if your own last-modified is older or the same send the 304
  • look at the If-None-Match header of the client, if it matches your own ETag send the 304
  • if you reach this place, the headers did not match, send complete body and new ETag/Last-Modified headers
Wim
+1  A: 

Here is a snippet of my render_file() function.

$last_modified = filemtime($filename);
if ($last_modified === false) {
  throw new Exception('Modify date unknown');
}
if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
  $if_modified_since = strtotime(preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']));
  if ($if_modified_since >= $last_modified) { // Is the Cached version the most recent?
    header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
    exit();
  }
}
header('Last-Modified: '.date('r', $last_modified)); // tz should be GMT according to specs but also works with other tzs

// other headers and contents go here  
Bob Fanger
And what about HTTP_IF_NONE_MATCH? Where whould it be fitted in your snippet?
AlexV
The modification-date was validation enough for me, calculating a Etag (checksum for the contents md5/sha1) generates some server-overhead. However, etags are less errorprone.If content correctness is important check the IF_NONE_MATCH first.If the IF_NONE_MATCH isn't set, then check the IF_MODIFIED_SINCE.Dont check the IF_MODIFIED_SINCE if the etag doesnt match. Because you know the browser cache is invalid! Just send the 304 header and exit()
Bob Fanger
A: 

Why?

Having done a lot of research on the subject I found that conditional requests actually slow down a site. There are certain scenarios where that is not the case, but mapping to general usage patterns overall it results in lower throughput and less effective caching.

C.

symcbean
Why would it slow down a site. I can't even imagine how it would slow it down (it you send proper cache headers)...
AlexV
The short answer to that question is about 5 pages of text and graphs. I keep meaning to put it on the internet somewhere.....watch this space.
symcbean
Would be interesting to see that :)
AlexV
+4  A: 

I've always used:

function caching_headers ($file, $timestamp) {
    $gmt_mtime = gmdate('r', $timestamp);
    header('ETag: "'.md5($timestamp.$file).'"');

    if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
        if ($_SERVER['HTTP_IF_MODIFIED_SINCE'] == $gmt_mtime || str_replace('"', '', stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])) == md5($timestamp.$file)) {
            header('HTTP/1.1 304 Not Modified');
            exit();
        }
    }

    header('Last-Modified: '.$gmt_mtime);
    header('Cache-Control: public');
}

Don't remember whether I wrote it or got it from somewhere else...

I'm normally using it at the top of a file in this way:

caching_headers ($_SERVER['SCRIPT_FILENAME'], filemtime($_SERVER['SCRIPT_FILENAME']));
Rich Bradshaw
Great function! Really! It saved my day!
Industrial