HTTP caching

Le prestazioni di siti web e applicazioni possono essere migliorate drasticamente riutilizzando le risorse già ottenute. Le web caches riducono la latenza e il traffico di rete, diminuendo il tempo necessario per rappresentare una risorsa. Attraverso l'uso dell'HTTP caching, i siti web diventano più responsivi.

I diversi tipi di cache

Il caching è una tecnica che immagazzina una copia di una risorsa data e la restituisce quando richiesta. Quando una web cache ha una risorsa richiesta in memoria, intercetta la richiesta e ritorna la sua copia invece di riscaricarla dal server originale. Così si raggiungono diversi risultati: alleggerisce il carico del server che non deve più servire tutti i client da solo, e migliora le prestazioni stando più vicina al client, ad esempio, ci mette meno tempo a restituire le risorse. Per un sito web, è una componente fondamentale nell’ottenere prestazioni elevate. D’altra parte, deve essere configurato correttamente in quanto non tutte le risorse rimangono identiche per sempre. È importante mettere in cache una risorsa solo finché non cambia.

Ci sono diversi tipi di cache: queste possono essere raggruppate in due principali categorie: private o condivise. Una cache condivisa è una cache che immagazzina risposte per essere utilizzate da più utenti. Una cache privata è una cache dedicata ad un singolo utente. Questa pagina tratterà per la maggior parte delle browser cache e di quelle del proxy, ma ci sono anche gateway cache, CDN, reverse proxy cache e load balancer che sono impiegati nei server web per maggiore affidabilità, prestazioni e scaling di siti web e applicazioni web.

What a cache provide, advantages/disadvantages of shared/private caches.

Cache private del browser

Una cache privata è dedicata ad un singolo utente. Potresti aver già visto “caching” nelle impostazioni del tuo browser. Una browser cache conserva tutti i documenti scaricati via http dall’utente. Questa cache è usata per rendere disponibili i documenti per la navigazione avanti o indietro, salvare, vedere come risorsa, ecc. senza inviare un'altra richiesta al server. Similmente migliora la ricerca offline di contenuti in cache.

Cache condivise

Una cache condivisa è una cache che immagazzina risposte per essere utilizzate da più di un utente. Ad esempio, un ISP o la tua compagnia potrebbero aver impostato un web proxy come parte della sua infrastruttura network locale per servire molti utenti così che risorse popolari vengono riutilizzate più volte, riducendo il traffico di rete e la latenza.

 I soggetti del caching

