Comprendere l'intestazione Vary
Pubblicato: 2022-03-10L'intestazione HTTP Vary viene inviata in miliardi di risposte HTTP ogni giorno. Ma il suo utilizzo non ha mai soddisfatto la sua visione originale e molti sviluppatori fraintendono ciò che fa o non si rendono nemmeno conto che il loro server web lo sta inviando. Con l'arrivo dei suggerimenti per i clienti, delle varianti e delle specifiche chiave, le varie risposte stanno iniziando da capo.
Cosa cambia?
La storia di Vary inizia con una bella idea di come dovrebbe funzionare il web. In linea di principio, un URL non rappresenta una pagina web, ma una risorsa concettuale, come il tuo estratto conto. Immagina di voler vedere il tuo estratto conto: vai su bank.com
e invii una richiesta GET
per /statement
. Fin qui tutto bene, ma non hai detto in quale formato vuoi che la dichiarazione sia. Questo è il motivo per cui il tuo browser includerà anche qualcosa come Accept: text/html
nella tua richiesta. In teoria, almeno, questo significa che potresti invece dire Accept: text/csv
e ottenere la stessa risorsa in un formato diverso.
Poiché lo stesso URL ora produce risposte diverse in base al valore dell'intestazione Accept
, qualsiasi cache che archivia questa risposta deve sapere che tale intestazione è importante. Il server ci dice che l'intestazione Accept
è importante in questo modo:
Vary: Accept
Potresti leggere questo come "Questa risposta varia in base al valore dell'intestazione Accept
della tua richiesta".
Questo fondamentalmente non funziona sul web di oggi. La cosiddetta "negoziazione dei contenuti" è stata un'ottima idea, ma ha fallito. Questo non significa che Vary
sia inutile, però. Una buona parte delle pagine che visiti sul Web contiene un'intestazione Vary
nella risposta: forse anche i tuoi siti Web li hanno e tu non lo sai. Quindi, se l'intestazione non funziona per la negoziazione dei contenuti, perché è ancora così popolare e come se la cavano i browser? Diamo un'occhiata.
In precedenza ho scritto di Vary in relazione alle reti di distribuzione dei contenuti (CDN), quelle cache intermedie (come Fastly, CloudFront e Akamai) che puoi mettere tra i tuoi server e l'utente. I browser devono anche comprendere e rispondere alle regole di Vary e il modo in cui lo fanno è diverso dal modo in cui Vary viene trattato dalle CDN. In questo post, esplorerò il mondo oscuro della variazione della cache nel browser.
Casi d'uso di oggi per variare nel browser
Come abbiamo visto in precedenza, l'uso tradizionale di Vary consiste nell'eseguire la negoziazione del contenuto utilizzando le intestazioni Accept
, Accept-Language
e Accept-Encoding
e, storicamente, le prime due di queste hanno fallito miseramente. Variare su Accept-Encoding
per fornire risposte compresse Gzip o Brotli, dove supportato, funziona per lo più ragionevolmente bene, ma tutti i browser supportano Gzip in questi giorni, quindi non è molto eccitante.
Che ne dici di alcuni di questi scenari?
- Vogliamo offrire immagini che abbiano la larghezza esatta dello schermo dell'utente. Se l'utente ridimensiona il proprio browser, scaricheremmo nuove immagini (che variano in base ai suggerimenti del cliente).
- Se l'utente si disconnette, vogliamo evitare di utilizzare le pagine memorizzate nella cache durante l'accesso (utilizzando un cookie come
Key
). - Gli utenti di browser che supportano il formato immagine WebP dovrebbero ottenere immagini WebP; in caso contrario, dovrebbero ottenere JPEG.
- Quando si utilizza un browser su uno schermo ad alta densità, l'utente dovrebbe ottenere immagini 2x. Se spostano la finestra del browser su uno schermo a densità standard e si aggiornano, dovrebbero ottenere immagini 1x.
Cache fino in fondo
A differenza delle edge cache, che agiscono come una gigantesca cache condivisa da tutti gli utenti, il browser è solo per un utente, ma ha molte cache diverse per usi distinti e specifici:
Alcuni di questi sono piuttosto nuovi e capire esattamente da quale cache viene caricato il contenuto è un calcolo complesso che non è ben supportato dagli strumenti di sviluppo. Ecco cosa fanno queste cache:
- cache di immagini
Questa è una cache con ambito pagina che memorizza i dati di immagine decodificati, in modo che, ad esempio, se includi la stessa immagine in una pagina più volte, il browser deve scaricarla e decodificarla solo una volta. - precaricare la cache
Questo è anche nell'ambito della pagina e memorizza tutto ciò che è stato precaricato in un'intestazioneLink
o in un<link rel="preload">
, anche se la risorsa è normalmente non memorizzabile nella cache. Come la cache delle immagini, la cache di precaricamento viene distrutta quando l'utente si allontana dalla pagina. - API della cache del lavoratore di servizio
Ciò fornisce un back-end della cache con un'interfaccia programmabile; quindi, nulla viene archiviato qui a meno che non lo si inserisca specificamente tramite codice JavaScript in un lavoratore del servizio. Verrà inoltre verificato solo se lo fai esplicitamente in un gestore difetch
del lavoratore del servizio. La cache di lavoro del servizio è nell'ambito dell'origine e, sebbene non sia garantita per essere persistente, è più persistente della cache HTTP del browser. - Cache HTTP
Questa è la cache principale con cui le persone hanno più familiarità. È l'unica cache che presta attenzione alle intestazioni della cache a livello HTTP comeCache-Control
e le combina con le regole euristiche del browser per determinare se memorizzare nella cache qualcosa e per quanto tempo. Ha la portata più ampia, essendo condivisa da tutti i siti web; quindi, se due siti Web non correlati caricano la stessa risorsa (ad esempio, Google Analytics), potrebbero condividere lo stesso hit della cache. - Cache push HTTP/2 (o "cache push H2")
Questo si trova con la connessione e memorizza gli oggetti che sono stati inviati dal server ma non sono stati ancora richiesti da nessuna pagina che sta utilizzando la connessione. Viene applicato l'ambito alle pagine che utilizzano una connessione particolare, che è essenzialmente lo stesso dell'ambito a un'origine singola, ma viene anche distrutto quando la connessione si chiude.
Di questi, la cache HTTP e la cache di lavoro del servizio sono meglio definite. Per quanto riguarda le cache di immagine e di preload, alcuni browser potrebbero implementarle come un'unica "cache di memoria" legata al rendering di una particolare navigazione, ma il modello mentale che sto descrivendo qui è ancora il modo giusto di pensare al processo. Se sei interessato, consulta la nota sulle specifiche sul preload
. Nel caso del push del server H2, la discussione sul destino di questa cache rimane attiva.
L'ordine in cui una richiesta controlla queste cache prima di avventurarsi nella rete è importante, perché la richiesta di qualcosa potrebbe estrarla da uno strato esterno di memorizzazione nella cache in uno interno. Ad esempio, se il tuo server HTTP/2 invia un foglio di stile insieme a una pagina che ne ha bisogno, e quella pagina precarica anche il foglio di stile con un <link rel="preload">
, il foglio di stile finirà per toccare tre cache nel browser. Innanzitutto, si troverà nella cache push H2, in attesa di essere richiesto. Quando il browser esegue il rendering della pagina e arriva al tag di preload
, estrarrà il foglio di stile dalla cache push, tramite la cache HTTP (che potrebbe memorizzarlo, a seconda dell'intestazione Cache-Control
del foglio di stile) e salverà nella cache di precaricamento.
Presentazione di Vary come validatore
OK, quindi cosa succede quando prendiamo questa situazione e aggiungiamo Vary al mix?
A differenza delle cache intermedie (come le CDN), i browser in genere non implementano la capacità di memorizzare più varianti per URL . La logica di ciò è che le cose per cui usiamo tipicamente Vary
(principalmente Accept-Encoding
e Accept-Language
) non cambiano frequentemente nel contesto di un singolo utente. Accept-Encoding
potrebbe (ma probabilmente non lo fa) cambiare con un aggiornamento del browser e Accept-Language
molto probabilmente cambierà solo se modifichi le impostazioni locali della lingua del tuo sistema operativo. Capita anche che sia molto più facile implementare Vary in questo modo, anche se alcuni autori di specifiche ritengono che questo sia stato un errore.
La maggior parte delle volte non è una grande perdita per un browser memorizzare solo una variazione, ma è importante non utilizzare accidentalmente una variazione che non è più valida se i dati "variati su" cambiano.
Il compromesso è trattare Vary
come un validatore, non una chiave. I browser calcolano le chiavi della cache nel modo normale (essenzialmente, utilizzando l'URL) e quindi, se ottengono un risultato, verificano che la richiesta soddisfi le regole Vary integrate nella risposta memorizzata nella cache. In caso contrario, il browser considera la richiesta come mancata nella cache e passa al livello successivo della cache o alla rete. Quando viene ricevuta una nuova risposta, sovrascriverà la versione memorizzata nella cache, anche se tecnicamente è una variazione diversa.
Dimostrazione del comportamento variabile
Per dimostrare il modo in cui viene gestito Vary
, ho creato una piccola suite di test. Il test carica un intervallo di URL diversi, che variano in base a intestazioni diverse, e rileva se la richiesta ha raggiunto la cache o meno. Inizialmente stavo usando ResourceTiming per questo, ma per una maggiore compatibilità, ho finito per passare semplicemente a misurare il tempo necessario per completare la richiesta (e ho aggiunto intenzionalmente un ritardo di 1 secondo alle risposte lato server per rendere la differenza davvero chiara).
Diamo un'occhiata a ciascuno dei tipi di cache e a come dovrebbe funzionare Vary
e se funziona effettivamente in questo modo. Per ogni test, mostro qui se dovremmo aspettarci di vedere un risultato dalla cache ("HIT" contro "MISS") e cosa è effettivamente successo.
Precarico
Il precaricamento è attualmente supportato solo in Chrome, dove le risposte precaricate vengono archiviate in una cache di memoria finché non sono necessarie alla pagina. Le risposte popolano anche la cache HTTP nel loro percorso verso la cache di precaricamento, se sono memorizzabili nella cache HTTP. Poiché è impossibile specificare le intestazioni di richiesta con un precaricamento e la cache di precaricamento dura solo quanto la pagina, testarlo è difficile, ma possiamo almeno vedere che gli oggetti con un'intestazione Vary
vengono precaricati correttamente:
API della cache dell'operatore di servizio
Chrome e Firefox supportano i lavoratori dei servizi e, nello sviluppo delle specifiche dei lavoratori dei servizi, gli autori hanno voluto correggere quelle che vedevano come implementazioni non funzionanti nei browser, per fare in modo che Vary
nel browser funzionasse più come le CDN. Ciò significa che mentre il browser dovrebbe memorizzare solo una variazione nella cache HTTP, dovrebbe mantenere più variazioni nell'API Cache. Firefox (54) esegue questa operazione correttamente, mentre Chrome utilizza la stessa logica di variabilità come validatore che utilizza per la cache HTTP (il bug viene monitorato).
Cache HTTP
La cache HTTP principale dovrebbe osservare Vary
e lo fa in modo coerente (come validatore) in tutti i browser. Per molto, molto di più su questo, vedere il post di Mark Nottingham "State of Browser Caching, Revisited".
Cache push HTTP/2
Dovrebbe essere osservato Vary
, ma in pratica nessun browser lo rispetta effettivamente e i browser abbineranno felicemente e consumeranno le risposte push con richieste che contengono valori casuali nelle intestazioni su cui le risposte variano.
La ruga "304 (non modificata)".
Lo stato della risposta HTTP "304 (non modificato)" è affascinante. Il nostro "caro leader", Artur Bergman, mi ha fatto notare questa gemma nella specifica di memorizzazione nella cache HTTP (enfasi mia):
Il server che genera una risposta 304 deve generare uno dei seguenti campi di intestazione che sarebbero stati inviati in una risposta 200 (OK) alla stessa richiesta:
Cache-Control
,Content-Location
,Date
,ETag
,Expires
eVary
.
Perché una risposta 304
dovrebbe restituire un'intestazione Vary
? La trama si infittisce quando leggi cosa dovresti fare dopo aver ricevuto una risposta 304
che contiene quelle intestazioni:
Se una risposta archiviata viene selezionata per l'aggiornamento, la cache deve \[…] utilizzare altri campi di intestazione forniti nella risposta 304 (non modificata) per sostituire tutte le istanze dei campi di intestazione corrispondenti nella risposta archiviata.
Aspetta cosa? Quindi, se l'intestazione Vary
di 304
è diversa da quella nell'oggetto memorizzato nella cache esistente, dovremmo aggiornare l'oggetto memorizzato nella cache? Ma ciò potrebbe significare che non corrisponde più alla richiesta che abbiamo fatto!
In quello scenario, a prima vista, il 304
sembra dirti contemporaneamente che puoi e non puoi utilizzare la versione memorizzata nella cache. Ovviamente, se il server davvero non voleva che tu utilizzassi la versione memorizzata nella cache, avrebbe inviato un 200
, non un 304
; quindi, la versione memorizzata nella cache dovrebbe essere sicuramente utilizzata, ma dopo aver applicato gli aggiornamenti ad essa, potrebbe non essere utilizzata di nuovo per una richiesta futura identica a quella che ha effettivamente popolato la cache in primo luogo.
(Nota a margine: in Fastly, non rispettiamo questa stranezza delle specifiche. Quindi, se riceviamo un 304
dal tuo server di origine, continueremo a utilizzare l'oggetto memorizzato nella cache non modificato, oltre a reimpostare il TTL.)
I browser sembrano rispettarlo, ma con una stranezza. Aggiornano non solo le intestazioni della risposta, ma anche le intestazioni della richiesta che si accoppiano con esse, al fine di garantire che, dopo l'aggiornamento, la risposta memorizzata nella cache corrisponda alla richiesta corrente. Questo sembra avere senso. Le specifiche non lo menzionano, quindi i fornitori di browser sono liberi di fare ciò che vogliono; fortunatamente, tutti i browser mostrano questo stesso comportamento.
Suggerimenti per i clienti
La funzione Suggerimenti client di Google è una delle novità più significative accadute a Vary nel browser da molto tempo. A differenza Accept-Encoding
e Accept-Language
, i suggerimenti per i clienti descrivono valori che potrebbero cambiare regolarmente quando un utente si sposta nel tuo sito Web, in particolare i seguenti:
-
DPR
Rapporto pixel del dispositivo, la densità dei pixel dello schermo (potrebbe variare se l'utente ha più schermi) -
Save-Data
Se l'utente ha abilitato la modalità di salvataggio dei dati -
Viewport-Width
Larghezza in pixel della finestra corrente -
Width
Larghezza della risorsa desiderata in pixel fisici
Non solo questi valori potrebbero cambiare per un singolo utente, ma l'intervallo di valori per quelli relativi alla larghezza è ampio. Quindi, possiamo utilizzare totalmente Vary
con queste intestazioni, ma rischiamo di ridurre l'efficienza della nostra cache o addirittura di rendere inefficace la memorizzazione nella cache.
La proposta di intestazione chiave
Suggerimenti per i clienti e altre intestazioni altamente granulari si prestano a una proposta su cui Mark ha lavorato, denominata Key. Diamo un'occhiata a un paio di esempi:
Key: Viewport-Width;div=50
Questo dice che la risposta varia in base al valore dell'intestazione della richiesta Viewport-Width
, ma arrotondata per difetto al multiplo di 50 pixel più vicino!
Key: cookie;param=sessionAuth;param=flags
L'aggiunta di questa intestazione a una risposta significa che stiamo variando su due cookie specifici: sessionAuth
e flags
. Se non sono cambiati, possiamo riutilizzare questa risposta per una richiesta futura.
Quindi, le principali differenze tra Key
e Vary
sono:
-
Key
consente di variare i sottocampi all'interno delle intestazioni, il che rende improvvisamente possibile variare i cookie, perché puoi variare su un solo cookie: sarebbe enorme; - i singoli valori possono essere raggruppati in intervalli , per aumentare la possibilità di un hit nella cache, particolarmente utile per variare cose come la larghezza del viewport.
- tutte le varianti con lo stesso URL devono avere la stessa chiave. Pertanto, se una cache riceve una nuova risposta per un URL per il quale ha già alcune varianti esistenti e il valore dell'intestazione della
Key
della nuova risposta non corrisponde ai valori di tali varianti esistenti, tutte le varianti devono essere eliminate dalla cache.
Al momento della scrittura, nessun browser o CDN supporta Key
, anche se in alcuni CDN potresti essere in grado di ottenere lo stesso effetto suddividendo le intestazioni in entrata in più intestazioni private e variando su quelle (vedi il nostro post, "Ottenere il massimo da variare con Fastly"), quindi i browser sono l'area principale in cui Key
può avere un impatto.
Il requisito per tutte le varianti di avere la stessa ricetta chiave è alquanto limitante e mi piacerebbe vedere una sorta di opzione di "uscita anticipata" nelle specifiche. Ciò ti consentirebbe di eseguire operazioni come "Varia in base allo stato di autenticazione e, se effettuato l'accesso, varia anche in base alle preferenze".
La proposta di varianti
La Key
è un bel meccanismo generico, ma alcune intestazioni hanno regole più complesse per i loro valori e la comprensione della semantica di tali valori può aiutarci a trovare modi automatizzati per ridurre la variazione della cache. Ad esempio, immagina che due richieste arrivino con diversi valori Accept-Language
, en-gb
e en-us
, ma sebbene il tuo sito Web supporti la variazione della lingua, hai solo un "inglese". Se rispondiamo alla richiesta per l'inglese americano e quella risposta viene memorizzata nella cache su un CDN, non può essere riutilizzata per la richiesta in inglese britannico, perché il valore Accept-Language
sarebbe diverso e la cache non è abbastanza intelligente per sapere meglio .
Entra, con notevole clamore, la proposta Varianti. Ciò consentirebbe ai server di descrivere quali varianti supportano, consentendo alle cache di prendere decisioni più intelligenti su quali variazioni sono effettivamente distinte e quali sono effettivamente le stesse.
In questo momento, Variants è una bozza molto precoce e, poiché è progettato per aiutare con Accept-Encoding
e Accept-Language
, la sua utilità è piuttosto limitata alle cache condivise, come le CDN, piuttosto che alle cache del browser. Ma si accoppia bene con Key
e completa il quadro per un migliore controllo della variazione della cache.
Conclusione
C'è molto da vedere qui, e mentre può essere interessante capire come funziona il browser sotto il cofano, ci sono anche alcune cose semplici che puoi distillare da esso:
- La maggior parte dei browser tratta
Vary
come un validatore. Se desideri memorizzare nella cache più varianti separate, trova invece un modo per utilizzare URL diversi. - I browser ignorano
Vary
per le risorse inviate utilizzando il push del server HTTP/2, quindi non variare su qualsiasi cosa tu spinga. - I browser hanno un sacco di cache e funzionano in modi diversi. Vale la pena provare a capire in che modo le tue decisioni di memorizzazione nella cache influiscono sulle prestazioni di ciascuna di esse, specialmente nel contesto di
Vary
. -
Vary
non è così utile come potrebbe essere e laKey
abbinata a Client Hints sta iniziando a cambiarlo. Segui il supporto del browser per scoprire quando puoi iniziare a usarli.
Vai avanti e sii variabile.