Înțelegerea antetului Variabil

Publicat: 2022-03-10
Rezumat rapid ↬ Antetul Vary HTTP este trimis în miliarde de răspunsuri HTTP în fiecare zi. Dar utilizarea sa nu și-a îndeplinit niciodată viziunea inițială, iar mulți dezvoltatori înțeleg greșit ce face sau nici măcar nu realizează că serverul lor web îl trimite. Odată cu venirea Sugestiilor pentru clienți, Variantelor și specificațiilor cheie, răspunsurile variate primesc un nou început.

Antetul Vary HTTP este trimis în miliarde de răspunsuri HTTP în fiecare zi. Dar utilizarea sa nu și-a îndeplinit niciodată viziunea inițială, iar mulți dezvoltatori înțeleg greșit ce face sau nici măcar nu realizează că serverul lor web îl trimite. Odată cu venirea Sugestiilor pentru clienți, Variantelor și specificațiilor cheie, răspunsurile variate primesc un nou început.

Ce este Vary?

Povestea lui Vary începe cu o idee frumoasă despre cum ar trebui să funcționeze web-ul. În principiu, o adresă URL nu reprezintă o pagină web, ci o resursă conceptuală, cum ar fi extrasul dvs. bancar. Imaginați-vă că doriți să vedeți extrasul dvs. bancar: accesați bank.com și trimiteți o solicitare GET pentru /statement . Până acum, bine, dar nu ați spus în ce format doriți declarația. Acesta este motivul pentru care browserul dvs. va include și ceva de genul Accept: text/html în cererea dvs. În teorie, cel puțin, asta înseamnă că ai putea spune Accept: text/csv în schimb și poți obține aceeași resursă într-un format diferit.

Ilustrație a conversației negociate pe conținut între utilizator și bancă
(Vezi versiunea mare)

Deoarece aceeași adresă URL produce acum răspunsuri diferite pe baza valorii antetului Accept , orice cache care stochează acest răspuns trebuie să știe că acel antet este important. Serverul ne spune că antetul Accept este important astfel:

 Vary: Accept

Puteți citi acest lucru ca „Acest răspuns variază în funcție de valoarea antetului Accept al solicitării dvs.”.

Acest lucru practic nu funcționează pe web de astăzi. Așa-numita „negociere de conținut” a fost o idee grozavă, dar a eșuat. Totuși, asta nu înseamnă că Vary este inutil. O parte decentă din paginile pe care le vizitați pe web poartă un antet Vary în răspuns - poate site-urile dvs. le au și ele, iar dvs. nu știți. Deci, dacă antetul nu funcționează pentru negocierea conținutului, de ce este încă atât de popular și cum se ocupă browserele cu el? Hai să aruncăm o privire.

Am scris anterior despre Vary în legătură cu rețelele de livrare de conținut (CDN), acele cache intermediare (cum ar fi Fastly, CloudFront și Akamai) pe care le poți pune între serverele tale și utilizator. De asemenea, browserele trebuie să înțeleagă și să răspundă la regulile Vary, iar modul în care fac acest lucru este diferit de modul în care este tratat Vary de către CDN-uri. În această postare, voi explora lumea tulbure a variației cache-ului din browser.

Mai multe după săritură! Continuați să citiți mai jos ↓

Cazurile de utilizare de astăzi pentru variarea în browser

După cum am văzut mai devreme, utilizarea tradițională a lui Vary este de a efectua negocierea conținutului folosind antetele Accept , Accept-Language și Accept-Encoding și, din punct de vedere istoric, primele două dintre acestea au eșuat lamentabil. Variarea în Accept-Encoding pentru a oferi răspunsuri comprimate Gzip sau Brotli, acolo unde este acceptată, funcționează în mare parte destul de bine, dar toate browserele acceptă Gzip în zilele noastre, așa că nu este foarte interesant.

Ce zici de unele dintre aceste scenarii?

  • Dorim să oferim imagini care au lățimea exactă a ecranului utilizatorului. Dacă utilizatorul își redimensionează browserul, vom descărca imagini noi (variand în funcție de Sugestii pentru clienți).
  • Dacă utilizatorul se deconectează, dorim să evităm utilizarea oricăror pagini care au fost stocate în cache în timp ce au fost conectate (folosind un cookie ca Key ).
  • Utilizatorii browserelor care acceptă formatul de imagine WebP ar trebui să obțină imagini WebP; în caz contrar, ar trebui să primească JPEG.
  • Când folosește un browser pe un ecran de înaltă densitate, utilizatorul ar trebui să obțină imagini de două ori. Dacă mută fereastra browserului pe un ecran cu densitate standard și se reîmprospătează, ar trebui să obțină imagini 1x.