L'HTTP caching  non è obbligatorio, ma è normalmente utile. Le HTTP caches normalmente sono limitate alle risposte a GET e potrebbero rifiutare altri metodi. La chiave primaria della cache consiste nel metodo richiesto e nell'URI desiderato (spesso viene usato solo l'URI, in quanto solo le richieste GET vengono salvate nella cache).

Informazioni che spesso vengono salvate nella cache:

  • Risultati ottenuti da una richiesta riuscita: una risposta 200 (OK) ad una richiesta GET contenente risorse come document HTML, immagini o file.
  • Reindirizzamenti permanenti: una risposta 301 (Moved Permanently).
  • Risposte di errore: la pagina allegata ad una risposta 404 (Not Found).
  • Risultati incompleti: una risposta 206 (Partial Content).
  • Risposte diverse da GET se c'è l'equivalente di una chiave.

I dati inseriti possono essere composti da più risposte differenziate da una chiave secondaria se la richiesta è oggetto di scambio di contenuti. Per ulterior informazioni riguardo all'header Vary leggi below.

Controllare la cache

L'header Cache-Control

L'header generico Cache-Control HTTP/1.1 viene usato per specificare i meccanismi del caching per richieste e risposte. Questo header può essere usato per definire le caching policies attraverso le direttive proposte.

No caching

La cache non dovrebbe salvare né le richieste né le risposte. Ogni richiesta viene mandata al server, dovendo quindi riscaricare ogni volta la risposta.

Cache-Control: no-store

Cache but revalidate

La cache invia chiede una validazione da parte del server prima di fornire la sua copia.

Cache-Control: no-cache

Private and public caches

La direttiva "public" indica che la risposta può essere salvata da qualsiasi cache. Questo può essere utile per salvare pagine con codici di risposta HTTP che normalmente non si potrebbero salvare.

Dall'altro lato, "private" indica che la risposta è legata ad un solo utente, quindi non sarà salvata in una cache condivisa. In questo caso la risposta potrebbe essere salvata da una cache privata del browser.

Cache-Control: private
Cache-Control: public

Expiration

La direttiva più importante è max-age=<secondi>, che indica il tempo massimo (in secondi) in cui la risorsa è considerata fresca ("fresh"). Questa direttiva è relativa al tempo indicato dalla richiesta e sovrascrive l'header Expires (se impostato). I file che non mutano (come ad esempio immagini, fogli CSS e script JavaScript) sono buoni candidati per essere salvati nella cache

Per ulteriori dettagli visita la sezione Freshness.

Cache-Control: max-age=31536000

Validation

Quando viene usata la direttiva "must-revalidate" la cache deve verificare lo stato della risorsa prima di usarla, evitando le risorse scadute. Per ulteriori dettagli visita la sezione Validation.

Cache-Control: must-revalidate

L'header Pragma

Pragma è un header HTTP/1.0. Pragma: no-cache corrisponde a Cache-Control: no-cache,  forzando quindi le cache a validare le risorse prima di utilizzarle. Pragma non è incluso nelle risposte HTTP, quindi non può sostituire completamente l'header Cache-Control presente in HTTP/1.1.

Si dovrebbe utilizzare l'header Pragma solo per retrocompatibilità con le cache in HTTP/1.0, dove l'header Cache-Control di HTTP/1.1 non è presente.

Freschezza

Una volta che una risorsa è memorizzata in cache, potrebbe teoricamente essere servita dalla cache per sempre. Le cache hanno una memoria finita quindi gli oggetti sono periodicamente rimossi dalla memoria. Questo processo si chiama sfratto della cache (cache eviction). D’altra parte, alcune risorse possono essere cambiate sul server quindi la cache andrebbe aggiornata. Dato che l’HTTP è un protocollo client-server, i server non possono contattare le cache e i client quando una risorsa cambia; devono comunicare un tempo di scadenza per la risorsa. Prima di questa scadenza, la risorsa è fresca; dopo la scadenza, la risorsa è datata. Gli algoritmi di sfratto spesso prediligono risorse fresche a quelle datate. Da notare che una risorsa datata non è sfrattata o ignorata; quando la cache riceve una richiesta per una risorsa datata, la inoltra con un If-None-Match per verificare se è di fatto ancora fresca. In questo caso il server ritorna un 304 (Non Modificata) header senza inviare il body della risorsa richiesta, risparmiando banda.

Qui è un esempio di questo processo con una proxy cache condivisa:

Show how a proxy cache acts when a doc is not cache, in the cache and fresh, in the cache and stale.

La durata della freschezza è calcolata secondo diversi header. Se l'header "Cache-Control: max-age=N" è presente la durata sarà pari ad N, altrimenti (come spesso accade) si controlla se è presente l'header Expires. Se esiste, la durata equivale alla differenza tra il valore dell'header Date e il valore dell'header Expires.

Controllo euristico della freschezza

Se un server centrale non specifica esplicitamente la durata della freschezza (usando ad esempio gli header Cache-Control o Expires) potrebbe essere utilizzato un approccio euristico.

In questo caso si cerca l'header Last-Modified. Se presente, la freschezza è uguale alla differenza tra il valore di ques'ultimo e il valore di Date divisa per 10:

dataDiScadenza = orarioDellaRisposta + durataDellaFreschezza - tempoDiVitaCorrente

dove tempoDiRisposta è l'ora in cui la risposta è stata ricevuta dal browser. Per maggiori informazioni: RFC 7234: Hypertext Transfer Protocol (HTTP/1.1): 4.2.2.  Calculating Heuristic Freshness.

Revved resources

Più si usa una risorsa in cache, più reattive saranno le risposte e le performance di un sito web. Per ottimizzarle, la norma è impostare i tempi di scadenza più in là possibile nel futuro. Questo è possibile con risorse che sono regolarmente aggiornate, o spesso, ma è problematico per risorse che vengono aggiornate raramente. Sono le risorse che beneficerebbero di più dalle cache, eppure questo le rende difficili da aggiornare. È tipico delle risorse tecniche incluse e linkate da ogni pagina web: I file Javascript e CSS cambiano di rado, ma quando succede vuoi che vengano aggiornati rapidamente.

I web developer hanno inventato una tecnica che Steve Souders chiamò revving. File sporadicamente aggiornati sono nominati in un modo specifico: nel loro URL, solitamente nel nome del file, un numero di revisione (o versione) viene aggiunto. In questo modo ogni nuova revisione di questa risorsa viene considerata come una risorsa che non cambia e può avere un tempo di scadenza molto avanti nel futuro, di solito un anno o più. Per avere le nuove versioni, tutti i link che fanno capo a loro vanno cambiati, questa è la limitazione di questo metodo: complessità aggiuntive vengono solitamente risolti dalle tool chain usate dai web developer. Quando la risorsa che cambia di rado viene modificata, questa induce un cambiamento aggiuntivo a risorse spesso variabili. Quando vengono lette, le nuove versioni della altre vengono anch’esse lette.

Questa tecnica ha un vantaggio aggiuntivo: aggiornare due risorse in cache nello stesso momento preverrà situazioni in cui la risorsa vecchia viene usata in combinazione con quella nuova dell’altra. Questo è molto importante quando i siti web hanno fogli CSS o script JS che hanno dipendenze reciproche, ad esempio che dipendono l’uno sull’altro perché si riferiscono agli stessi elementi HTML.

How the revved cache mechanism works. With minor typo fix to grammar issue: https://github.com/mdn/sprints/issues/2618

La versione di revisione aggiunta a una revved resource non ha bisogno di essere una classica stringa di revisione come 1.1.3, o un numero che aumenta monotonamente. Può essere qualsiasi cosa che impedisca le collisioni, come un hash o una data.

Validazione della cache

Quando una risorsa presente nella cache scade può essere validata o può essere richiesta nuovamente al server. La validazione può avvenire solo se il server ha fornito un validatore forte (strong validator) o un validatore debole (weak validator).

La rivalidazione comincia quando l'utente ricarica la pagina, oppure quando la risposta contenuta nella cache inculde l'header "Cache-Control: must-revalidate". Un altro fattore è costituito dalle impostazioni nel pannello Avanzate->Cache, dove è possibile forzare la validazione ogni volta che un documento viene caricato.

ETags

L'header ETag, contenuto in una risposta, è un valore opaco per lo user agent (opaque-to-the-useragent) che può essere usato come un validatore forte. Questo significa che uno user-agent HTTP, ad esempio un browser, non sa cosa cosa questa stringa rappresenti e non può predirne il valore. Se l'header ETag fosse parte di una risposta contenente una risorsa, il client può inserire If-None-Match nell'header delle richieste future, così da validare le risorse salvate nella cache.

Last-Modified

L'header Last-Modified, contenuto in una risposta, può essere usato come un validatore debole. È considerato debole per via della sua risoluzione (un secondo). Se in una risposta è presente l'header Last-Modified il client può inserire If-Modified-Since nell'header della richiesta per validare il documento salvato nella cache.

Quando viene fatta una richiesta di validazione il server può ignorarla e rispondere con 200 OK, oppure può ritornare 304 Not Modified (senza corpo) per permettere al browser di usare il contenuto della cache. In quest'ultimo caso ci possono essere anche altri header che aggiornano la data di scadenza del documento salvato nella cache.

"Variare" le risposte

L'header delle risposte HTTP Vary indica gli header con cui usare una risposta salvata nella cache.

Quando una cache riceve una richiesta contenente l'header Vary non deve usare una risposta salvata nella cache, a meno che tutti gli header contenuti nel campo Vary corrispondano agli header contenuti nella cache.

The Vary header leads cache to use more HTTP headers as key for the cache.Questa proprietà viene principalmente usata per salvare nella cache una risorsa non compressa o compressa in vari formati, per inviarla poi agli user agent a seconda delle codifiche che supportano. Per esempio, un server può impostare Vary: Accept-Encoding per assicurarsi che la risorsa sia salvata nella cache secondo le codifiche richieste, come Accept-Encoding: gzip,deflate,sdch.

Vary: Accept-Encoding
Nota: Usa Vary cautamente—può ridurre drasticamente i vantaggi del caching! Un caching server dovrebbe usare la normalizzazione per rimuovere duplicati e richieste non necessarie. Questo si verifica particolarmente quando si usa Vary con header che possono accettare diversi valori.

L'header Vary può tornare utile per fornire contenuti diversi a utenti mobili o desktop, o per permettere ai motori di ricerca di ottenere la versione mobile di una pagina (specificando opzionalmente che non c'è Cloaking). Questo viene attuato con l'header Vary: User-Agent, dato che il valore dell'header User-Agent è diverso tra desktop e mobile.

