Ci sono varie tecniche che un programmatore può usare per migliorare le performance di una applicazione web al fine di rendere migliore la user experience. Una di queste e anche una delle meno considerate è quello di usare la cache HTTP.
La cache HTTP è una specifica universalmente adottata in tutti i browser web di ultima generazione, pertanto è possibile sfruttare questa caratteristica nelle nostre applicazioni essendo largamente supportata e di facile implementazione indipendentemente dal linguaggio di programmazione che si usa. L’uso appropriato di questa tecnica migliora considerevolmente i tempi di risposta da parte del server e ne riduce il carico. In controparte un uso inappropriato del caching potrebbe comportare di fornire agli utenti della nostra applicazione dei contenuti “obsoleti” cioè non aggiornati alla ultima versione disponibile.
In questo articolo cerchiamo di fare una panoramica sull’uso della tecnica di caching usando le intestazioni HTTP standard preposte a tal fine, vedendo anche dei casi d’uso reali in cui tale strategia risulta appropriata.
Indice
HTTP Caching
Il caching HTTP si verifica ogni qualvolta si richiede una risorsa web e il browser memorizza una copia locale di tale risorsa al fine di velocizzarne il reperimento in eventuali richieste future della stessa risorsa. Quando una risorsa viene inserita completamente nella cache, il browser può, in caso tale risorsa venga nuovamente richiesta, prelevarla dalla cache anzichè richiederla nuovamente al server. Risorse completamente “cachabili” sono per esempio, i fogli di stile CSS, i file Javascript, le immagini e persino quei contenuti dinamici che cambiano di rado. In questi casi, una volta che il browser ha scaricato tali file e inseriti nella cache, non è necessario richiederli nuovamente al server durante tutta la sessione utente. E’ evidente in queste situazioni la riduzione dei tempi di caricamento nonchè un alleggerimento del carico sul server. A livello applicativo si usano degli specifici header HTTP da allegare alla risposta (response) per indicare al browser come gestire il caching della risorsa.
HTTP Cache Headers
Ci sono due header principali per la gestione della cache: Cache-Control ed Expires.
Cache-Control
l’header Cache-Control è il più importante tra i due e serve proprio per attivare il meccanismo di caching del browser. Settando opportunamente queto header, il browser memorizza nella cache il file per il tempo specificato. Senza questa intestazione il browser richiederà nuovamente il file al server per ogni richiesta successiva. I valori che può assumere questo header sono:
Cache-Control:public
le risorse public possono essere memorizzate sia nella cache del browser dell’utente finale, ma anche su qualsiasi proxy intermedio e possono servire anche per soddisfare richieste di altri utenti.
Cache-Control:private
le risorse private possono essere cachate solo sul browser dell’utente.
Il valore di questo header è composto : oltre al tipo di caching (public o private) è possibile specificare quanto una risorsa può permanere in cache prima di essere considerata obsoleta. A tal fine si usa il parametro max-age che specifica appunto quanto una risorsa può stare in cache in quanto ritenuta ancora valida. Il valore di tale parametro è espresso in secondi.
Cache-Control:public, max-age: 31536000
Expires
Quando mediante l’header Cache-Control si attiva il caching si può inserire a supporto l’header Expires per indicare una data specifica di “scadenza” della risorsa. Superata tale data la risorsa non deve essere più considerata valida e nell’eventualità venisse nuovamente richiesta questa va reperita dal server. Fino a tale data i browser useranno la risorsa presente nella cache.
Cache-Control:public Expires: Mon, 28 Nov 2016 21:31:00 GMT
NB: Nel caso si specifichi nell’header della response sia Expires che max-age, quest’ultima ha precedenza sulla prima.
Abbiamo visto come gli header Cache-Control ed Expires ci permettono di dire al browser fino a quando una risorsa in cache può considerarsi ancora valida. Esistono altri header che invece permettono di dire al browser come reperire la risorsa dalla rete. Questo tipo di richieste si chiamano conditional request.
Conditional Request
Le richieste condizionali (conditional request) sono particolari richieste che può fare i il browser al server per chiedere se ha una copia aggiornata della risorsa. Il browser invierà alcune informazioni sulla risorsa memorizzata nella cache e il server determina se il contenuto non è aggiornato e deve essere quindi rimandato oppure se la copia del browser è già la più recente. In quest’ultimo caso il server restituisce il codice HTTP 304 (not modified).
Sebbene le richieste condizionali siano comunque chiamate verso il server, le risorse non modificate vengono restituite al browser con un response body vuoto, risparmiando il costo del trasferimento dell’intera risorsa. I servizi di back-end sono spesso in grado di determinare molto rapidamente la data dell’ultima modifica di una risorsa senza accedere necessariamente alla risorsa con conseguente risparmio sui tempi di elaborazione.
Esistono due tipi di richieste condizionali il cui discriminante è il modo su come il server determina se una risorsa è ancora valida o meno. Vediamoli in dettaglio di seguito:
Time-Based
Una richiesta condizionale time-based quindi basata sul tempo, si basa sulla data di ultima modifica della risorsa. A tal fine è il server che specifica tale data in un header specifico della response che è Last-Modified.
Cache-Control:public Last-Modified: Mon, 28 Nov 2016 17:45:03 GMT
Quando il browser deve richiedere nuovamente la risorsa invia una conditional-request in cui specifica la data di ultima modifica della risorsa presente in cache, se tale valore è uguale a quello del server viene resituito HTTP 304 (not modified), se sul server invece c’è una copia più recente della risorsa a quel punto il server invierà la risorsa aggiornata all’ultima versione. La conditional request sarà una cosa del genere:
If-Modified-Since: Mon, 28 Nov 2016 17:45:03 GMT
Content-Based
Una richiesta condizionale basata sul contenuto si basa sui cosiddetti Etags (Entity Tag) che non sono altro che degli hash (tipicamente MD5) della risorsa. Il principo è simile al caso visto prima con la differenza che anzichè usare la data di ultima modifica di una risorsa si usa la sua impronta (hash). Se la hash memorizzata in cache e diversa da quella sul server vuol dire che la risorsa è cambiata dall’ultima volta che è stata reperita, in questo caso il server rimanderà la risorsa aggironata. Se invece le impronte su cache e server coincidono il server risponderà con il già visto codice HTTP 304. L’uso degli Etags è particolarmente utile quando la data di ultima modifica è difficile da determinare. In questa situazione il server invierà nell’header della risposta una cosa del genere:
Cache-Control:public, max-age=31536000 ETag: "15f0fff99ed5aae4edffdd6496d7131f"
Nelle richieste successive il client userà una richiesta condizionale del tipo:
If-None-Match: "15f0fff99ed5aae4edffdd6496d7131f"
Il server similmente a if-Modified-Since verificherà se l’hash attuale della risora coincide con quella inviata nella richiesta, se si, ritornerà HTTP 304, altrimenti invierà la risorsa aggiornata.
Casi d’uso
File Statici
Il punto di partenza per qualsiasi sviluppatore che vuole sfruttare i meccanismi appena visti dovrebbe essere quello DI impostare da subito una politica di caching a quei contenuti che non cambiano. Normalmente questo includerà i file statici che vengono serviti con l’applicazione come le immagini, i file CSS e JavaScript. Poiché questi file sono in genere ri-chiesti in ogni pagina, l’uso di politiche di caching determina un grande miglioramento delle prestazioni e può essere ottenuto con poco sforzo. In questi casi, è necessario impostare l’header Cache-Control, con un valore di max-age abbastanza ampio, ad esempio di un anno in avanti dal momento della richiesta. Si raccomanda anche di usare l’header Expires impostando un valore simile.
Cache-Control:public, max-age=31536000 Expires: Thu, 28 Nov 2017 00:00:00 GMT
(31536000 secondi corrispondono ad un anno)
Alcuni web server, come ad esempio Apache, applicano automaticamente il meccanismo di caching a questo tipo di file.
Contenuti dinamici
Quando si trattano contenuti dinamici bisogna stare un pò più attenti, e sta allo sviluppatore la valutazione sul se e come usare il caching per ogni risorsa, onde evitare di fornire all’utente finale contenuti obsoleti. Due esempi possono essere i contenuti di un blog feed RSS (che non cambierà più di una volta ogni poche ore), oppure uno stream JSON che rappresentano la timeline di Twitter di un utente (che viene aggiornata nel giro di pochi secondi). In questi due casi è necessario sicuramente impostare politiche di caching diverse, valutando attentamente i periodi di validità delle risorse in cache in modo tale da non causare problemi all’utente finale.
Contenuti Privati
Sono considerati contenuti privati tutto ciò che ha a che fare con la trasmissione di dati sensibili e soggetti a criteri di sicurezza, come può essere un applicazione di home banking, dati personali dell’utente ecc. In questi casi lo sviluppatore oltre a decidere l’opportuna politica di caching deve anche considerare l’impatto di avere cache intermedie (come proxy web) che potrebbero causare il caching dei contenuti fuori dal controllo dell’utente. In caso di dubbi, è meglio non usare nessun meccanismo di caching. Negli altri casi sicuramente è da impostare un Cache-Control: private in modo tale da essere sicuri che il cahing avvenga esclusivamente nel browser del’utente finale.
NO Cache
Quando si ha a che fare con risorse i cui il fattore sicurezza è importante oppure risorse che cambiano velocemente è sconsigliabile l’uso della cache. Per esempio, tutto ciò che viene coinvolto nel processo di checkout di un carrello della spesa di uno store online non deve essere mai messo in cache. Purtroppo, semplicemente omettendo le intestazioni di cache non è garantito che il browser non userà la cache in quanto dipende dalle politiche usate internamente al browser e che variano da browser a browser. Occorre invece in questi casi ” dire” esplicitamente al browser che non si vuole usare la cache per una determinata risorsa. Questo viene fatto sempre mediante l’header Cache-Control che assumerà il valore di no-cache e no-store.
Cache-Control:no-cahe, no-store
NB: no-cache e no-store sono equivalenti e si specificano entrambi per funzionare con qualsisi browser perchè ad esempio, IE usa no-cache mentre Firefox usa no-store
Conclusioni
Abbiamo visto come i meccanismi di caching ci vengono in aiuto per ottimizzare le prestazioni di una applicazione web. Nei browser moderni ci sono ormai vari strumenti per gli sviluppatori che ci permettonno di analizzare gli header delle request/response, come Firebug per Firefox o gli Strumenti per sviluppatori di Chrome; questi strumenti sno molto utile quando si sviluppa per il web. Voglio anche segnalare il tool online REDbot come strumento immediato per vedere le intestazioni di risposta allegate ad una response, basta inserire un url fare la richiesta e vengono visualizzati gli header restituiti.