Cache până în jos

Spre deosebire de edge cache-urile, care acționează ca un cache gigantic partajat de toți utilizatorii, browserul este doar pentru un singur utilizator, dar are o mulțime de cache-uri diferite pentru utilizări distincte și specifice:

Ilustrație a cache-urilor în browser
(Vezi versiunea mare)

Unele dintre acestea sunt destul de noi, iar înțelegerea exactă din cache-ul din care este încărcat conținutul este un calcul complex care nu este bine susținut de instrumentele pentru dezvoltatori. Iată ce fac aceste cache-uri:

  • cache de imagini
    Aceasta este o memorie cache aferentă paginii care stochează date de imagine decodificate, astfel încât, de exemplu, dacă includeți aceeași imagine pe o pagină de mai multe ori, browserul trebuie să o descarce și să o decodeze o singură dată.
  • preîncărcare cache
    Acesta este, de asemenea, în funcție de pagină și stochează orice a fost preîncărcat într-un antet Link sau într-o etichetă <link rel="preload"> , chiar dacă resursa nu poate fi stocată în cache. La fel ca cache-ul de imagini, cache-ul de preîncărcare este distrus atunci când utilizatorul navighează departe de pagină.
  • API-ul cache al lucrătorului de servicii
    Aceasta oferă un back-end cache cu o interfață programabilă; deci, nimic nu este stocat aici decât dacă îl puneți în mod specific acolo prin codul JavaScript într-un lucrător de service. De asemenea, va fi verificat doar dacă faceți acest lucru în mod explicit într-un handler de fetch a lucrătorului de servicii. Cache-ul lucrătorului de servicii este în funcție de origine și, deși nu este garantat a fi persistent, este mai persistent decât cache-ul HTTP al browserului.
  • Cache HTTP
    Acesta este principalul cache cu care oamenii sunt cel mai familiarizați. Este singurul cache care acordă atenție antetelor cache la nivel HTTP, cum ar fi Cache-Control și le combină cu regulile euristice proprii ale browserului pentru a determina dacă să memoreze ceva în cache și pentru cât timp. Are cel mai larg domeniu de aplicare, fiind partajat de toate site-urile web; deci, dacă două site-uri web fără legătură încarcă același material (de exemplu, Google Analytics), ele ar putea avea aceeași accesare în cache.
  • HTTP/2 push cache (sau „H2 push cache”)
    Aceasta se află cu conexiunea și stochează obiecte care au fost împinse de pe server, dar nu au fost încă solicitate de nicio pagină care utilizează conexiunea. Este încadrat în paginile care utilizează o anumită conexiune, care este în esență la fel ca și în domeniul unei singure origini, dar este și distrus atunci când conexiunea se închide.

Dintre acestea, cache-ul HTTP și cache-ul lucrătorului de servicii sunt cel mai bine definite. În ceea ce privește imaginile și cache-urile de preîncărcare, unele browsere le-ar putea implementa ca un singur „cache de memorie” legat de randarea unei anumite navigații, dar modelul mental pe care îl descriu aici este încă modul corect de a gândi proces. Consultați nota de specificații despre preload dacă sunteți interesat. În cazul push-ului serverului H2, discuția despre soarta acestui cache rămâne activă.

Ordinea în care o solicitare verifică aceste cache înainte de a se aventura în rețea este importantă, deoarece solicitarea a ceva ar putea să-l tragă dintr-un strat exterior de cache într-unul interior. De exemplu, dacă serverul dvs. HTTP/2 împinge o foaie de stil împreună cu o pagină care are nevoie de ea și acea pagină preîncarcă, de asemenea, foaia de stil cu o etichetă <link rel="preload"> , atunci foaia de stil va ajunge să atingă trei cache-urile din browser. Mai întâi, va sta în memoria cache push H2, așteptând să fie solicitat. Când browserul redă pagina și ajunge la eticheta de preload , va scoate foaia de stil din memoria cache push, prin cache-ul HTTP (care o poate stoca, în funcție de antetul Cache-Control al foii de stil) și va salva acesta în memoria cache de preîncărcare.

