After searching a lot, reading every tutorials I've found and asking some questions here, I've finally managed to answer corrctly (at least I think) to if-none-match and if-modified-since HTTP requests.
To do a quick recap, this is what I do on every pages cacheable:
session_cache_limiter('public'); //Cache on clients and proxies
session_cache_expire(180); //3 hours
header('Content-Type: ' . $documentMimeType . '; charset=' . $charset);
header('ETag: "' . $eTag . '"'); //$eTag is a MD5 of $currentLanguage + $lastModified
if ($isXML)
header('Vary: Accept'); //$documentMimeType can be either application/xhtml+xml or text/html for XHTML (based on $_SERVER['HTTP_ACCEPT'])
header('Last-Modified: ' . $lastModified);
header('Content-Language: ' . $currentLanguage);
Also, every page have it's own URL (for every languages). For example, "index.php" will be served under URL "/en/home" in English and "/fr/accueil" in French.
My big problem was to answer a "304 Not Modified" to if-none-match and if-modified-since HTTP requests only when needed.
The best doc I've found is: http://rithiur.anthd.com/tutorials/conditionalget.php
And this is the implementation I did of it (this piece of code is called ASAP on pages that can be cached):
$ifNoneMatch = array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
$ifModifiedSince = array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
if ($ifNoneMatch !== false && $ifModifiedSince !== false)
{
//Both if-none-match and if-modified-since were received.
//They must match the document values in order to send a HTTP 304 answer.
if ($ifNoneMatch == $eTag && $ifModifiedSince == $lastModified)
{
header('Not Modified', true, 304);
exit();
}
}
else
{
//Only one header received, it it match the document value, send a HTTP 304 answer.
if (($ifNoneMatch !== false && $ifNoneMatch == $eTag) || ($ifModifiedSince !== false && $ifModifiedSince == $lastModified))
{
header('Not Modified', true, 304);
exit();
}
}
My question is two fold:
BTW, I use PHP 5.1.0+ only (I don't support versions lower that that).
Edit: Added bounty... I expect quality answer. Don't answer/vote if you are guessing something!
The conditional GET method is intended to reduce unnecessary network usage by allowing cached entities to be refreshed without requiring multiple requests or transferring data already held by the client. The semantics of the GET method change to a "partial GET" if the request message includes a Range header field.
A conditional GET is an HTTP GET request that may return an HTTP 304 response (instead of HTTP 200). An HTTP 304 response indicates that the resource has not been modified since the previous GET, and so the resource is not returned to the client in such a response.
Full conditional GET reference from RFC 2616: A conditional GET method requests that the entity be transferred only under the circumstances described by the conditional header field(s).
HTTP conditional requests are requests that are executed differently, depending on the value of specific headers. These headers define a precondition, and the result of the request will be different if the precondition is matched or not.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With