Vary: User-Agent

Normalizzazione

Come anticipato sopra, i caching server utilizzerano le risposte salvate nella cache solo con le richieste i cui header (e valori annessi) corrispondono esattamente alle risposte salvate. Questo significa che per ogni piccola differenza verrà fatta una nuova richiesta al server centrale, che verrà poi salvata nella cache.

Per esempio, tutte le richieste con i seguenti header verranno richieste al server, per poi essere salvate nella cache: Accept-Encoding: gzip,deflate,sdch, Accept-Encoding: gzip,deflateAccept-Encoding: gzip. Probabilmente, però, il server centrale risponderà sempre con la stessa risorsa (un file gzip)!

Per evitare duplicati e richieste non necessarie, i caching server dovrebbero usare la normalizzazione per pre-processare le richieste e per salvare solo i file necessari nella cache. Ad esempio, nel caso di Accept-Encoding si possono controllare gzip e gli altri tipi di compressione prima di procedere. In "pseudo codice" si può codificare come:

// Normalizza Accept-Encoding
if (req.http.Accept-Encoding) {
  if (req.http.Accept-Encoding ~ "gzip") {
    set req.http.Accept-Encoding = "gzip";
  }
  // elsif other encoding types to check
else {
  unset req.http.Accept-Encoding;
  }
}

User-Agent ha ancora più varianti di Accept-Encoding. Quindi s Vary: User-Agent viene utilizzato per salvare nella cache le varianti dei file per mobile/desktop si potrebbe controllare se "mobile""desktop" sono presenti nell'header User-Agent.

Guarda anche