Utilizzare la cache dei browser con gli ETag in PHP

Utilizzare la Cache dei browser con gli EtagDi tecniche per sfruttare la cache interna dei browser ne esistono molte. La più semplice prevede di impostare una “scadenza” alla risorsa in questione e il browser continuerà ad utilizzare la copia scaricata in precedenza fino a tale data, ma cosa succede quando il contenuto di questa risorsa viene modificato?

In questo caso il browser non è in grado di notare la differenza e continuerà a utilizzare la copia in cache fino a quando questa non scade.

Un trucco che si può utilizzare è quello di cambiare l’url della risorsa, rinominando il file o aggiungendo una variabile (ES: http://www.artera.it/immagine.png?version=2), in questo caso il browser sarà costretto a scaricare il nuovo file perché viene considerato una risorsa completamente nuova della quale non ha nessuna versione in cache.

Questo sistema risulta tuttavia scomodo perché ad ogni aggiornamento bisogna anche modificarne tutti i riferimenti sul sito, ma fortunatamente esiste un’alternativa valida fornita dai browser, ovvero gli ETag.

Come funzionano

Gli ETag sono un identificativo fornito dal web-server al browser che identifica univocamente una specifica versione di una risorsa situata ad un dato URL.

Il funzionamento è molto semplice: Quando il browser richiede una risorsa il server web include all’interno degli header una particolare stringa univoca, ad esempio “ABCDEFG” (l’ETag).

Il browser salva quindi la risorsa in cache ricordandosi questa stringa per il futuro.

Ad una successiva richiesta il browser dirà al server (simulazione in linguaggio umano) «mi serve la risorsa X, considera che ho già la versione “ABCDEFG”», il server trova la risorsa X e ne verifica la versione, se è la stessa risponderà al browser dicendo «la risorsa non è stata modificata» (HTTP/1.0 304 Not Modified) senza inviare altri dati e quindi con notevole risparmio di banda, altrimenti invierà la nuova versione con il nuovo ETag e il browser invaliderà la copia di cache precedente.

ETag automatici per le risorse statiche in Apache

Apache supporta la generazione automatica di un ETag per le risorse statiche utilizzando parametri come dimensione del file e data di ultima modifica per capire quando una risorsa è stata modificata.

Abilitare questa funzionalità è molto semplice, basta inserire la direttiva FileETag riga all’interno del file .htaccess o in un blocco del file di configurazione di Apache.

FileETag MTime Size

ETag in PHP

Vediamo come generare in PHP degli ETag da fornire al browser per fare cache di contenuti generati dinamicamente.

Nel nostro esempio svilupperemo una pagina PHP che prende l’immagine “sfondo.png“, gli sovrappone una scritta ricevuta in GET, e invia il risultato al browser
Ecco come si presenta lo script senza l’uso degli ETag.

<?php
if (empty($_GET['s']) {
  header('HTTP/1.0 400 Bad Request');
  exit;
}

$im = imagecreatefromstring( file_get_contents("sfondo.png") );
$color = imagecolorallocate($im, 0, 0, 0);
imagestring($im, 2, 0, 0, $_GET['s'], $color);

header('Content-type: image/png');
imagepng($im);
imagedestroy($im);

Dal codice si evince che se non viene modificata l’immagine “sfondo.png” e se il testo fornito come parametro resta identico allora anche l’immagine finale sarà identica e quindi sarebbe meglio che il browser utilizzasse la sua cache.

Dobbiamo quindi generare un ETag in base a questi parametri ma dato che la stringa di testo viene fornita tramite GET, al cambiare della stringa cambia anche l’URL e quindi non abbiamo bisogno di tenerlo in considerazione (Nota: diverse risorse possono avere lo stesso ETag senza creare problemi al browser che le salva in cache utilizzando sia l’URL che l’ETag come discriminanti).

Per motivi di performance useremo anche noi, come Apache, dimensione e data di modifica del file “sfondo.png” anziché il suo contenuto per generare l’ETag (che deve avere le virgolette intorno).

$etag = filesize("sfondo.png").filemtime("sfondo.png");
$etag = '"'.$etag.'"';

Questo ETag verrà inviato al browser, dopodiché verifichiamo se il browser ci ha comunicato un ETag con l’header HTTP_IF_NONE_MATCH e se il tag corrisponde interrompiamo lo script.

Il codice finale è quindi:

<?php
if (empty($_GET['s']) {
  header('HTTP/1.0 400 Bad Request');
  exit;
}

// Calcolo un etag e lo comunico al client
$etag = filesize("sfondo.png").filemtime("sfondo.png");
$etag = '"'.$etag.'"';
header("Etag: $etag");

// Verifico l'etag richiesto dal client ed eventualmente interrompo lo script
if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) &&
    $_SERVER['HTTP_IF_NONE_MATCH']==$etag) {
  header('HTTP/1.0 304 Not Modified');
  header('Content-Length: 0');
  exit;
}

// Il browser non ha una copia in cache, quindi proseguo nella generazione dell'immagine
$im = imagecreatefromstring( file_get_contents("sfondo.png") );
$color = imagecolorallocate($im, 0, 0, 0);
imagestring($im, 2, 0, 0, $_GET['s'], $color);

header('Content-type: image/png');
imagepng($im);
imagedestroy($im);

Verifichiamo il funzionamento con i Developer Tools di Chrome

Grazie ai Developer Tools di Chrome possiamo verificare gli header inviati e ricevuti dal browser e capire se il nostro codice sfrutta effettivamente la cache. La procedura che seguiremo è facilmente riproducibile anche utilizzando Firefox+Firebug.

Carichiamo il nostro script nel browser (nel nostro caso abbiamo utilizzato il nome banners.php) e apriamo i Developer Tools facendo clic col tasto destro e selezionando “Ispeziona elemento“.

Selezionamo la pagina “Resources” dalle voci disponibili nell’area superiore e cerchiamo “banners.php” dall’elenco di risorse.
Nel pannello di destra dovremmo vedere gli header della richiesta (assicuriamoci di aver selezionato la tab “Headers“) che mostrano lo status 200 e l’Etag generato.

Prima richiesta senza cache
Prima richiesta senza cache

Se ricarichiamo la pagina (attenzione che F5 potrebbe svuotare la cache del browser) dovremmo vedere che ora la risposta contiene lo status 304 perché la risorsa risulta uguale a quella presente in cache e il caricamento sarà di conseguenza istantaneo.

Il codice 304 dimostra che il browser ha sfruttato la cache
Il codice 304 dimostra che il browser ha sfruttato la cache