Proseguendo con il nostro tutorial è giunto il momento di ottimizzare un po’ le prestazioni aggiungendo la gestione della cache.
Come accennato in precedenza, dobbiamo tenere conto di 2 tipi di cache:
- Cache lato browser per ridurre il numero delle richieste
- Cache lato server del css precompilato
Cache lato browser
Forniremo 3 header al browser per fargli fare cache del nostro css finale:
- Last-Modified: utilizzando la data di ultima modifica del file modificato più di recente tra quelli richiesti
- Etag: utilizzando la data di ultima modifica e la dimensione di tutti i file richiesti
- Expires
Potrebbe sembrare superfluo l’uso degli header Etag/Last-Modified insieme a Expires, ma è comunque conveniente utilizzare entrambi combinatamente.
Una risorsa che viene immagazzinata in cache tramite header Expires non genera nessuna richiesta HTTP da parte del browser, a differenza della risorsa Etag che genera sempre una richiesta e se il contenuto non è stato modificato, non scarica alcun dato oltre agli header della risposta.
La prima cosa da fare quindi è leggere data di ultima modifica e dimensione di ogni file richiesto e genero un etag aggregando i dati. Per l’header Last-Modified invece utilizzo solo la data di modifica più recente tra quella dei file richiesti.
[php]
//[…]
$files = explode(‘ ‘,$_GET[‘f’]);
$etagdata = array();
$lastmtime = 0;
foreach ($files as $file) {
$file = trim($file);
$template = “css/$file.ccss”;
if (!$s->templateExists($template))
$template = “css/$file.css”;
$template_file = $s->template_dir.DIRECTORY_SEPARATOR.$template;
if ($s->templateExists($template)) {
$mtime = filemtime($template_file);
$etagdata[] = $mtime;
$etagdata[] = filesize($template_file);
if ($mtime>$lastmtime)
$lastmtime = $mtime;
}
}
// Calcolo un etag e lo comunico al client
$modtime = gmdate(‘r’, $lastmtime);
$etagdata = implode(‘:’, $etagdata);
$etagdata = md5($etagdata.’:’.$modtime);
$etag = “\\”$etagdata\\””;
header(“Last-Modified: $modtime”);
header(“Etag: $etag”);
// Impostiamo anche una data di scadenza per ridurre le richieste, ma nel futuro prossimo (4H) dato che abbiamo anche gli ETag
header(‘Expires: ‘ . gmdate(‘D, d M Y H:i:s’, time()+14400) . ‘ GMT’);
// Verifico l’etag richiesto dal client ed eventualmente interrompo lo script
if (
(!empty($_SERVER[‘HTTP_IF_MODIFIED_SINCE’]) && $_SERVER[‘HTTP_IF_MODIFIED_SINCE’]==$modtime)
||
(!empty($_SERVER[‘HTTP_IF_NONE_MATCH’]) && $_SERVER[‘HTTP_IF_NONE_MATCH’]==$etag)
) {
header(‘HTTP/1.1 304 Not Modified’);
header(‘Content-Length: 0’);
exit;
}
[/php]
Come tocco finale aggiungiamo anche la compressione gzip dei contenuti:
[php]
if (isset($_SERVER[‘HTTP_ACCEPT_ENCODING’]) && strpos($_SERVER[‘HTTP_ACCEPT_ENCODING’], ‘gzip’) !== false)
ob_start(‘ob_gzhandler’);
[/php]
Cache lato server
Il nostro obiettivo è di evitare di processare i file sorgenti ogni volta, ma farlo solo quando questi vengono modificati.
Per farlo salviamo il file finale in un file di cache e impostiamo la sua data di ultima modifica all’ultima data di modifica dei file sorgenti.
Alle richieste successive verifichiamo che questo file esista e che la sua data di modifica non sia inferiore a quella dei file sorgenti e in tal caso serviamo il file precompilato.
[php]
//cache dir
if (!file_exists(“$basedir/cache/css”)) {
mkdir(“$basedir/cache/css”);
chmod(“$basedir/cache/css”, 0777);
}
$cachefile = “$basedir/cache/css/$etagdata.css”;
if (!file_exists($cachefile) || filemtime($cachefile)<$lastmtime) {
$csscontent = “”;
foreach ($files as $file) {
$template = “css/$file.ccss”;
// Se il file non esiste tra i CCSS (CleanCSS), allora lo cerco come CSS normale
if (!$s->templateExists($template)) {
$template = “css/$file.css”;
$csscontent .= “/* $file.css */\\n”;
$csscontent .= trim($s->fetch($template)).”\\n”;
} else {
$csscontent .= “/* $file.ccss */\\n”;
$csscontent .= CleanCSS::convertString($s->fetch($template)).”\\n”;
}
}
file_put_contents($cachefile, $csscontent);
chmod($cachefile, 0666);
touch($cachefile, $lastmtime);
echo $csscontent;
} else {
echo file_get_contents($cachefile);
}
[/php]
I file precompilati andranno a finire nella cartella cache/css e ricordiamoci quindi di cancellarli se per qualsiasi motivo dobbiamo forzarne la rigenerazione.
Il codice sorgente completo è disponibile per il download. Come per la puntata precedente mi raccomando di assicurarvi di dare i permessi di scrittura alla cartella cache.
Col prossimo articolo si concluderà la serie sulla gestione avanzata dei css aggiungendo la generazione dinamica delle varianti delle regole e la minificazione del codice.