Flux HTTP/2 PUSH prin cache-urile browserului
(Vezi versiunea mare)

Vă prezentăm pe Vary ca validator

OK, deci ce se întâmplă când luăm această situație și adăugăm Vary la amestec?

Spre deosebire de cache-urile intermediare (cum ar fi CDN-urile), browserele nu implementează de obicei capacitatea de a stoca mai multe variații pentru fiecare adresă URL . Motivul pentru aceasta este că lucrurile pentru care folosim de obicei Vary (în principal Accept-Encoding și Accept-Language ) nu se schimbă frecvent în contextul unui singur utilizator. Accept-Encoding se poate modifica (dar probabil nu) la o actualizare a browserului, iar Accept-Language s-ar schimba, cel mai probabil, numai dacă editați setările de limbaj ale sistemului de operare. De asemenea, se întâmplă să fie mult mai ușor de implementat Vary în acest fel, deși unii autori de specificații cred că aceasta a fost o greșeală.

Nu este o mare pierdere de cele mai multe ori pentru un browser să stocheze o singură variantă, dar este important să nu folosim accidental o variație care nu mai este valabilă dacă datele „variate” se întâmplă să se schimbe.

Compromisul este de a trata Vary ca un validator, nu o cheie. Browserele calculează cheile de cache în mod obișnuit (în esență, folosind adresa URL), apoi, dacă obțin o atingere, verifică dacă cererea îndeplinește orice reguli Vary care sunt incluse în răspunsul din cache. Dacă nu, atunci browserul tratează cererea ca o pierdere a memoriei cache și trece la următorul strat de cache sau în rețea. Când se primește un răspuns nou, acesta va suprascrie versiunea stocată în cache, chiar dacă din punct de vedere tehnic este o variantă diferită.

Demonstrarea unui comportament variabil

Pentru a demonstra modul în care este gestionat Vary , am creat o mică suită de teste. Testul încarcă o serie de adrese URL diferite, care variază în funcție de antete și detectează dacă solicitarea a ajuns în memoria cache sau nu. Inițial foloseam ResourceTiming pentru asta, dar pentru o mai mare compatibilitate, am ajuns să măsoare doar cât de mult durează cererea pentru a se finaliza (și am adăugat în mod intenționat o întârziere de 1 secundă răspunsurilor de pe server pentru a face diferența cu adevărat clară).

Să ne uităm la fiecare dintre tipurile de cache și cum ar trebui să funcționeze Vary și dacă funcționează de fapt așa. Pentru fiecare test, arăt aici dacă ar trebui să ne așteptăm să vedem un rezultat din cache („HIT” versus „MISS”) și ce sa întâmplat de fapt.

Preîncărcare

În prezent, preîncărcarea este acceptată numai în Chrome, unde răspunsurile preîncărcate sunt stocate într-o memorie cache până când sunt necesare paginii. Răspunsurile populează, de asemenea, cache-ul HTTP în drum spre memoria cache de preîncărcare, dacă acestea pot fi stocate în cache HTTP. Deoarece specificarea anteturilor de solicitare cu o preîncărcare este imposibilă, iar memoria cache de preîncărcare durează atâta timp cât pagina, testarea acesteia este dificilă, dar putem vedea cel puțin că obiectele cu antet Vary sunt preîncărcate cu succes:

Rezultatele testului pentru link rel=preload în Google Chrome
(Vezi versiunea mare)

API-ul Service Worker Cache

Chrome și Firefox sprijină lucrătorii de servicii, iar în dezvoltarea specificației pentru lucrătorii de servicii, autorii au vrut să repare ceea ce ei considerau ca implementări deteriorate în browsere, pentru a face ca Vary în browser să funcționeze mai mult ca CDN-urile. Aceasta înseamnă că, în timp ce browserul ar trebui să stocheze o singură variantă în memoria cache HTTP, se presupune că trebuie să păstreze mai multe variații în API-ul Cache. Firefox (54) face acest lucru corect, în timp ce Chrome folosește aceeași logică de variație ca validator pe care o folosește pentru cache-ul HTTP (bunul este urmărit).

Rezultatele testului pentru memoria cache a lucrătorilor de service în Google Chrome
(Vezi versiunea mare)

Cache HTTP

