Answering Http_If_Modified_Since and Http_If_None_Match in PHP

Answering HTTP_IF_MODIFIED_SINCE and HTTP_IF_NONE_MATCH in PHP

I've always used:

function caching_headers ($file, $timestamp) {
$gmt_mtime = gmdate('r', $timestamp);
header('ETag: "'.md5($timestamp.$file).'"');
header('Last-Modified: '.$gmt_mtime);
header('Cache-Control: public');

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();
}
}
}

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']));

HTTP if-none-match and if-modified-since and 304 clarification in PHP

This should be put in the end (moved for better look).

$anyTagMatched = anyTagMatched() ;
if( $anyTagMatched || ( ( null === $anyTagMatched ) && !isExpired() ) ) {
notModified() ;
}
// Output content

Pseudocode (review needed):

<?php

/**
* Calculates eTag for the current resource.
*/
function calculateTag() {
}

/**
* Gets date of the most recent change.
*/
function lastChanged() {
}

/**
* TRUE if any tag matched
* FALSE if none matched
* NULL if header is not specified
*/
function anyTagMatched() {
$if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) :
false ;

if( false !== $if_none_match ) {
$tags = split( ", ", $if_none_match ) ;
$myTag = calculateTag() ;
foreach( $tags as $tag ) {
if( $tag == $myTag ) return true ;
}
return false ;
}
return null ;
}

function isExpired() {
$if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ?
stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) :
false;

if( false !== $if_modified_since ) {
// Compare time here; pseudocode.
return ( $if_modified_since < lastChanged() ) ;
}

return true ;
}

function notModified() {
header('HTTP/1.0 304 Not Modified');
exit ;
}

Main answer here.

304 Not Modified issue

Finally solved this bug. Gzip was the culprit. Since I was gzipping the responses to If-Modified-Since and If-None-Match requests too, gzip was adding a few bytes (kind of a gzip header) to the response. Now I have stopped gzipping responses to If-Modified-Since and If-None-Match requests, and it works like a charm.

Handling If-modified-since header in a PHP-script

This is definitely possible in PHP!

When the browser checks if there were modifications, it sends an If-Modified-Since header; in PHP this value would be set inside $_SERVER['HTTP_IF_MODIFIED_SINCE'].

To decode the date/time value (encoded using rfc822 I believe), you can just use strtotime(), so:

if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && 
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= filemtime($localFileName))
{
header('HTTP/1.0 304 Not Modified');
exit;
}

Explanation: if the If-Modified-Since header is sent by the browser AND the date/time is at least the modified date of the file you're serving, you write the "304 Not Modified" header and stop.

Otherwise, the script continues as per normal.

PHP setting Etag reliably

You should be wrapping your etag in double quotes (as the link Codler mentioned shows):

'"' . $etag . '"'

I don't think it's likely to solve your problem, but you probably want

header('Not Modified',true,304);

instead of

header('HTTP/1.0 304 Not Modified');

As of PHP 5.4 there's a better way to do this with http_response_code:

http_response_code(304);

Also, have you checked for the usual suspects stopping headers? Unicode Byte-Order Markers are very annoying with this. (Disregard if you can see other headers you're setting yourself)

Is my implementation of HTTP Conditional Get answers in PHP is OK?

  • It's not quite correct. Please take a look at the algorithm: alt text http://img532.imageshack.us/img532/1017/cache.png
  • The solution is proxy-friendly, you may use Cache-control: proxy-revalidate to force caches to obey any freshness information you give them about a resource (only applies to shared|proxy caches)

Here is the function that might help:

function isModified($mtime, $etag) {
return !( (
isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
&&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $mtime
) || (
isset($_SERVER['HTTP_IF_NONE_MATCH'])
&&
$_SERVER['HTTP_IF_NONE_MATCH'] == $etag
) ) ;
}

I suggest that you take a look at the following article: http://www.peej.co.uk/articles/http-caching.html

Update:

[AlexV] Is is even possible to receive if-none-match AND if-modified-since at the same time?

You can definitely have both set. However:

If none of the entity tags match, then the server MAY perform the requested method as if the If-None-Match header field did not exist, but MUST also ignore any If-Modified-Since header field(s) in the request. That is, if no entity tags match, then the server MUST NOT return a 304 (Not Modified) response.

RFC2616 #14.26

Example values (W stands for 'weak'; read more in RFC2616 #13.3.3):

If-None-Match: "xyzzy", "r2d2xxxx", "c3piozzzz"
If-None-Match: W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"
If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT
If-None-Match: *

As a special case, the value "*" matches any current entity of the resource.

Allowing caching of image.php until source has been changed

Personally I use something like this and works perfectly;

$etag = '"'. md5($img) .'"';
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])
&& $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
header('HTTP/1.1 304 Not Modified');
header('Content-Length: 0');
exit;
}

$expiry = 604800; // (60*60*24*7)
header('ETag: '. $etag);
header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
header('Expires:'. gmdate('D, d M Y H:i:s', time() + $expiry) .' GMT');
...
// show/send/read image

But here is something else if you like to see (ref: Answering HTTP_IF_MODIFIED_SINCE and HTTP_IF_NONE_MATCH in PHP).

304: Not modified and front end caching

HTTP_IF_MODIFIED_SINCE is the right way to do it. If you aren't getting it, check that Apache has mod_expires and mod_headers enabled and working properly. Borrowed from a comment on PHP.net:

$last_modified_time = filemtime($file); 
$etag = md5_file($file);
// always send headers
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
header("Etag: $etag");
// exit if not modified
if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time ||
@trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) {
header("HTTP/1.1 304 Not Modified");
exit;
}

// output data


Related Topics



Leave a reply



Submit