Caching HTTP
Cos’è il Caching HTTP
La cache HTTP memorizza una risposta associata a una richiesta e riutilizza la risposta memorizzata per le richieste successive.
La riutilizzabilità presenta diversi vantaggi. Innanzitutto, poiché non è necessario inviare la richiesta al server di origine, più il client e la cache sono vicini, più veloce sarà la risposta. L’esempio più tipico è quando il browser stesso memorizza una cache per le richieste del browser.
Inoltre, quando una risposta è riutilizzabile, il server di origine non ha bisogno di elaborare la richiesta, quindi non deve analizzare e instradare la richiesta, ripristinare la sessione in base al cookie, interrogare il DB per i risultati o eseguire il rendering del motore del modello. Ciò riduce il carico sul server.
Il corretto funzionamento della cache è fondamentale per la salute del sistema.
Tipi di cache
Nella specifica di caching HTTP, ci sono due tipi principali di cache: cache private e cache condivise.
Cache private
Una cache privata è una cache legata a un client specifico, in genere una cache del browser. Poiché la risposta archiviata non è condivisa con altri client, una cache privata può archiviare una risposta personalizzata per quell’utente.
D’altro canto, se i contenuti personalizzati sono archiviati in una cache diversa da una cache privata, altri utenti potrebbero essere in grado di recuperare tali contenuti, il che potrebbe causare una perdita di informazioni involontaria.
Se una risposta contiene contenuti personalizzati e si desidera archiviare la risposta solo nella cache privata, è necessario specificare una direttiva privata.
I contenuti personalizzati sono solitamente controllati dai cookie, ma la presenza di un cookie non indica sempre che sia privato e quindi un cookie da solo non rende la risposta private.
Http Cache-Control: private
Cache condivisa
La cache condivisa si trova tra il client e il server e può archiviare risposte che possono essere condivise tra gli utenti. E le cache condivise possono essere ulteriormente sottoclassificate in proxy-cache e managed cache.
Cache proxy
Oltre alla funzione di controllo degli accessi, alcuni proxy implementano la memorizzazione nella cache per ridurre il traffico in uscita dalla rete. Questo di solito non è gestito dallo sviluppatore del servizio, quindi deve essere controllato da intestazioni HTTP appropriate e così via. Tuttavia, in passato, le implementazioni obsolete di proxy-cache, come quelle che non comprendono correttamente lo standard HTTP Caching, hanno spesso causato problemi agli sviluppatori.
Le Header kitchen-sink come le seguenti vengono utilizzate per cercare di aggirare le implementazioni di “vecchia proxy cache e non aggiornata” che non comprendono le direttive specifiche di HTTP Caching correnti come no-store.
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
Tuttavia, negli ultimi anni, poiché HTTPS è diventato più comune e la comunicazione client/server è diventata crittografata, le cache proxy nel percorso possono solo incanalare una risposta e non possono comportarsi come una cache, in molti casi. Quindi, in quello scenario, non c’è bisogno di preoccuparsi delle implementazioni obsolete di proxy cache che non riescono nemmeno a vedere la risposta.
D’altra parte, se un proxy bridge TLS decrittografa tutte le comunicazioni in modalità person-in-the-middle installando un certificato da una CA (autorità di certificazione) gestita dall’organizzazione sul PC ed esegue il controllo degli accessi, ecc., è possibile vedere il contenuto della risposta e memorizzarlo nella cache. Tuttavia, poiché CT (certificate transparency) si è diffuso negli ultimi anni e alcuni browser consentono solo certificati emessi con un SCT (signed certificate timestamp), questo metodo richiede l’applicazione di una policy aziendale. In un ambiente così controllato, non c’è bisogno di preoccuparsi che la cache proxy sia “obsoleta e non aggiornata”.
Cache Managed
Le cache managed(gestite) vengono distribuite esplicitamente dagli sviluppatori di servizi per scaricare il server di origine e distribuire i contenuti in modo efficiente. Esempi sono:
- proxy inversi,
- CDN
- service worker in combinazione con la Cache API.
Le caratteristiche delle cache gestite variano a seconda del prodotto distribuito. Nella maggior parte dei casi, è possibile controllare il comportamento della cache tramite l’intestazione Cache-Control e i propri file di configurazione o dashboard.
Ad esempio, la specifica HTTP Caching essenzialmente non definisce un modo per eliminare esplicitamente una cache, ma con una cache gestita, la risposta archiviata può essere eliminata in qualsiasi momento tramite operazioni di dashboard, chiamate API, riavvii e così via. Ciò consente una strategia di caching più proattiva.
È anche possibile ignorare i protocolli standard di specifica di HTTP Caching a favore di una manipolazione esplicita. Ad esempio, quanto segue può essere specificato per escludere una cache privata o una cache proxy, mentre si utilizza la propria strategia per memorizzare nella cache solo una cache gestita.
HTTP Cache-Control: no-store
Ad esempio, Varnish Cache utilizza la logica VCL (Varnish Configuration Language, un tipo di DSL) per gestire l’archiviazione della cache, mentre i service worker in combinazione con la Cache API consentono di creare tale logica in JavaScript.
Ciò significa che se una cache gestita ignora intenzionalmente una direttiva no-store, non è necessario percepirla come “non conforme” allo standard. Ciò che dovresti fare è evitare di utilizzare headers kitchen-sink, ma leggere attentamente la documentazione di qualsiasi meccanismo di cache gestita che stai utilizzando e esser certo di controllare la cache correttamente nei modi disposti dal meccanismo che hai scelto di utilizzare.
Nota che, alcune CDN forniscono le proprie intestazioni che sono efficaci solo per quel CDN (ad esempio, Surrogate-Control). Attualmente, è in corso il lavoro per definire un’intestazione CDN-Cache-Control per standardizzarle.
Caching Euristico
HTTP è progettato per memorizzare nella cache il più possibile, quindi anche se non viene fornito alcun Cache-Control, le risposte verranno archiviate e riutilizzate se vengono soddisfatte determinate condizioni. Questo è chiamato caching euristico.
Ad esempio, prendi la seguente risposta. Questa risposta è stata aggiornata l’ultima volta 1 anno fa.
HTTP HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Date: Tue, 22 Feb 2022 22:22:22 GMT Last-Modified: Tue, 22 Feb 2021 22:22:22 GMT
È euristicamente noto che i contenuti che non sono stati aggiornati per un anno intero non saranno aggiornati per un po’ di tempo dopo. Pertanto, il client memorizza questa risposta (nonostante la mancanza di max-age) e la riutilizza per un po’. Quanto a lungo riutilizzarla dipende dall’implementazione, ma la specifica consiglia circa il 10% (in questo caso 0,1 anni) del tempo dopo l’archiviazione.
La memorizzazione nella cache euristica è una soluzione alternativa che è stata introdotta prima che il supporto Cache-Control venisse ampiamente adottato e, sostanzialmente, tutte le risposte dovrebbero specificare esplicitamente un’intestazione Cache-Control.
Fresh e stale in base all’età
Le risposte HTTP archiviate hanno due stati: fresh e stale. Lo stato fresh indica solitamente che la risposta è ancora valida e può essere riutilizzata, mentre lo stato stale significa che la risposta memorizzata nella cache è già scaduta.
Il criterio per determinare quando una risposta è fresh e quando è stale è l’età. In HTTP, l’età è il tempo trascorso da quando è stata generata la risposta. Questo è simile al TTL in altri meccanismi di memorizzazione nella cache.
Prendiamo la seguente risposta di esempio (604800 secondi equivalgono a una settimana):
HTTP HTTP/1.1 200 OK Content-Type: text/html Content-Length: 824 Date: Sun, 10 Jul 2022 12:10:22 GMT Cache-Control: max-age=406700
La cache che ha archiviato la risposta di esempio calcola il tempo trascorso da quando è stata generata la risposta e utilizza il risultato come età della risposta.
Per la risposta di esempio, il significato di max-age è il seguente:
Se l’età della risposta è inferiore a una settimana, la risposta è fresca.
Se l’età della risposta è superiore a una settimana, la risposta è obsoleta.
Finché la risposta archiviata rimane fresca, verrà utilizzata per soddisfare le richieste del client.
Quando una risposta viene archiviata in una cache condivisa, è possibile comunicare al client l’età della risposta. Continuando con l’esempio, se la cache condivisa memorizzasse la risposta per un giorno, invierebbe la seguente risposta alle richieste client successive.
HTTP HTTP/1.1 200 OK Content-Type: text/html Content-Length: 824 Date: Sun, 10 Jul 2022 12:10:22 GMT Cache-Control: max-age=406700 Age: 83409
Il client che riceve quella risposta la troverà fresca per i restanti 518400 secondi, la differenza tra max-age e Age della risposta.
Expires o max-age
In HTTP/1.0, la freschezza era specificata dall’intestazione Expires.
L’intestazione Expires specifica la durata della cache utilizzando un tempo esplicito anziché specificando un tempo trascorso.
HTTP
Scade: Domenica, 10 Luglio 2022 12:10:22 GMT
Tuttavia, il formato dell’ora è difficile da analizzare, sono stati trovati molti bug di implementazione ed è possibile indurre problemi spostando intenzionalmente l’orologio di sistema; pertanto, max-age, per specificare un tempo trascorso, è stato adottato per Cache-Control in HTTP/1.1.
Se sono disponibili sia Expires che Cache-Control: max-age, max-age è definito come preferito. Quindi non è necessario fornire Expires ora che HTTP/1.1 è ampiamente utilizzato.
Vary
Il modo in cui le risposte vengono distinte l’una dall’altra si basa essenzialmente sui loro URL:
URL | Response body | Size | Value |
---|---|---|---|
https://esempio.com/index.html | <!doctype html>… | 0x00000001 (1 entry) | 0x00000280 (640 pixels) |
https://esempio.com/style.css | body { … | ||
https://esempio.com/script.js | function main () { … |
Ma il contenuto delle risposte non è sempre lo stesso, anche se hanno lo stesso URL. In particolare quando viene eseguita la negoziazione del contenuto, la risposta dal server può dipendere dai valori delle intestazioni di richiesta Accept, Accept-Language e Accept-Encoding.
Ad esempio, per il contenuto in inglese restituito con un’intestazione Accept-Language: en e memorizzato nella cache, non è auspicabile riutilizzare la risposta memorizzata nella cache per le richieste che hanno un’intestazione di richiesta Accept-Language: ja.
In questo caso, puoi far sì che le risposte vengano memorizzate nella cache separatamente, in base alla lingua, aggiungendo Accept-Language al valore dell’intestazione Vary.
Ciò fa sì che la cache venga codificata in base a un composto dell’URL di risposta e dell’intestazione della richiesta Accept-Language, anziché basarsi solo sull’URL di risposta.
URL | Accept-Language | Response body | Value |
---|---|---|---|
https://esempio.com/index.html | ja-JP | <!doctype html>… | 0x00000280 (640 pixels) |
https://esempio.com/index.html | en-US | <!doctype html>… | |
https://esempio.com/style.css | ja-JP | body { … | |
https://esempio.com/script.js | ja-JP | function main () { … |
Inoltre, se stai fornendo l’ottimizzazione del contenuto (ad esempio, per un design responsivo) in base allo user agent, potresti essere tentato di includere User-Agent nel valore dell’intestazione Vary. Tuttavia, l’intestazione della richiesta User-Agent ha generalmente un numero molto elevato di varianti, il che riduce drasticamente la possibilità che la cache venga riutilizzata.
Quindi, se possibile, considera invece un modo per variare il comportamento in base al rilevamento delle funzionalità anziché in base all’intestazione della richiesta User-Agent.
Per le applicazioni che utilizzano cookie per impedire ad altri di riutilizzare contenuti personalizzati memorizzati nella cache, dovresti specificare Cache-Control: private anziché specificare un cookie per Vary.
Validazione
Le risposte obsolete non vengono immediatamente scartate. HTTP ha un meccanismo per trasformare una risposta obsoleta in una nuova chiedendo al server di origine. Questo è chiamato convalida o, a volte, rivalidazione.
La convalida viene eseguita utilizzando una richiesta condizionale che include un’intestazione di richiesta If-Modified-Since o If-None-Match.
If-Modified-Since
La seguente risposta è stata generata alle 2022 11:48:22 e ha un’età massima di 1 ora, quindi sai che è nuova fino alle 01:10:22.
HTTP HTTP/1.1 200 OK Content-Type: text/html Content-Length: 824 Date: Sun, 10 Jul 2022 12:10:22 GMT Last-Modified: Sun, 10 Jul 2022 11:48:00 GMT Cache-Control: max-age=3600
Alle 01:10:22, la risposta diventa obsoleta e la cache non può essere riutilizzata. Quindi la richiesta qui sotto mostra un client che invia una richiesta con un’intestazione di richiesta If-Modified-Since, per chiedere al server se sono state apportate modifiche dall’ora specificata.
GET /index.html HTTP/1.1 Host: example.com Accept: text/html If-Modified-Since: Sun, 10 Jul 2022 11:48:00 GMT
Il server risponderà con 304 Not Modified se il contenuto non è cambiato dall’ora specificata.
Dato che questa risposta indica solo “nessuna modifica”, non c’è un corpo di risposta, c’è solo un codice di stato, quindi la dimensione del trasferimento è estremamente ridotta.
HTTP/1.1 304 Not Modified Content-Type: text/html Date: Sun, 10 Jul 2022 01:10:22 GMT GMT Last-Modified: Sun, 10 Jul 2022 11:48:00 GMT Cache-Control: max-age=3600
Dopo aver ricevuto tale risposta, il client ripristina la risposta obsoleta archiviata come nuova e può riutilizzarla per l’ora rimanente.
Il server può ottenere l’ora di modifica dal file system del sistema operativo, il che è relativamente facile da fare nel caso di file statici. Tuttavia, ci sono alcuni problemi; ad esempio, il formato dell’ora è complesso e difficile da analizzare e i server distribuiti hanno difficoltà a sincronizzare gli orari di aggiornamento dei file.
Per risolvere tali problemi, l’intestazione di risposta ETag è stata standardizzata come alternativa.
ETag/If-None-Match
Il valore dell’intestazione di risposta ETag è un valore arbitrario generato dal server. Non ci sono restrizioni su come il server deve generare il valore, quindi i server sono liberi di impostare il valore in base a qualsiasi mezzo scelgano, come un hash del contenuto del corpo o un numero di versione.
Ad esempio, se viene utilizzato un valore hash per l’intestazione ETag e il valore hash della risorsa index.html è 33a64df5, la risposta sarà la seguente:
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 824 Date: Sun, 10 Jul 2022 12:10:22 GMT ETag: "33a64df5" Cache-Control: max-age=3600
Alle 23:22:22, la risposta diventa obsoleta e la cache non può essere riutilizzata. Quindi la richiesta qui sotto mostra un client che invia una richiesta con un’intestazione di richiesta If-Modified-Since, per chiedere al server se sono state apportate modifiche dall’ora specificata.
GET /index.html HTTP/1.1 Host: example.com Accept: text/html If-None-Match: "33a64df5"
Il server restituirà 304 Not Modified se il valore dell’intestazione ETag che determina per la risorsa richiesta è lo stesso del valore If-None-Match nella richiesta.
Ma se il server determina che la risorsa richiesta dovrebbe ora avere un valore ETag diverso, il server risponderà invece con un 200 OK e l’ultima versione della risorsa.
Nota: RFC9110 preferisce che i server inviino sia ETag che Last-Modified per una risposta 200, se possibile. Durante la convalida della cache, se sono presenti sia If-Modified-Since che If-None-Match, allora If-None-Match ha la precedenza per il validatore. Se stai solo considerando la memorizzazione nella cache, potresti pensare che Last-Modified non sia necessario. Tuttavia, Last-Modified non è utile solo per la memorizzazione nella cache; è un’intestazione HTTP standard che viene utilizzata anche dai sistemi di gestione dei contenuti (CMS) per visualizzare l’ora dell’ultima modifica, dai crawler per regolare la frequenza di scansione e per altri vari scopi. Quindi, considerando l’ecosistema HTTP complessivo, è meglio fornire sia ETag che Last-Modified.
Forza la riconvalida
Se non vuoi che una risposta venga riutilizzata, ma vuoi invece recuperare sempre il contenuto più recente dal server, puoi usare la direttiva no-cache per forzare la convalida.
Aggiungendo Cache-Control: no-cache alla risposta insieme a Last-Modified ed ETag, come mostrato di seguito, il client riceverà una risposta 200 OK se la risorsa richiesta è stata aggiornata, o altrimenti riceverà una risposta 304 Not Modified se la risorsa richiesta non è stata aggiornata.
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 824 Date: Sun, 10 Jul 2022 12:10:22 GMT GMT Last-Modified: Sun, 10 Jul 2022 11:48:00 GMT ETag: deadbeef Cache-Control: no-cache
Si afferma spesso che la combinazione di max-age=0 e must-revalidate ha lo stesso significato di no-cache.
Cache-Control: max-age=0, must-revalidate
max-age=0 significa che la risposta è immediatamente obsoleta, e must-revalidate significa che non deve essere riutilizzata senza una nuova convalida una volta obsoleta, quindi, in combinazione, la semantica sembra essere la stessa di no-cache.
Tuttavia, quell’uso di max-age=0 è un residuo del fatto che molte implementazioni precedenti a HTTP/1.1 non erano in grado di gestire la direttiva no-cache, quindi per gestire quella limitazione, max-age=0 è stato utilizzato come soluzione alternativa.
Ma ora che i server conformi a HTTP/1.1 sono ampiamente distribuiti, non c’è motivo di utilizzare mai quella combinazione max-age=0 e must-revalidate, dovresti invece utilizzare semplicemente no-cache.
Non memorizzare nella cache
La direttiva no-cache non impedisce l’archiviazione delle risposte, ma impedisce invece il riutilizzo delle risposte senza una nuova convalida.
Se non vuoi che una risposta venga archiviata in nessuna cache, utilizza no-store.
HTTP Cache-Control: no-store
Tuttavia, in generale, un requisito “non memorizzare nella cache” in pratica equivale al seguente insieme di circostanze:
Non si desidera che la risposta venga archiviata da nessuno diverso dal client specifico, per motivi di privacy.
Si desidera fornire sempre informazioni aggiornate.
Non si sa cosa potrebbe accadere in implementazioni obsolete.
In questo insieme di circostanze, no-store non è sempre la direttiva più appropriata.
Le sezioni seguenti esaminano le circostanze in modo più dettagliato.
Non condividere con altri
Sarebbe problematico se una risposta con contenuto personalizzato fosse inaspettatamente visibile ad altri utenti di una cache.
In tal caso, l’utilizzo della direttiva privata farà sì che la risposta personalizzata venga archiviata solo con il client specifico e non venga divulgata ad altri utenti della cache.
HTTP Cache-Control: private
In casi del genere, anche se viene specificato no-store, deve essere specificato anche private.
Fornisci contenuti aggiornati ogni volta
La direttiva no-store impedisce che una risposta venga archiviata, ma non elimina alcuna risposta già archiviata per lo stesso URL.
In altre parole, se è già presente una vecchia risposta archiviata per un URL specifico, restituire no-store non impedirà che la vecchia risposta venga riutilizzata.
Tuttavia, una direttiva no-cache costringerà il client a inviare una richiesta di convalida prima di riutilizzare qualsiasi risposta archiviata.
HTTP Cache-Control: no-cache
Se il server non supporta richieste condizionali, puoi forzare il client ad accedere al server ogni volta e ottenere sempre la risposta più recente con 200 OK.
Gestione delle implementazioni obsolete
Come soluzione alternativa per le implementazioni obsolete che ignorano no-store, potresti vedere intestazioni kitchen-sink come le seguenti.
HTTP Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
Consigliamo di utilizzare no-cache come alternativa per gestire tali implementazioni obsolete e non è un problema se no-cache viene fornito dall’inizio, poiché il server riceverà sempre la richiesta.
Se è la cache condivisa a preoccuparti, puoi assicurarti di impedire la memorizzazione nella cache indesiderata aggiungendo anche private:
HTTP Cache-Control: no-cache, private
Cosa si perde con no-store
Si potrebbe pensare che aggiungere no-store sarebbe il modo giusto per rinunciare alla memorizzazione nella cache.
Tuttavia, non è consigliabile concedere no-store in modo generoso, perché si perdono molti vantaggi di HTTP e dei browser, tra cui la cache back/forward del browser.
Pertanto, per ottenere i vantaggi del set completo di funzionalità della piattaforma web, si preferisca l’uso di no-cache in combinazione con private.
Ricarica e forza ricarica
La convalida può essere eseguita per le richieste e per le risposte.
Le azioni di ricarica e forza ricarica sono esempi comuni di convalida eseguita dal lato browser.
Ricarica
Per il ripristino da danneggiamento della finestra o l’aggiornamento all’ultima versione della risorsa, i browser forniscono una funzione di ricarica per gli utenti.
Una visualizzazione semplificata della richiesta HTTP inviata durante un ricaricamento del browser è la seguente:
GET / HTTP/1.1 Host: example.com Cache-Control: max-age=0 If-None-Match: "deadbeef" If-Modified-Since: Sun, 10 Jul 2022 10:10:22 GMT
(Le richieste da Chrome, Edge e Firefox sono molto simili a quelle sopra; le richieste da Safari saranno leggermente diverse.)
La direttiva max-age=0 nella richiesta specifica “riutilizzo di risposte con età pari o inferiore a 0”, quindi, in effetti, le risposte archiviate in modo intermedio non vengono riutilizzate.
Di conseguenza, una richiesta viene convalidata da If-None-Match e If-Modified-Since.
Tale comportamento è definito anche nello standard Fetch e può essere riprodotto in JavaScript chiamando fetch() con la modalità cache impostata su no-cache (si noti che reload non è la modalità corretta per questo caso):
js // Note: "reload" non e' il modo giusto per un normale reload; "no-cache" is fetch("/", { cache: "no-cache" });
Forza ricarica
I browser usano max-age=0 durante i ricaricamenti per motivi di retrocompatibilità, perché molte implementazioni obsolete precedenti a HTTP/1.1 non comprendevano no-cache. Ma no-cache ora va bene in questo caso d’uso e forza ricarica è un modo aggiuntivo per ignorare le risposte memorizzate nella cache.
La richiesta HTTP durante un forza ricaricamento del browser appare come segue:
HTTP GET / HTTP/1.1 Host: example.com Pragma: no-cache Cache-Control: no-cache
(Le richieste da Chrome, Edge e Firefox sono molto simili a quelle sopra; le richieste da Safari saranno leggermente diverse.)
Dato che non si tratta di una richiesta condizionale con no-cache, puoi star certo che otterrai un 200 OK dal server di origine.
Tale comportamento è definito anche nello standard Fetch e può essere riprodotto in JavaScript chiamando fetch() con la modalità cache impostata su reload (nota che non è force-reload):
JS // Nota: "reload" — piuttosto che "no-cache" — è la modalità corretta per un "force reload" fetch("/", { cache: "reload" });
Come evitare la riconvalida
Ai contenuti che non cambiano mai dovrebbe essere assegnato un max-age lungo tramite cache busting, ovvero includendo un numero di versione, un valore hash, ecc. nell’URL della richiesta.
Tuttavia, quando l’utente ricarica, viene inviata una richiesta di riconvalida anche se il server sa che il contenuto è immutabile.
Per impedirlo, è possibile utilizzare la direttiva immutable per indicare esplicitamente che la riconvalida non è richiesta perché il contenuto non cambia mai.
HTTP Cache-Control: max-age=31536000, immutable
Ciò impedisce una riconvalida non necessaria durante i ricaricamenti.
Nota che, invece di implementare tale direttiva, Chrome ha modificato la sua implementazione in modo che la riconvalida non venga eseguita durante i ricaricamenti per le sottorisorse.
Eliminazione delle risposte archiviate
Fondamentalmente non esiste alcun modo per eliminare le risposte che sono già state archiviate con un max-age lungo.
Immagina che la seguente risposta da https://example.com/ sia stata archiviata.
HTTP Copia negli appunti HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Cache-Control: max-age=31536000
Potresti voler sovrascrivere quella risposta una volta scaduta sul server, ma il server non può fare nulla una volta che la risposta è stata archiviata, poiché non riceve più richieste a causa della memorizzazione nella cache.
Uno dei metodi menzionati nella specifica è inviare una richiesta per lo stesso URL con un metodo non sicuro come POST, ma di solito è difficile farlo intenzionalmente per molti client.
Esiste anche una specifica per un’intestazione e un valore Clear-Site-Data: cache, ma non tutti i browser la supportano e, anche quando viene utilizzata, influisce solo sulle cache del browser e non ha alcun effetto sulle cache intermedie.
Pertanto, si dovrebbe presumere che qualsiasi risposta archiviata rimarrà per il suo periodo di età massima a meno che l’utente non esegua manualmente un’azione di ricaricamento, ricaricamento forzato o cancellazione della cronologia.
La memorizzazione nella cache riduce l’accesso al server, il che significa che il server perde il controllo di quell’URL. Se il server non vuole perdere il controllo di un URL, ad esempio nel caso in cui una risorsa venga aggiornata frequentemente, dovresti aggiungere no-cache in modo che il server riceva sempre le richieste e invii le risposte previste.
Richiesta Collapse
La cache condivisa si trova principalmente prima del server di origine e ha lo scopo di ridurre il traffico verso il server di origine.
Quindi, se più richieste identiche arrivano contemporaneamente a una cache condivisa, la cache intermedia inoltrerà una singola richiesta per conto proprio all’origine, che potrà quindi riutilizzare il risultato per tutti i client. Questo è chiamato request collapse.
La riduzione richiesta si verifica quando le richieste arrivano contemporaneamente, quindi anche se nella risposta è specificato max-age=0 o no-cache, verrà riutilizzata.
Se la risposta è personalizzata per un utente specifico e non vuoi che venga condivisa in riduzione, dovresti aggiungere la direttiva private:
Modelli comuni di caching
Ci sono molte direttive nella specifica Cache-Control e potrebbe essere difficile comprenderle tutte. Ma la maggior parte dei siti Web può essere coperta da una combinazione di una manciata di modelli.
Questa sezione descrive i modelli comuni nella progettazione delle cache.
Impostazioni predefinite
Come accennato in precedenza, il comportamento predefinito per il caching (ovvero, per una risposta senza Cache-Control) non è semplicemente “non memorizzare nella cache”, ma il caching implicito secondo il cosiddetto “caching euristico”.
Per evitare tale caching euristico, è preferibile assegnare esplicitamente a tutte le risposte un’intestazione Cache-Control predefinita.
Per garantire che per impostazione predefinita vengano sempre trasferite le ultime versioni delle risorse, è prassi comune fare in modo che il valore predefinito di Cache-Control includa no-cache:
HTTP Cache-Control: no-cache
Inoltre, se il servizio implementa cookie o altri metodi di accesso e il contenuto è personalizzato per ogni utente, è necessario specificare anche private per impedire la condivisione con altri utenti:
HTTP Cache-Control: no-cache, private
Cache Busting
Le risorse che funzionano meglio con la memorizzazione nella cache sono file statici immutabili il cui contenuto non cambia mai. E per le risorse che cambiano, è una buona prassi comune cambiare l’URL ogni volta che cambia il contenuto, in modo che l’unità URL possa essere memorizzata nella cache per un periodo più lungo.
Ad esempio, considera il seguente HTML:
html Copia negli Appunti <script src="bundle.js"></script> <link rel="stylesheet" href="build.css" /> <body> hello </body>
Nello sviluppo web moderno, le risorse JavaScript e CSS vengono aggiornate frequentemente man mano che lo sviluppo procede. Inoltre, se le versioni delle risorse JavaScript e CSS utilizzate da un client non sono sincronizzate, la visualizzazione si interromperà.
Quindi l’HTML sopra rende difficile la memorizzazione nella cache di bundle.js e build.css con max-age.
Pertanto, puoi servire JavaScript e CSS con URL che includono una parte variabile in base a un numero di versione o a un valore hash. Di seguito sono riportati alcuni dei modi per farlo.
# versione nel nome file bundle.v123.js # versione nella query bundle.js?v=123 # hash nel nome file bundle.YsAIAAAA-QG4G6kCMAMBAAAAAAAoK.js # hash nella query bundle.js?v=YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
Poiché la cache distingue le risorse tra loro in base ai loro URL, la cache non verrà riutilizzata di nuovo se l’URL cambia quando una risorsa viene aggiornata.
HTML <script src="bundle.v123.js"></script> <link rel="stylesheet" href="build.v123.css" /> <body> hello </body>
Con questo design, sia le risorse JavaScript che CSS possono essere memorizzate nella cache per un lungo periodo. Quindi, per quanto tempo dovrebbe essere impostato max-age? La specifica QPACK fornisce una risposta a questa domanda.
QPACK è uno standard per la compressione dei campi di intestazione HTTP, con tabelle di valori di campo comunemente utilizzati definiti.
Di seguito sono riportati alcuni valori di intestazione cache comunemente utilizzati.
36 cache-control max-age=0 37 cache-control max-age=604800 38 cache-control max-age=2592000 39 cache-control no-cache 40 cache-control no-store 41 cache-control public, max-age=31536000
Se selezioni una di queste opzioni numerate, puoi comprimere i valori in 1 byte quando vengono trasferiti tramite HTTP3.
I numeri 37, 38 e 41 sono per periodi di una settimana, un mese e un anno.
Poiché la cache rimuove le vecchie voci quando vengono salvate le nuove voci, la probabilità che una risposta memorizzata esista ancora dopo una settimana non è così alta, anche se max-age è impostato su 1 settimana. Pertanto, in pratica, non fa molta differenza quale scegli.
Nota che il numero 41 ha la max-age più lunga (1 anno), ma con public.
Il valore pubblico ha l’effetto di rendere la risposta archiviabile anche se è presente l’intestazione Autorizzazione.
Nota: la direttiva pubblica dovrebbe essere utilizzata solo se è necessario archiviare la risposta quando è impostata l’intestazione Autorizzazione. Non è richiesta in caso contrario, perché una risposta verrà archiviata nella cache condivisa finché viene specificato max-age.
Quindi, se la risposta è personalizzata con l’autenticazione di base, la presenza di pubblico potrebbe causare problemi. Se sei preoccupato per questo, puoi scegliere il secondo valore più lungo, 38 (1 mese).
HTTP # risposta per bundle.v123.js # Se non personalizzi mai le risposte tramite Autorizzazione Cache-Control: pubblico, max-age=31536000 # Se non puoi esserne certo Cache-Control: max-age=2592000
Convalida
Non dimenticare di impostare le intestazioni Last-Modified ed ETag, in modo da non dover ritrasmettere una risorsa durante il ricaricamento. È facile generare queste intestazioni per file statici predefiniti.
Il valore ETag qui potrebbe essere un hash del file.
HTTP # risposta per bundle.v123.js Ultima modifica: mar, 22 feb 2022 20:20:20 GMT ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
Inoltre, è possibile aggiungere immutable per impedire la convalida al ricaricamento.
Il risultato combinato è mostrato di seguito.
HTTP # bundle.v123.js HTTP/1.1 200 OK Content-Type: application/javascript Content-Length: 1024 Cache-Control: public, max-age=31536000, immutable Last-Modified: Sun, 10 Jul 2022 20:20:20 GMT ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
Il busting della cache è una tecnica per rendere una risposta memorizzabile nella cache per un lungo periodo modificando l’URL quando cambia il contenuto. La tecnica può essere applicata a tutte le sottorisorse, come le immagini.
Nota: quando si valuta l’uso di immutable e QPACK: se si teme che immutable modifichi il valore predefinito fornito da QPACK, considerare che in questo caso la parte immutable può essere codificata separatamente dividendo il valore Cache-Control in due righe, sebbene ciò dipenda dall’algoritmo di codifica utilizzato da una particolare implementazione QPACK.
HTTP Cache-Control: public, max-age=31536000 Cache-Control: immutable
Risorse principali
A differenza delle sottorisorse, le risorse principali non possono essere sottoposte a cache busting perché i loro URL non possono essere decorati nello stesso modo in cui possono essere gli URL delle sottorisorse.
Se viene archiviato il seguente HTML stesso, la versione più recente non può essere visualizzata anche se il contenuto viene aggiornato sul lato server.
html <script src="bundle.v123.js"></script> <link rel="stylesheet" href="build.v123.css" /> <body> hello </body>
In tal caso, no-cache sarebbe appropriato, piuttosto che no-store, poiché non vogliamo archiviare l’HTML, ma vogliamo solo che sia sempre aggiornato.
Inoltre, aggiungendo Last-Modified ed ETag, i client potranno inviare richieste condizionali e potrà essere restituito un 304 Not Modified se non sono stati apportati aggiornamenti all’HTML:
HTTP HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Cache-Control: no-cache Last-Modified: Sun, 10 Jul 2022 20:20:20 GMT ETag: AAPuIbAOdvAGEETbgAAABBAABAAE
Quella impostazione è appropriata per l’HTML non personalizzato, ma per una risposta personalizzata tramite cookie, ad esempio dopo un accesso, non dimenticare di specificare anche private:
HTTP Copia negli Appunti HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Cache-Control: no-cache, private Last-Modified: Sun, 10 Jul 2022 20:20:20 GMT ETag: AAPuIbAOdvAGEETbgAAABBAABAAE Set-Cookie: __Host-SID=AHNtAyt7pvJrUL5g5tnGwER; Secure; Path=/; HttpOnly
Lo stesso può essere utilizzato per favicon.ico, manifest.json, .well-known e endpoint API i cui URL non possono essere modificati tramite cache busting.
La maggior parte dei contenuti Web può essere coperta da una combinazione dei due modelli descritti sopra.
Ulteriori informazioni sulle cache gestite
Con il metodo descritto nelle sezioni precedenti, le sottorisorse possono essere memorizzate nella cache per un lungo periodo di tempo tramite cache busting, ma le risorse principali (che di solito sono documenti HTML) non possono esserlo.
La memorizzazione nella cache delle risorse principali è difficile perché, utilizzando solo direttive standard dalla specifica HTTP Caching, non c’è modo di eliminare attivamente i contenuti della cache quando i contenuti vengono aggiornati sul server.
Tuttavia, è possibile implementare una cache gestita come una CDN o un service worker.
Ad esempio, una CDN che consente l’eliminazione della cache tramite un’API o un’operazione di dashboard consentirebbe una strategia di memorizzazione nella cache più aggressiva archiviando la risorsa principale ed eliminando esplicitamente la cache pertinente solo quando si verifica un aggiornamento sul server.
Un service worker potrebbe fare lo stesso se potesse eliminare i contenuti nella Cache API quando si verifica un aggiornamento sul server.