Cache-ul principal HTTP ar trebui să observe Vary și o face în mod consecvent (ca validator) în toate browserele. Pentru multe, multe mai multe despre aceasta, consultați postarea lui Mark Nottingham „Starea caching-ului browserului, revizuită”.

HTTP/2 Push Cache

Vary ar trebui să fie observată, dar, în practică, niciun browser nu o respectă de fapt, iar browserele vor potrivi și vor consuma cu plăcere răspunsurile push cu cereri care poartă valori aleatorii în anteturi pe care răspunsurile variază.

Rezultatele testului pentru cache push H2 în Google Chrome
(Vezi versiunea mare)

Rida „304 (NeModificat)”.

Starea de răspuns HTTP „304 (Nemodificat)” este fascinantă. „Dragul nostru lider”, Artur Bergman, mi-a subliniat această bijuterie în specificația de cache HTTP (sublinierea mea):

Serverul care generează un răspuns 304 trebuie să genereze oricare dintre următoarele câmpuri de antet care ar fi fost trimise într-un răspuns 200 (OK) la aceeași solicitare: Cache-Control , Content-Location , Date , ETag , Expires și Vary .

De ce un răspuns 304 ar returna un antet Vary ? Intriga se îngroașă atunci când citiți despre ce ar trebui să faceți la primirea unui răspuns 304 care conține acele anteturi:

Dacă un răspuns stocat este selectat pentru actualizare, memoria cache trebuie să \[…] să folosească alte câmpuri de antet furnizate în răspunsul 304 (Nemodificat) pentru a înlocui toate instanțele câmpurilor de antet corespunzătoare din răspunsul stocat.

Stai ce? Deci, dacă antetul Vary al lui 304 este diferit de cel din obiectul din cache existent, ar trebui să actualizăm obiectul din cache? Dar asta ar putea însemna că nu se mai potrivește cu solicitarea pe care am făcut-o!

În acest scenariu, la prima vedere, 304 pare să vă spună simultan că puteți și nu puteți utiliza versiunea în cache. Desigur, dacă serverul chiar nu ar fi vrut să utilizați versiunea în cache, ar fi trimis un 200 , nu un 304 ; Deci, versiunea stocată în cache ar trebui să fie utilizată cu siguranță - dar după aplicarea actualizărilor acesteia, este posibil să nu fie folosită din nou pentru o cerere viitoare identică cu cea care a populat de fapt memoria cache în primul rând.

(Notă secundară: la Fastly, nu respectăm această particularitate a specificației. Deci, dacă primim un 304 de la serverul dvs. de origine, vom continua să folosim obiectul din cache nemodificat, în afară de resetarea TTL.)

Browserele par să respecte acest lucru, dar cu o ciudatenie. Ei actualizează nu doar anteturile de răspuns, ci și anteturile cererii care se împerechează cu acestea, pentru a garanta că, după actualizare, răspunsul din cache se potrivește cu cererea curentă. Acest lucru pare să aibă sens. Specificația nu menționează acest lucru, așa că furnizorii de browsere sunt liberi să facă ceea ce le place; din fericire, toate browserele prezintă același comportament.

Sfaturi pentru clienți

Funcția Google Client Hints este unul dintre cele mai semnificative lucruri noi care i s-au întâmplat lui Vary în browser de mult timp. Spre deosebire Accept-Encoding și Accept-Language , Sugestiile pentru clienți descriu valori care s-ar putea schimba în mod regulat pe măsură ce un utilizator se deplasează pe site-ul dvs. web, în ​​special următoarele:

  • DPR
    Raportul pixelilor dispozitivului, densitatea pixelilor ecranului (poate varia dacă utilizatorul are mai multe ecrane)
  • Save-Data
    Dacă utilizatorul a activat modul de salvare a datelor
  • Viewport-Width
    Lățimea pixelilor ferestrei de vizualizare curente
  • Width
    Lățimea dorită a resursei în pixeli fizici

Nu numai că aceste valori se pot schimba pentru un singur utilizator, dar intervalul de valori pentru cele legate de lățime este mare. Deci, putem folosi pe deplin Vary cu aceste anteturi, dar riscăm să ne reducem eficiența cache-ului sau chiar să facem ca cache să fie ineficientă.

Propunerea de antet cheie

Sugestii pentru clienți și alte anteturi foarte granulare se pretează la o propunere la care Mark a lucrat, numită Key. Să ne uităm la câteva exemple:

 Key: Viewport-Width;div=50

