Iniziamo una lunga serie di articoli sull’ottimizzazione dei siti web in cui verranno trattate le tecniche e i suggerimenti per rendere efficiente il proprio sito web.
In questo articolo parlo della cache dei browser, come e perché gestirla. Vedrai cos’è una richiesta http, un’intestazione http e le tre tecniche principali per istruire il browser su come dovrà comportarsi con gli elementi di una pagina, cioé, quando leggerli dal server d’origine e quando caricarli dalla cache.
Cos’è una richiesta http?
Quando il browser visita un sito apre una connessione con l’host (il server che ospita il sito), composta da una richiesta e da una risposta. Supponiamo di voler visitare la pagina http://jblog.it/informazioni.html.
La richiesta http che invia il browser
GET /informazioni HTTP/1.1
Host: jblog.it
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: it
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
La risposta http
HTTP/1.1 200 OK
Date: Fri, 24 Apr 2009 10:26:08 GMT
Server: Apache
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
Le informazioni inviate e restituite potrebbero essere molte di più, ognuna ha un suo preciso significato, e possono essere controllate e gestite via codice (lato client e lato server). Sul sito del w3c puoi trovare l’elenco completo dei campi di intestazione delle connessioni http: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html.
La prima riga della richiesta: GET /informazioni HTTP/1.1
In questa riga si trovano sostanzialmente tre informazioni: il tipo della richiesta (GET, POST, HEAD, PUT, DELETE, OPTIONS, TRACE), l’indirizzo della risorsa (/informazioni) e il protocollo con cui si effettua la richiesta (HTTP/1.1).
La prima riga della risposta: HTTP/1.1 200 OK
Il codice 200 indica il successo della risposta http. Esistono vari codici numerici per indicare l’esito della risposta http. Ad esempio, il classico codice 404 indica che non è stata trovata la risorsa specificata nella richiesta http. Se ti interessa puoi trovare una lista completa su wikipedia: http://it.wikipedia.org/wiki/Elenco_dei_codici_di_stato_HTTP.
Perché bisogna ridurre le richieste http?
Il numero di richieste http dipende dal numero di elementi da caricare. Ad esempio, se in una pagina ci sono 2 immagini e 1 foglio di stile il browser effettuerà 4 richieste http (una è per il codice html della pagina).
Come avrai ben capito, la regola è “ridurre gli elementi della pagina”, o meglio “ottimizzare gli elementi di una pagina senza eliminarne nessuno”. Con “ottimizzare” non intendo “ridurre la loro dimensione”. Infatti riducendo la dimensione degli elementi avresti un miglior tempo di caricamento mantenendo lo stesso numero di richieste http.
Ottimizzare o ridurre?
Senza dubbio è importantissimo ottimizzare la qualità e le dimensioni dei componenti di una pagina, ma una singola richiesta http è molto più gravosa di qualche kbyte in più. Ecco perché ridurre le richieste http è fondamentale.
I campi di intestazione del protocollo HTTP per gestire la cache
Il protocollo HTTP 1.1 ha alcuni campi di intestazione (o header fields) che consentono di fornire alcune informazioni riguardo un file (data, ultima modifica, periodo, ecc.) e controllare la cache.
Cache-Control
Specifica le direttive che devono rispettare tutti i metodi di caching dei componenti restituiti. Le direttive di cache-control:
- sovrascrivono gli algoritmi di caching di default
- non possono essere specificate per una singola cache (se si utilizza un proxy o un gateway)
- sono unidirezionali, cioè non implicano che la risposta abbia le stesse direttive
Alcune direttive possono essere utilizzate solo in fase di richiesta o solo in fase di risposta.
- direttive di richiesta: no-cache, no-store, max-age, max-stale, min-fresh, no-transform, only-if-cached, cache-extension.
- direttive di risposta: public, private, no-cache, no-store, no-transform, must-revalidate, proxy-revalidate, max-age, s-maxage, cache-extension.
public. La risposta può essere memorizzata in qualsiasi cache. Utile se si utilizza una cache condivisa.
private. La risposta non deve essere memorizzata nella cache condivisa.
no-cache. Evita che la risposta venga memorizzata in cache. SOLO in fase di risposta è possibile scrivere no-cache="field-name", dove field-name è il nome di un campo di intestazione che non si vuole memorizzare in cache. In pratica, quando una risposta (ad es. una pagina) viene memorizzata in cache, vengono memorizzati anche i suoi campi di intestazione. Se uno di quei campi è “Set-Cookie” e non vogliamo che venga memorizzato in cache basterà scrivere no-cache="Set-Cookie". In tal modo la pagina verrà comunque memorizzata in cache senza il campo di intestazione “Set-Cookie”. L’utilità? Dato che “Set-Cookie” crea un cookie, memorizzandolo con la pagina, quando si riutilizzerà la versione in cache verrà creato nuovamente il cookie.
no-store. Evita che la risposta venga memorizzata permanentemente nella memoria non-volatile. Questa direttiva si applica a qualsiasi tipo di cache (condivisa e non). La sua utilità consiste nel fatto che si impedisce la memorizzazione di vecchie versioni di pagine e documenti che vengono costantemente aggiornati.
s-maxage. Specifica il periodo (in secondi) dopo il quale devono essere aggiornate le risposte nella cache condivisa. Questa direttiva sovrascrive la direttiva max-age e il campo di intestazione expires.
max-age. Indica che il client è disposto ad accettare una risposta con un periodo (in secondi) maggiore di quello specificato. Se non è specificata la direttiva max-stale, il client non accetterà una risposta datata.
min-fresh. Indica che il client è disposto ad accettare una risposta che sia aggiornata almeno a un dato periodo (in secondi).
max-stale. Indica che il client è disposto ad accettare una risposta che supera la sua scadenza di un certo numero di secondi.
only-if-cached. Consente di caricare un contenuto solo se in cache è presente una sua versione, altrimenti non viene restituita alcuna risposta. Questa direttiva è utile nelle reti particolarmente lente.
must-revalidate. Poichè una cache può essere configurata in modo che ignori il tempo di scadenza di una risposta, questa direttiva consente di forzare l’aggiornamento di una risposta già memorizzata in cache. Questa direttiva viene sempre rispettata dal browser.
proxy-revalidate. Ha gli stessi effetti di must-revalidate, ma solo sulla cache condivisa.
no-transform. Evita che una cache intermedia (ad es. di un proxy) manipoli la risposta per risparmiare risorse. Infatti può succedere che un proxy comprima un’immagine per risparmiare spazio. Questa direttiva è utile nel caso di immagini da trasmettere fedelmente (ad es. in campo medico) in cui ogni pixel deve rimanere al suo posto.
Expires
Indica una data (con l’ora) a partire dalla quale la risposta memorizzata in cache deve essere considerata “vecchia”, quindi essere aggiornata.
La data deve essere espressa in un preciso formato definito dal protocollo HTTP. Ad esempio: Tue, 03 Nov 2007 17:30:00 GMT.
Expires: Tue, 03 Nov 2007 17:30:00 GMT
Pragma
Indica che la risposta non deve essere memorizzata in cache. L’unica direttiva è appunto “no-cache”.
In pratica esegue la stessa funzione di cache-control=”no-cache”, ma è necessario utilizzarla per mantenere la compatibilità con la versione 1.0 del protocollo HTTP.
Pragma: no-cache
ETag
Gli ETag o Entity Tag rappresentano un modo semplice ed efficiente per determinare se la risposta nella cache corrisponde a quella che si trova nel server di origine. Concretamente un ETag non è altro che una stringa che identifica univocamente una risposta memorizzata in cache.
Com’è composta la stringa? Non ha dei valori predefiniti ma deve essere creata in base a dei valori che potrebbero identificare la versione di una risposta. Generalmente si preferisce identificare le risposte in base alla data dell’ultima modifica e alla dimensione. Queste due informazioni vengono compresse in modo da formare un’unica stringa di dimensioni contenute, ad esempio utilizzando algoritmi di hashing come md5.
Gli svantaggi. L’unico svantaggio è che il formato e il valore di un ETag cambia da server a server. Se un determinato file risiede su più server nella stessa directory, con identiche dimensioni, permessi, timestamp, a seconda del server da cui si legge. La soluzione consiste nel creare il valore dell’ETag con un semplice script lato-server. In pratica, per avere dei benefici dagli ETag è necessario gestirli completamente con degli script lato-server, altrimenti è meglio non utilizzarli. Ecco un esempio di script.
ETag: "0401g4ff812fgk12gqg2g4j7"
Last-Modified
Rappresenta la data e l’ora dell’ultima modifica del file sul server d’origine nella forma, ad esempio, Tue, 03 Nov 2007 17:30:00 GMT.
Questo campo di intestazione è utile soprattutto nelle pagine dinamiche in cui vengono stampati dei dati ricavati da un database, o nei file creati dinamicamente (ad es. un foglio di stile che ne unisce 5 per evitare di avere 5 richieste http).
If-Match
Fornisce un metodo efficiente per controllare se la risposta ottenuta corrisponde a quella memorizzata in cache, utilizzando gli ETag, ed eventualmente aggiornarla. Viene anche usata per evitare modifiche accidentali sull’errata versione di una risorsa.
If-Match:* oppure If-Match:0401g4ff812fgk12gqg2g4j7
Nel primo caso, il carattere “*” è un carattere speciale e il campo fa corrispondere tutti i componenti di una risposta.
Nel secondo caso, il campo di intestazione fa corrispondere l’ETag con la stringa specificata.
If-None-Match
Fornisce un metodo efficiente per controllare se la risposta ottenuta corrisponde a quella memorizzata in cache, utilizzando gli ETag, ed eventualmente aggiornarla.
Al contrario di If-Match non si deve avere la corrispondenza con l’ETag confrontato o con il carattere “*”.
If-None-Match:* oppure If-Match:0401g4ff812fgk12gqg2g4j7
If-Modified-Since
Fornisce un metodo automatico per aggiornare la versione in cache di una risposta che non è stata modificata da una certa data (e ora) nel formato: Tue, 03 Nov 2007 17:30:00 GMT. Se la risposta è stata modificata a partire dalla data specificata si effettua l'aggiornamento della versione in cache, altrimenti viene restituita la versione memorizzata in cache.
If-Modified-Since: Sat, 12 Oct 2008 20:12:00 GMT
If-Unmodified-Since
Fornisce un metodo automatico per aggiornare la versione in cache di una risposta che non è stata modificata da una certa data (e ora) nel formato: Tue, 03 Nov 2007 17:30:00 GMT. Se la risposta NON è stata modificata a partire dalla data specificata si effettua l'aggiornamento della versione in cache, altrimenti viene restituita la versione memorizzata in cache.
If-Unmodified-Since: Sat, 12 Oct 2008 20:12:00 GMT
If-Range
Se la versione in cache di una risposta è parziale, questo campo di intestazione permette di ottenere solo la parte mancante. La condizione fallisce se la risorsa richiesta è stata modificata. In questo caso sarà effettuata una seconda richiesta per ottenere una risposta completa che aggiorni la versione memorizzata in cache.
If-Range: Sat, 12 Oct 2008 20:12:00 GMT
If-Range: 0401g4ff812fgk12gqg2g4j7
Nel primo caso si confronta la data dell’ultima modifica, mentre nel secondo caso si confronta la stringa dell’ETag.
Come utilizzare i campi di intestazione
In questo paragrafo non troverai una ricetta pronta per gestire la cache perché non so quali sono le tue esigenze. Leggi attentamente il paragrafo precedente per decidere le operazioni da effettuare in ogni pagina e risorsa del tuo sito.
Il mio consiglio è quello di analizzare i contenuti delle tue pagine (attuali e futuri) e decidere se memorizzare in cache una particolare risorsa, come gestire la versione della cache, il tempo di validità, ecc.
Se nel paragrafo precedente alcuni punti sono spiegati male, manca qualcosa o ci sono errori fammelo sapere e correggerò tutto al più presto.
Ci sono tre metodi per impostare i campi di intestazione:
- impostando il tag <meta>
- all’interno del proprio codice lato-server (php, asp, cgi, ecc.)
- configurando il web server Apache tramite il file .htaccess o il file principale httpd.conf
1. Impostare i tag <meta>
Sicuramente conoscerai già il tag <meta>. Probabilmente l’avrai utilizzato per inserire description e keywords nelle tue pagine web, per migliorare l’indicizzazione del tuo sito da parte dei motori di ricerca.
Per ogni campo di intestazione che decidi di utilizzare devi:
- creare un tag <meta> con gli attributi “http-equiv” e “content”
- scrivere il nome del campo nell’attributo ”http-equiv”, rispettando le lettere maiuscole e minuscole
- scrivere il valore del campo nell’attributo “content”
Esempi
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="Tue, 03 Nov 2007 17:30:00 GMT" />
2. Il codice lato-server
Ogni linguaggio di programmazione ha la sua funzione per inviare un campo di intestazione.
Al momento l’unico linguaggio che conosco è PHP, con cui basterà scrivere header("Cache-Control: no-cache"); .
I campi di intestazione vanno scritti prima di qualsiasi output sulla pagina, altrimenti si verifica un errore che in sostanza dice “impossibile modificare l’intestazione – gli header sono stati già inviati”.
Tutto qui? In teoria non ci sarebbe altro… Posso solo aggiungere delle situazioni in cui diventa fondamentale utilizzare la funzione header.
Combinare più fogli di stile
Per diminuire le richieste http è una buona abitudine combinare più fogli di stile in uno solo. Si potrebbe fare manualmente, ma se in futuro vorrai effettuare delle modifiche dovrai modificare i singoli file e ricombinarli oppure modificare il file grande con la difficoltà di aver a che fare con centinaia di righe di codice.
Il metodo è quello di creare un file .css in cui inserire uno script php che permetta di stampare al suo interno tutti i file .css da te specificati. Dovrai inoltre impostare Apache affinché processi i file .css come file .php .
Per file dinamici di questo tipo sarà necessario richiamare più volte la funzione header all’inizio del file in modo da inviare i campi di intestazione riguardanti la gestione della cache.
Questo argomento farà parte del prossimo articolo su come combinare e ottimizzare immagini, css e script.
Ecco un semplice script php.
< ?
$files = array("css1.css","css2.css");
$mtime = 0;
$this_mtime = 0;
ob_start();
foreach($files as $file)
{
if(file_exists($file))
{
include($file); /* includo il file */
$this_mtime = filemtime($file); /* leggo la data dell'ultima modifica */
if($mtime < $this_mtime) $mtime=$this_mtime;
}
}
$lenght = ob_get_length(); /* lunghezza del contenuto */
$content = ob_get_contents(); /* il contenuto nella variabile $content da stampare successivamente */ ob_end_clean();
/* Headers */
header("Content-Type: text/css");
header("Last-Modified: ".date("D, d M Y H:i:s",$mtime)." GMT");
header("Content-Length",$lenght);
header("ETag: ".md5("compressed.css".$lenght.$mtime)); /* il file si chiama compressed.css */
header("Cache-Control: max-age=345600");
/* Stampo il contenuto */
echo $content;
?>
- per calcolare l’ultima modifica da inviare nel campo di intestazione “Last-Modified” sarà quella del file che è stato modificato più recentemente
- nell’ETag inserisco anche il nome del file per aumentare l’univocità dell’ETag
Come ultima operazione inserire nel file .htaccess la riga:
AddHandler application/x-httpd-php .css
Questa riga fa in modo che Apache processi i file css come file php.
Pagine dinamiche e database
Quando una pagina “dinamica” ha sempre gli stessi dati per diversi giorni è importante fare in modo che un utente, a partire dalla seconda visita, non ricarichi una nuova versione di quella pagina ma legga la versione memorizzata in cache.
Nei cms, come in Wordpress, c’è una netta separazione fra template e codice. In alcuni non è possibile creare un template per una singola pagina per altri, come Wordpress, si. Tuttavia, secondo me, è totalmente inutile creare un template per scrivere solo i tag <meta> e avere una gestione personalizzata della cache per quella pagina. Per questo motivo conviene inviare i campi di intestazione all’interno del codice.
Una prima idea è quella di definire delle condizioni per cui dei record restituiti dal database devono essere considerati “vecchi”, e in base a queste ed altre condizioni inviare gli opportuni valori dei campi di intestazione.
3. Configurare il web server Apache
Per gestire la cache con Apache è necessario caricare i moduli mod_expires e mod_headers.
Caricare i moduli
- cerca e apri il file httpd.conf
- individua la lunga lista di “mod_…”
- individua le righe con “mod_expires” e “mod_header”
- elimina (se c’è) il carattere “#” che si trova prima del nome dei moduli che vuoi attivare
- riavvia il server Apache
A questo punto, nel file .htaccess bisogna definire le regole che consentono di gestire la cache a seconda del tipo o estensione dei file richiesti durante le connessioni http.
Il modulo mod_expires
Per conoscere meglio questo modulo ti consiglio di andare a leggere la guida ufficiale di Apache (in inglese): http://httpd.apache.org/docs/2.2/mod/mod_expires.html.
Il modulo mod_headers
Per conoscere meglio questo modulo ti consiglio di andare a leggere la guida ufficiale di Apache (in inglese): http://httpd.apache.org/docs/2.2/mod/mod_headers.html.
Gestione della cache in base all’estensione
ExpiresActive On ExpiresDefault A300 <FilesMatch ".html$"> Expires A86400 </FilesMatch> <FilesMatch ".(gif|jpg|png|js|css)$"> Expires A2592000 </FilesMatch>
- la prima riga (ExpiresActive) attiva il modulo “mod_expires”
- la seconda riga (ExpiresDefault) imposta un valore di default del campo di intestazione “Expires” pari a 300 secondi
- il primo blocco <FilesMatch> fa in modo che tutti i file html abbiano il campo di intestazione “Expires” con il valore 86400 secondi
- il secondo blocco <FilesMatch> fa in modo che tutti i file gif, jpg, png, js, css abbiano il campo di intestazione “Expires” con il valore 2592000 secondi
- la lettera A indica che il valore viene impostato dopo l’accesso al file, mentre la lettera M imposterebbe il valore dopo la modifica al file
Svantaggi. Spesso capita che alcuni file, benché siano dello stesso tipo, abbiano estensioni diverse. Ad esempio, le immagini con una compressione Jpeg possono avere l’estensione jpg oppure jpeg. Quindi se capitasse un file jpeg non si avrebbe la corrispondenza.
Gestione della cache in base al tipo MIME
Per assicurarci di individuare tutti i file di un certo tipo è necessario leggere il loro tipo MIME.
ExpiresActive On ExpiresDefault A300 ExpiresByType text/css "access plus 1 day" ExpiresByType text/javascript "access plus 3 days" ExpiresByType image/png "access plus 2 months"
Forzare l’aggiornamento dei componenti
Se si utilizzano dei componenti che devono essere sempre aggiornati spesso non basta scrivere i campi di intestazione "Cache-Control: no-cache" e "Pragma: no-cache". Esistono infatti prove documentate sul fatto che a volte questi campi vengono ignorati dai browser.
Quando utilizzavo un script captcha notavo che non si aggiornava l’immagine, il che era davvero un problema in quanto se l’utente sbagliava il codice doveva ricaricarsi la pagina con una nuova immagine.
La soluzione consiste nello scrivere l’indirizzo dell’immagine con un elemento dipendente dal tempo. Quindi, invece di scrivere:
<img src="myimage.jpg" alt="La mia immagine" />
si cambierà l’indirizzo in:
<img src="myimage.jpg?<? echo time(); ?>" alt="La mia immagine" />
dove la funzione time() restituisce data e ora attuali in numeri interi (ad es. 11022103710).
Nel caso si utilizzi l’immagine come sfondo, anziché scrivere:
<div style="background:#000 url('img/blackback.png') no-repeat left bottom"></div>
si dovrà modificare l’indirizzo in:
<div style="background:#000 url('img/blackback.png?<? echo time(); ?>') no-repeat left bottom"></div>
Uno dei possibili svantaggi sta nel fatto che non tutti i file possono essere processati con il compilatore php. Se vuoi che un file .js (javascript) o .css (foglio di stile) venga processato come un file php basterà aggiungere al file .htaccess le seguenti righe:
AddHandler application/x-httpd-php .css
AddHandler application/x-httpd-php .js
Per concludere, scrivendo questo articolo ho scoperto un mondo riguardo la cache e la sua gestione, ed ho capito che gestire adeguatamente la cache permette di ottimizzare notevolmente il caricamento delle pagine di un sito web.
Come utilizzavi la cache fino ad oggi? Ti limitavi a scrivere i tre tag <meta> o avevi già pensato di iniziare a utilizzare la cache più seriamente? Ti sembra importante o è solo una mia impressione? L’articolo è scritto bene o manca qualcosa?
Non perdere il prossimo articolo: “Ridurre le richieste HTTP (parte 2): combinare e ottimizzare immagini, script e css”.
Crediti
- Libro. Creare siti web ad alte prestazioni: http://www.tecnichenuove.com/libri/creare_siti…
- Guida ufficiale di Apache: http://httpd.apache.org/docs/2.2/
- Use Server Cache Control to Improve Performance: http://websiteoptimization.com/speed/tweak/cache/
- Foto1: http://www.flickr.com/photos/aureusbay/297387489/
- Foto2: http://www.flickr.com/photos/thingsarebetterwithaparrott/1054908626/
- Foto3: http://www.flickr.com/photos/galopoulos/567890941/


