Cache-vriendelijke pagina’s in PHP
In de meeste gevallen zul je niet willen dat de browser de uitvoer van je PHP-scripts in de cache opslaat. Het moet gewoon iedere keer weer worden opgevraagd. In sommige gevallen is het echter beter om de browser wel te laten cachen. Bijvoorbeeld als je PHP een plaatje laat verkleinen. In dat geval zul je de browser een beetje moeten helpen, zodat het plaatje wel wordt opgeslagen in de cache.
Doel
We zijn op zoek naar een manier om het voor de browser mogelijk te maken de uitvoer van PHP-scripts in de cache op te slaan. Daardoor hoeft de bezoeker niet steeds te wachten tot het bestand opnieuw is binnengehaald en heeft je server het minder druk.
Er zit ook een nadeel aan het gebruik van de cache. Omdat bestanden door de browser worden bewaard, zal die browser niet iedere keer op de server kijken of er iets veranderd is. Als je pagina net veranderd is, zullen de bezoekers nog steeds de oude versie zien. (Als ze eerder zijn geweest, dat wel.) Voor een forum is het dus niet handig om de cache in te schakelen, voor dingen die zelden veranderen wel. Je zou daarbij kunnen denken aan stylesheets en plaatjes die je met behulp van PHP een klein beetje aanpast.
Middel 1: de serverheaders
Iedere pagina die je browser van de server ontvangt, wordt voorafgegaan door een aantal headers. Deze headers geven meer informatie over de opgevraagde pagina. De headers van een html-pagina op mijn server zagen er vandaag bijvoorbeeld zo uit:
HTTP/1.1 200 OK Date: Mon, 17 Mar 2003 13:09:54 GMT Server: Apache/1.3.27 (Unix) mod_ssl/2.8.11 OpenSSL/0.9.6g PHP/4.3.1 Last-Modified: Mon, 24 Feb 2003 13:54:55 GMT Content-Type: text/html
Afgezien van de eerste header, dat is een speciale, kun je
zien dat deze headers op dezelfde manier zijn opgebouwd: de
naam van de header, een dubbele punt en dan de informatie.
Van één van deze headers, Last-Modified:
, zullen
we in dit artikel gebruik gaan maken.
N.B.: De headers worden niet weergegeven door je browser. Als je toch de headers van je scripts wilt bekijken, dan kun je in dit artikel lezen hoe je dat kunt doen.
Last-Modified:
De eerste header die we nodig hebben is de
Last-Modified:
-header. Deze header heeft als
inhoud een datum en tijd. Deze datum en tijd geven aan
wanneer het bestand in kwestie voor het laatst gewijzigd
is. Omdat de browser deze informatie gebruik bij het
bijhouden van de cache, moeten we hiervoor straks iets
zinvols terugsturen.
Cache-Control:
Met de Cache-Control:
-header kunnen we de
browser opdracht geven de pagina wel, of juist niet te
cachen. Ook kunnen we aangeven hoe lang het gecachede
bestand dan gebruikt mag worden. Wij zullen in dit artikel
twee van die opdrachten gebruiken:
-
max-age: seconds
geeft aan hoeveel secondes het bestand bewaard mag worden. Na die tijd moet de browser opnieuw naar de server; -
must-revalidate
vertelt de browser dat, als de max-age is verstreken, toch echt opnieuw aan de server gevraagd moet worden wat er moet gebeuren. Zonder deze opdracht mag de browser het zelf weten, nu moet hij terug naar de server.
HTTP/1.1
De laatste header die we zullen gebruiken is de header die
we zojuist als 'speciaal' terzijde hebben geschoven.
HTTP/1.1
geeft aan welke versie van het
HTTP-protocol er gebruikt wordt, in dit voorbeeld versie
1.1. Achter deze header komt een driecijferige code en een
verklaring van die code. Dit is de statuscode. Bij code
200 OK
geeft de server aan dat de pagina goed
wordt verstuurd. Bij code 404 Not Found
, vast
wel bekend, zegt de server dat de gevraagde pagina niet is
gevonden.
In ons geval zijn we vooral benieuwd naar code 302
Not Modified
. Hiermee vertellen we de browser dat de
pagina niet is veranderd. In dat geval wordt dan ook geen
pagina meegestuurd, want die heeft de browser immers al.
Dit is precies waar we naar op zoek zijn.
Middel 2: De browserheaders
Met het bestuderen van de headers die de server kan versturen, zijn we er nog niet. De browser stuurt namelijk ook headers. Deze headers zien er hetzelfde uit als de serverheaders, alleen hebben ze andere namen. In dit artikel hebben we voornamelijk belangstelling voor n header.
If-Modified-Since:
Deze header wordt gevolgd door een datum. Met de
If-Modified- Since:
-header stelt de browser de
server een vraag: geef deze pagina alleen, als hij sinds de
opgegeven datum is veranderd. Is dat niet het geval, stuur
mij dan alleen de statuscode 302 Not Modified
(zie boven). Dat scheelt namelijk tijd: het hele bestand
hoeft niet meer verzonden te worden.
Deze header zullen we straks ook tegenkomen. Is de cache
van de browser, volgens de max-age
, verouderd,
dan zal de browser ons deze header toesturen. Als ons
script daar dan goed op antwoordt, dus eventueel code 302
terugstuurt, dan is ons cache-systeem klaar.
Uitvoering
Nu we weten hoe de verschillende headers precies werken,
kunnen we die kennis omzetten in een PHP-script. De regels
die volgen kun je gewoon verwerken in ieder PHP-script dat
je wilt laten cachen. Let er daarbij wel op dat PHP alleen
headers kan versturen zolang er nog geen gewone tekst
verstuurd is, dus je moet deze opdrachten boven de eerste
keer echo
plaatsen.
De tijd
Je zult eerst moeten zorgen dat je de datum achterhaalt
waarop de pagina voor het laatst gewijzigd is. Bij
bestanden kun je dat opvragen met filemtime(). In dit voorbeeld slaan we die
tijd in ieder geval op in de variabele $tijd
.
Die tijd moeten we vervolgens omzetten in het formaat dat
volgens het HTTP-protocol gewenst is. Die omgezette tijd
slaan we op in $last_modified
, en zullen we
straks gebruiken bij het samenstellen van de headers.
// tijd omzetten naar: Mon, 17 Mar 2003 13:12:25 GMT $last_modified = gmdate('D, d M Y H:i:s',$tijd).' GMT';
If-Modified-Since?
Nu we de tijd bepaald hebben, kunnen we kijken of de
browser een If-Modified-Since:
-header heeft
meegestuurd. Is dat het geval, en komt die datum overeen
met onze $last_modified
, dan hoeven we de
pagina niet opnieuw te sturen. De regel HTTP/1.1 304
Not Modified
volstaat.
De headers die de browser ons stuurde, kunnen we
terugvingen in de array $_SERVER
. De header
die wij willen opvragen heet dus
$_SERVER['IF_MODIFIED_SINCE']
.
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { if ($_SERVER['HTTP_IF_MODIFIED_SINCE'] == $last_modified) { // de cache is nog steeds goed genoeg header("HTTP/1.1 304 Not Modified"); header("Cache-Control: max-age=86400, must-revalidate"); exit; } }
Na de HTTP/1.1
-header sturen we nog een
header: Cache-Control
. Dat is precies dezelfde
header als de header die we zo zullen versturen. We zullen
er dan wat langer naar kijken.
Wel versturen
Als het script nu nog steeds wordt uitgevoerd, betekent dat dat we de browser wel de hele pagina moeten sturen. Ofwel omdat de cache verouderd is, ofwel omdat de browser de pagina nog niet gecached heeft.
In dit geval zouden we eerst een HTTP/1.1 200
OK
- header moeten versturen, we sturen immers de
hele pagina. Gelukkig doet PHP dat al voor ons, dus hoeven
we ons daar niet mee te bemoeien.
Interessanter is het om te kijken naar de twee headers die
we wel versturen: Cache-Control:
en
Last-Modified:
. Cache-Control:
wordt, zie boven, gevolgd door de opdracht
max-age
en een aantal secondes. Dit is de tijd
dat een browser zonder zich met de server te bemoeien
gewoon de gecachede versie van de pagina mag laten zien. In
mijn voorbeeld gebruik ik 86400, dat is 60 x 60 x 24 = 24
uur. Je kunt er natuurlijk zelf iets anders invullen. Na
max-age
volgt zoals gezegd nog de opdracht
must-revalidate
.
Last-Modified:
wordt gevolgd door de datum
waarop de pagina voor het laatst is veranderd. Die datum
hebben we net al vastgesteld, en is te vinden als
$last_modified
.
header('Cache-Control: max-age=86400, must-revalidate'); header('Last-Modified: '.$last_modified);
De informatie
Nadat de headers verstuurd zijn, kun je nu de informatie zelf versturen. Wat die informatie is, mag je zelf bepalen.
Tot slot
Je kunt nu zorgen dat je pagina's en bestanden gewoon gecached worden. Zeker als je je plaatjes door PHP laat verzorgen is dat voor je bezoekers erg fijn.
De cache en sessies
Als je de session_*
-functies van PHP gebruikt,
zul je waarschijnlijk zien dat het script niet goed werkt.
Je pagina's willen maar niet in de cache blijven hangen.
Dit wordt veroorzaakt door het gebruik van sessies. PHP
verstuurt in dat geval juist headers die het cachen
tegengaan, en tot dusver lijkt het erop dat het niet-cachen
het daarbij wint van het wel-cachen. Wil je laten cachen,
dan zul je op die pagina's geen sessions moeten gebruiken.