Aceasta spune că răspunsul variază în funcție de valoarea antetului de solicitare Viewport-Width , dar rotunjit în jos la cel mai apropiat multiplu de 50 de pixeli!

 Key: cookie;param=sessionAuth;param=flags

Adăugarea acestui antet într-un răspuns înseamnă că variam în funcție de două module cookie specifice: sessionAuth și flags . Dacă nu s-au schimbat, putem reutiliza acest răspuns pentru o solicitare viitoare.

Deci, principalele diferențe dintre Key și Vary sunt:

  • Key permite variarea subcâmpurilor din anteturi, ceea ce face dintr-o dată posibilă variarea modulelor cookie, deoarece puteți varia doar pe un singur cookie - ar fi uriaș;
  • valorile individuale pot fi grupate în intervale , pentru a crește șansa unei lovituri în cache, util în special pentru a varia lucruri precum lățimea ferestrei de vizualizare.
  • toate variantele cu aceeași adresă URL trebuie să aibă aceeași cheie. Deci, dacă un cache primește un răspuns nou pentru o adresă URL pentru care are deja unele variante existente, iar valoarea antetului Key a noului răspuns nu se potrivește cu valorile acelor variante existente, atunci toate variantele trebuie eliminate din cache.

La momentul scrierii, niciun browser sau CDN nu acceptă Key , deși în unele CDN-uri este posibil să obțineți același efect prin împărțirea antetelor primite în mai multe antete private și variarea acestora (consultați postarea noastră, „Obținerea maximă a variației cu Rapid”), astfel încât browserele sunt zona principală în care Key poate avea un impact.

Cerința ca toate variantele să aibă aceeași rețetă cheie este oarecum limitativă și aș dori să văd un fel de opțiune de „ieșire timpurie” în specificație. Acest lucru vă va permite să faceți lucruri precum „Variați starea de autentificare și, dacă sunteți autentificat, variați și preferințe”.

Propunerea de variante

Key este un mecanism generic frumos, dar unele anteturi au reguli mai complexe pentru valorile lor, iar înțelegerea semanticii acestor valori ne poate ajuta să găsim modalități automate de reducere a variației cache-ului. De exemplu, imaginați-vă că două solicitări vin cu valori diferite Accept-Language , en-gb și en-us , dar, deși site-ul dvs. web acceptă variația limbii, aveți doar o singură „engleză”. Dacă răspundem la cererea pentru limba engleză din SUA și acel răspuns este stocat în cache pe un CDN, atunci nu poate fi reutilizat pentru solicitarea în limba engleză din Marea Britanie, deoarece valoarea Accept-Language ar fi diferită și memoria cache nu este suficient de inteligentă pentru a ști mai bine .

Intră, cu mare furie, propunerea Variante. Acest lucru ar permite serverelor să descrie variantele pe care le acceptă, permițând cache-urilor să ia decizii mai inteligente cu privire la care variații sunt de fapt distincte și care sunt efectiv aceleași.

Momentan, Variants este o versiune foarte timpurie și, deoarece este concepută pentru a ajuta cu Accept-Encoding și Accept-Language , utilitatea sa este mai degrabă limitată la cache-urile partajate, cum ar fi CDN-urile, mai degrabă decât cache-urile browserului. Dar se potrivește frumos cu Key și completează imaginea pentru un control mai bun al variației cache-ului.

Concluzie

Sunt multe de luat aici și, deși poate fi interesant să înțelegeți cum funcționează browserul sub capotă, există și câteva lucruri simple pe care le puteți distila din el:

  • Majoritatea browserelor tratează Vary ca un validator. Dacă doriți ca mai multe variante separate să fie stocate în cache, găsiți o modalitate de a utiliza adrese URL diferite.
  • Browserele ignoră Vary pentru resursele transmise folosind HTTP/2 server push, așa că nu variați în ceea ce privește ceea ce faceți push.
  • Browserele au o mulțime de cache și funcționează în moduri diferite. Merită să încercați să înțelegeți modul în care deciziile dvs. de stocare în cache afectează performanța în fiecare dintre ele, mai ales în contextul Vary .
  • Vary nu este atât de util pe cât ar putea fi, iar Key asociată cu Client Hints începe să schimbe asta. Urmăriți asistența pentru browser pentru a afla când puteți începe să le utilizați.

Du-te și fii variabil.