Introduzione a HTTP/2

HTTP/2 renderà le nostre applicazioni più veloci, semplici e affidabili (una combinazione rara) consentendoci di annullare molte delle soluzioni alternative per HTTP/1.1 precedentemente eseguite all'interno delle nostre applicazioni e di risolvere questi problemi all'interno del livello di trasporto stesso. Inoltre, offre una serie di opportunità completamente nuove per ottimizzare le nostre applicazioni e migliorare le prestazioni.

Gli obiettivi principali di HTTP/2 sono ridurre la latenza abilitando il multiplexing completo di richieste e risposte, ridurre al minimo l'overhead del protocollo tramite una compressione efficiente dei campi delle intestazioni HTTP e aggiungere il supporto per l'assegnazione della priorità delle richieste e il push del server. Per implementare questi requisiti, è disponibile una vasta trasmissione di supporto di altri miglioramenti ai protocolli, come i nuovi meccanismi di controllo del flusso, gestione degli errori e di upgrade, ma queste sono le funzionalità più importanti che ogni sviluppatore web deve comprendere e sfruttare nelle proprie applicazioni.

HTTP/2 non modifica in alcun modo la semantica dell'applicazione HTTP. Tutti i concetti fondamentali, come metodi HTTP, codici di stato, URI e campi di intestazione, sono validi. HTTP/2 modifica invece il modo in cui i dati vengono formattati (in frame) e trasportati tra il client e il server, entrambi i quali gestiscono l'intero processo, e nascondono tutta la complessità alle nostre applicazioni all'interno del nuovo livello di frame. Di conseguenza, tutte le applicazioni esistenti possono essere distribuite senza modifiche.

Perché non HTTP/1.2?

Per raggiungere gli obiettivi di prestazioni stabiliti dal gruppo di lavoro HTTP, HTTP/2 introduce un nuovo livello di frame binario non compatibile con le versioni precedenti dei server e dei client HTTP/1.x, pertanto la versione principale del protocollo è aumentata a HTTP/2.

Detto questo, a meno che tu non implementi un server web (o un client personalizzato) utilizzando i socket TCP non elaborati, non vedrai alcuna differenza: tutta la nuova struttura di frame di basso livello viene eseguita dal client e dal server per tuo conto. Le uniche differenze osservabili saranno il miglioramento delle prestazioni e della disponibilità di nuove funzionalità come l'assegnazione della priorità delle richieste, il controllo del flusso e il push del server.

Breve storia di SPDY e HTTP/2

SPDY era un protocollo sperimentale, sviluppato da Google e annunciato a metà del 2009, il cui obiettivo principale era provare a ridurre la latenza di caricamento delle pagine web risolvendo alcuni dei ben noti limiti delle prestazioni di HTTP/1.1. Nello specifico, gli obiettivi del progetto delineati sono stati impostati come segue:

  • Punta a una riduzione del 50% del tempo di caricamento della pagina (PLT).
  • Evita di dover apportare modifiche ai contenuti da parte degli autori dei siti web.
  • Riduci al minimo la complessità di deployment ed evita modifiche all'infrastruttura di rete.
  • Sviluppa questo nuovo protocollo in collaborazione con la community open source.
  • Raccogliere dati sul rendimento reali per (in) convalidare il protocollo sperimentale.

Non molto tempo dopo l'annuncio iniziale, Mike Belshe e Roberto Peon, entrambi ingegneri del software di Google, hanno condiviso i primi risultati, documentazione e codice sorgente per l'implementazione sperimentale del nuovo protocollo SPDY:

Finora abbiamo testato SPDY solo in condizioni di laboratorio. I risultati iniziali sono molto incoraggianti: quando scarichiamo i 25 siti web principali tramite connessioni di rete di casa simulate, notiamo un miglioramento significativo delle prestazioni: le pagine si caricano fino al 55% più velocemente. (Chromium Blog)

Nel 2012, il nuovo protocollo sperimentale è stato supportato in Chrome, Firefox e Opera e un numero in rapida crescita di siti, grandi (ad esempio Google, Twitter, Facebook) e piccoli, stavano implementando SPDY nella propria infrastruttura. Di fatto, SPDY era sulla buona strada per diventare uno standard di fatto grazie alla crescente adozione da parte del settore.

Osservando questa tendenza, HTTP Working Group (HTTP-WG) ha avviato un nuovo sforzo per sfruttare le lezioni apprese da SPDY, crearne e migliorarle e fornire uno standard "HTTP/2" ufficiale. È stata redatta una nuova carta, è stato presentato un invito aperto per le proposte HTTP/2 e, dopo diverse discussioni all'interno del gruppo di lavoro, la specifica SPDY è stata adottata come punto di partenza per il nuovo protocollo HTTP/2.

Negli anni successivi SPDY e HTTP/2 hanno continuato a coevolvere in parallelo, e come ramo sperimentale utilizzato per testare nuove funzionalità e proposte per lo standard HTTP/2. Ciò che sembra corretto sulla carta potrebbe non funzionare nella pratica, e viceversa, e SPDY ha offerto un percorso per testare e valutare ogni proposta prima della sua inclusione nello standard HTTP/2. Alla fine, questo processo ha durato tre anni e ha prodotto più di una dozzina di bozze intermedie:

  • Marzo 2012: invito a presentare proposte per HTTP/2
  • Novembre 2012: prima bozza di HTTP/2 (basata su SPDY)
  • Agosto 2014: pubblicazione della bozza HTTP/2-17 e HPACK bozza-12
  • Agosto 2014: ultima chiamata al gruppo di lavoro per HTTP/2
  • Febbraio 2015: IESG ha approvato le bozze HTTP/2 e HPACK
  • Maggio 2015: pubblicazione di RFC 7540 (HTTP/2) e RFC 7541 (HPACK)

All'inizio del 2015, l'IESG ha esaminato e approvato il nuovo standard HTTP/2 per la pubblicazione. Poco dopo, il team di Google Chrome ha annunciato il ritiro dell'estensione SPDY e NPN per TLS:

Le principali modifiche di HTTP/2 rispetto a HTTP/1.1 si concentrano sul miglioramento delle prestazioni. Alcune funzionalità chiave, come il multiplexing, la compressione delle intestazioni, la prioritizzazione e la negoziazione del protocollo, si sono evolute rispetto al lavoro svolto in un precedente protocollo aperto, ma non standard, denominato SPDY. Chrome supporta SPDY da Chrome 6, ma poiché la maggior parte dei vantaggi è presente in HTTP/2, è il momento di dire addio. Prevediamo di rimuovere il supporto per SPDY all'inizio del 2016 e allo stesso tempo di rimuovere il supporto per l'estensione TLS denominata NPN in favore di ALPN in Chrome. Consigliamo agli sviluppatori di server di passare a HTTP/2 e ALPN.

Siamo felici di aver contribuito al processo di standard aperti che ha portato a HTTP/2 e speriamo di vederne un'ampia adozione dato il grande impegno del settore in termini di standardizzazione e implementazione. (Blog di Chromium)

La coevoluzione di SPDY e HTTP/2 ha consentito a sviluppatori, browser e siti di ottenere un'esperienza reale con il nuovo protocollo man mano che veniva sviluppato. Di conseguenza, lo standard HTTP/2 è uno degli standard migliori e più ampiamente testati fin dall'inizio. Quando HTTP/2 è stato approvato dall'IESG, erano presenti decine di implementazioni di client e server accuratamente testate e pronte per la produzione. Infatti, poche settimane dopo l'approvazione del protocollo finale, molti utenti ne usufruivano già dei vantaggi in quanto diversi browser noti (e molti siti) eseguivano il deployment del supporto HTTP/2 completo.

Obiettivi di progettazione e tecnici

Le versioni precedenti del protocollo HTTP sono state progettate intenzionalmente per semplicità di implementazione: HTTP/0.9 era un protocollo di una riga per il bootstrap del World Wide Web; HTTP/1.0 documentava le estensioni popolari a HTTP/0.9 in uno standard informativo; HTTP/1.1 ha introdotto uno standard ufficiale della IETF. Consulta la pagina Breve storia di HTTP. HTTP/0.9-1.x ha quindi fornito esattamente ciò che voleva: HTTP è uno dei protocolli applicativi più diffusi su Internet.

Sfortunatamente, la semplicità dell'implementazione comportava anche un costo per le prestazioni dell'applicazione: i client HTTP/1.x devono utilizzare più connessioni per ottenere contemporaneità e ridurre la latenza; HTTP/1.x non comprime le intestazioni di richiesta e risposta, causando traffico di rete non necessario; HTTP/1.x non consente un'efficace assegnazione di priorità delle risorse, con un uso scorretto della connessione TCP sottostante e così via.

Questi limiti non sono stati fatali, ma man mano che le applicazioni web continuavano a crescere in ambito, complessità e importanza nella nostra vita quotidiana, hanno imposto un carico crescente sia per gli sviluppatori che per gli utenti del web, che è l'esatto divario che HTTP/2 è stato progettato per affrontare:

HTTP/2 consente un uso più efficiente delle risorse di rete e una percezione di latenza ridotta grazie all'introduzione della compressione dei campi di intestazione e alla possibilità di scambio simultaneo di più sulla stessa connessione... In particolare, consente l'interfoliazione dei messaggi di richiesta e risposta sulla stessa connessione e utilizza una codifica efficiente per i campi di intestazione HTTP. Consente inoltre di assegnare la priorità alle richieste, lasciando che le richieste più importanti vengano completate più rapidamente e migliorando ulteriormente le prestazioni.

Il protocollo risultante è più compatibile con la rete, poiché è possibile utilizzare meno connessioni TCP rispetto a HTTP/1.x. Ciò significa meno concorrenza con altri flussi e connessioni di lunga durata, che a loro volta porta a un migliore utilizzo della capacità di rete disponibile. Infine, HTTP/2 consente anche un'elaborazione più efficiente dei messaggi tramite l'uso dei frame per messaggi binari. (Hypertext Transfer Protocol versione 2, bozza 17)

È importante notare che HTTP/2 estende, non sostituisce, i precedenti standard HTTP. La semantica dell'applicazione HTTP è la stessa e non sono state apportate modifiche alle funzionalità offerte o ai concetti fondamentali come metodi HTTP, codici di stato, URI e campi di intestazione. Queste modifiche non rientravano esplicitamente nell'ambito dello sforzo HTTP/2. Detto questo, anche se l'API di alto livello rimane la stessa, è importante capire in che modo le modifiche di basso livello rispondono alle limitazioni delle prestazioni dei protocolli precedenti. Facciamo un breve tour del livello di inquadratura binaria e delle sue funzionalità.

Livello di inquadratura binaria

Al centro di tutti i miglioramenti delle prestazioni di HTTP/2 c'è il nuovo livello di frame binario, che determina le modalità di incapsulamento e trasferimento dei messaggi HTTP tra client e server.

Livello di frame binario HTTP/2

Il "livello" fa riferimento a una scelta di progettazione per introdurre un nuovo meccanismo di codifica ottimizzato tra l'interfaccia socket e l'API HTTP superiore esposta alle nostre applicazioni: la semantica HTTP, come verbi, metodi e intestazioni, non è interessata, ma il modo in cui vengono codificati durante il transito è diverso. A differenza del protocollo HTTP/1.x in testo normale delimitato da nuova riga, tutte le comunicazioni HTTP/2 vengono suddivise in messaggi e frame più piccoli, ognuno dei quali è codificato in formato binario.

Di conseguenza, sia il client che il server devono utilizzare il nuovo meccanismo di codifica binaria per comprendersi a vicenda: un client HTTP/1.x non capisce un server solo HTTP/2 e viceversa. Fortunatamente, le nostre applicazioni rimangono beatamente ignare di tutte queste modifiche, poiché il client e il server eseguono tutto il lavoro di framing necessario per nostro conto.

Flussi, messaggi e frame

L'introduzione del nuovo meccanismo di framing binario cambia il modo in cui i dati vengono scambiati tra client e server. Per descrivere questo processo, prendiamo a conoscere la terminologia HTTP/2:

  • Stream: un flusso bidirezionale di byte all'interno di una connessione stabilita, che può trasportare uno o più messaggi.
  • Messaggio: una sequenza completa di frame mappati a una richiesta o a un messaggio di risposta logico.
  • Frame: l'unità di comunicazione più piccola in HTTP/2, ciascuna contenente un'intestazione frame che identifica come minimo il flusso a cui appartiene il frame.

La relazione tra questi termini può essere riassunta come segue:

  • Tutte le comunicazioni vengono eseguite su una singola connessione TCP che può trasportare un numero qualsiasi di flussi bidirezionale.
  • Ogni flusso ha un identificatore univoco e informazioni facoltative sulla priorità che vengono utilizzate per il trasporto di messaggi bidirezionali.
  • Ogni messaggio è un messaggio HTTP logico, ad esempio una richiesta o risposta, composta da uno o più frame.
  • Il frame è la più piccola unità di comunicazione che porta un tipo specifico di dati, ad esempio le intestazioni HTTP, il payload dei messaggi e così via. I frame di flussi diversi possono essere interleanziati e quindi riassemblati tramite l'identificatore di flusso incorporato nell'intestazione di ciascun frame.

Stream, messaggi e frame HTTP/2

In breve, HTTP/2 scompone la comunicazione con protocollo HTTP in uno scambio di frame con codifica binaria, che vengono quindi mappati ai messaggi che appartengono a un determinato flusso, tutti multiplexati all'interno di una singola connessione TCP. Questa è la base su cui si basano tutte le altre funzionalità e le ottimizzazioni delle prestazioni fornite dal protocollo HTTP/2.

Multiplexing di richieste e risposte

Se con HTTP/1.x il client vuole effettuare più richieste in parallelo per migliorare le prestazioni, è necessario utilizzare più connessioni TCP (consulta la sezione Utilizzo di più connessioni TCP). Questo comportamento è una conseguenza diretta del modello di distribuzione HTTP/1.x, che garantisce che sia possibile consegnare una sola risposta alla volta (accodamento delle risposte) per connessione. Peggio ancora, questo comporta anche un blocco dell'head-of-line e un utilizzo inefficiente della connessione TCP sottostante.

Il nuovo livello di frame binario in HTTP/2 rimuove queste limitazioni e consente il multiplexing di richieste e risposte complete, consentendo a client e server di suddividere un messaggio HTTP in frame indipendenti, alternarli e riassemblarli dall'altra parte.

Multiplexing di richieste e risposte HTTP/2 all'interno di una connessione condivisa

Lo snapshot acquisisce più stream in corso all'interno della stessa connessione. Il client sta trasmettendo un frame DATA (stream 5) al server, mentre il server trasmette al client una sequenza di frame con interleaving per i flussi 1 e 3. Di conseguenza, sono in corso tre flussi paralleli.

La capacità di suddividere un messaggio HTTP in frame indipendenti, interlearli e riassemblarli dall'altro capo è l'aspetto più importante per il miglioramento di HTTP/2. In effetti, introduce un effetto a onde di numerosi vantaggi in termini di prestazioni nell'intero stack di tutte le tecnologie web, consentendo di:

  • Esegui l'interfoliazione di più richieste in parallelo senza bloccarne una.
  • Crea interleaving di più risposte in parallelo senza bloccarne una.
  • Utilizza un'unica connessione per fornire più richieste e risposte in parallelo.
  • Rimuovi le soluzioni alternative non necessarie per HTTP/1.x (consulta la pagina relativa all'ottimizzazione per HTTP/1.x, ad esempio file concatenati, sprite di immagini e partizionamento orizzontale dei domini).
  • Riduci i tempi di caricamento delle pagine eliminando le latenze non necessarie e migliorando l'utilizzo della capacità di rete disponibile.
  • E molto altro ancora...

Il nuovo livello di frame binario in HTTP/2 risolve il problema di blocco dell'head-of-line in HTTP/1.x ed elimina la necessità di più connessioni per abilitare l'elaborazione e la consegna parallele di richieste e risposte. Di conseguenza, il deployment delle nostre applicazioni è più rapido, semplice ed economico.

Assegnazione priorità stream

Una volta che un messaggio HTTP può essere suddiviso in più frame singoli e consentiamo la multiplexing dei frame di più flussi, l'ordine in cui i frame vengono interleanziati e pubblicati sia dal client che dal server diventa una considerazione fondamentale delle prestazioni. Per facilitare questa operazione, lo standard HTTP/2 consente a ogni flusso di avere una ponderazione e una dipendenza associate:

  • A ogni flusso può essere assegnato un peso intero compreso tra 1 e 256.
  • A ogni stream può essere assegnata una dipendenza esplicita da un altro.

La combinazione di dipendenze e ponderazioni dei flussi consente al client di creare e comunicare una "struttura ad albero delle priorità" che esprime come preferirebbe ricevere risposte. A sua volta, il server può utilizzare queste informazioni per dare priorità all'elaborazione dei flussi controllando l'allocazione di CPU, memoria e altre risorse e, quando i dati di risposta sono disponibili, allocare la larghezza di banda per garantire la distribuzione ottimale di risposte ad alta priorità al client.

Dipendenze e ponderazioni dei flussi HTTP/2

Una dipendenza di flusso all'interno di HTTP/2 viene dichiarata facendo riferimento all'identificatore univoco di un altro flusso come padre; se l'identificatore viene omesso, il flusso si dice che il flusso dipenda dal "flusso principale". La dichiarazione di una dipendenza dei flussi indica che, se possibile, al flusso padre devono essere allocate le risorse prima delle sue dipendenze. In altre parole, "elabora e fornisci la risposta D prima della risposta C".

Ai flussi che condividono lo stesso elemento padre (ovvero flussi di pari livello) devono essere allocate le risorse in proporzione alla loro ponderazione. Ad esempio, se il flusso A ha una ponderazione pari a 12 e l'elemento di pari livello B ha una ponderazione pari a 4, per determinare la proporzione delle risorse che ciascuno di questi flussi dovrebbe ricevere:

  1. Somma tutti i pesi: 4 + 12 = 16
  2. Dividi il peso di ogni stream per il peso totale: A = 12/16, B = 4/16

Di conseguenza, il flusso A dovrebbe ricevere tre quarti e il flusso B dovrebbe ricevere un quarto delle risorse disponibili; lo stream B dovrebbe ricevere un terzo delle risorse allocate per il flusso A. Vediamo qualche altro esempio pratico nell'immagine sopra. Da sinistra a destra:

  1. Né il flusso A né il flusso B specificano una dipendenza padre, che dipendono dal "flusso radice" implicito; A ha una ponderazione pari a 12, mentre B ha una ponderazione pari a 4. Pertanto, sulla base di ponderazioni proporzionali, il flusso B dovrebbe ricevere un terzo delle risorse allocate al flusso A.
  2. Il flusso D dipende dal flusso principale, mentre C dipende da D. Di conseguenza, D dovrebbe ricevere l'allocazione completa delle risorse prima di C. Le ponderazioni non sono significative, perché la dipendenza di C comunica una preferenza più forte.
  3. Il flusso D dovrebbe ricevere l'allocazione completa delle risorse prima di C; C dovrebbe ricevere l'allocazione completa delle risorse prima di A e B; il flusso B dovrebbe ricevere un terzo delle risorse allocate per il flusso A.
  4. Il flusso D dovrebbe ricevere l'allocazione completa delle risorse prima di E e C; E e C devono ricevere un'allocazione uguale prima di A e B; A e B dovrebbero ricevere un'allocazione proporzionale in base alle relative ponderazioni.

Come illustrato negli esempi precedenti, la combinazione di ponderazioni e dipendenze dei flussi fornisce un linguaggio espressivo per l'assegnazione delle priorità alle risorse, che è una funzionalità fondamentale per migliorare le prestazioni di navigazione quando abbiamo molti tipi di risorse con dipendenze e ponderazioni diverse. Inoltre, il protocollo HTTP/2 consente al client di aggiornare queste preferenze in qualsiasi momento, il che consente ulteriori ottimizzazioni nel browser. In altre parole, possiamo modificare le dipendenze e riassegnare i pesi in risposta all'interazione degli utenti e ad altri indicatori.

Una connessione per origine

Con il nuovo meccanismo di framing binario, HTTP/2 non ha più bisogno di più connessioni TCP ai flussi multiplex in parallelo; ogni flusso è suddiviso in molti frame, che possono essere interlacciati e prioritari. Di conseguenza, tutte le connessioni HTTP/2 sono permanenti ed è richiesta una sola connessione per origine, il che offre numerosi vantaggi in termini di prestazioni.

Sia per SPDY che per HTTP/2, la funzionalità killer è il multiplexing arbitrario su un singolo canale controllato da una congestione ottimale. Mi stupisce quanto sia importante e quanto funzioni. Un'ottima metrica che apprezzo molto è la frazione di connessioni create che eseguono una sola transazione HTTP (e quindi fanno in modo che tale transazione sostenga tutto il sovraccarico). Per HTTP/1, il 74% delle nostre connessioni attive esegue una sola transazione: le connessioni permanenti non sono utili come vorremmo tutti. Ma in HTTP/2 questo numero scende al 25%. Si tratta di un ottimo risultato grazie alla riduzione delle spese generali. (HTTP/2 è Live in Firefox, Patrick McManus)

La maggior parte dei trasferimenti HTTP è breve e bursty, mentre il protocollo TCP è ottimizzato per trasferimenti di dati in blocco di lunga durata. Riutilizzando la stessa connessione, HTTP/2 è in grado di utilizzare in modo più efficiente ogni connessione TCP e di ridurre notevolmente l'overhead complessivo del protocollo. Inoltre, l'utilizzo di un minor numero di connessioni riduce l'ingombro di memoria ed elaborazione lungo l'intero percorso di connessione (in altre parole client, intermediari e server di origine). Questo riduce i costi operativi complessivi e migliora l'utilizzo e la capacità della rete. Di conseguenza, il passaggio a HTTP/2 non dovrebbe solo ridurre la latenza di rete, ma anche contribuire a migliorare la velocità effettiva e a ridurre i costi operativi.

Controllo del flusso

Il controllo del flusso è un meccanismo che impedisce al mittente di sovraccaricare il destinatario con dati che potrebbe non volere o che potrebbe non essere in grado di elaborare: il destinatario potrebbe essere occupato, sottoposto a un carico elevato o essere disposto ad allocare solo una quantità fissa di risorse per un determinato flusso. Ad esempio, il client potrebbe aver richiesto uno stream video di grandi dimensioni con priorità elevata, ma l'utente ha messo in pausa il video e ora vuole metterlo in pausa o limitarne la pubblicazione dal server per evitare di recuperare e memorizzare nel buffering dati non necessari. In alternativa, un server proxy potrebbe avere connessioni upstream veloci e lente e in modo simile vuole regolare la velocità con cui il downstream consegna i dati in modo che corrisponda alla velocità dell'upstream per controllare l'utilizzo delle risorse e così via.

I requisiti precedenti ti ricordano il controllo del flusso TCP? Dovrebbero esserlo, poiché il problema è effettivamente identico (vedi Controllo del flusso). Tuttavia, poiché i flussi HTTP/2 sono multiplexati all'interno di una singola connessione TCP, il controllo del flusso TCP non è abbastanza granulare e non fornisce le API a livello di applicazione necessarie per regolare la distribuzione di singoli flussi. Per risolvere questo problema, HTTP/2 fornisce un insieme di semplici componenti di base che consentono al client e al server di implementare il proprio controllo del flusso a livello di flusso e connessione:

  • Il controllo del flusso è direzionale. Ogni destinatario può scegliere di impostare le dimensioni della finestra che desidera per ogni flusso e intera connessione.
  • Il controllo del flusso è basato sul credito. Ogni ricevitore pubblicizza la sua connessione iniziale e la finestra di controllo del flusso del flusso (in byte), che viene ridotta ogni volta che il mittente emette un frame DATA e viene incrementata tramite un frame WINDOW_UPDATE inviato dal destinatario.
  • Impossibile disabilitare il controllo del flusso. Una volta stabilita la connessione HTTP/2, il client e il server scambiano frame SETTINGS, che impostano le dimensioni della finestra di controllo del flusso in entrambe le direzioni. Il valore predefinito della finestra di controllo del flusso è impostato su 65.535 byte, ma il destinatario può impostare una dimensione massima della finestra (2^31-1 byte) e mantenerla inviando un frame WINDOW_UPDATE ogni volta che vengono ricevuti dati.
  • Il controllo del flusso è hop-by-hop, non end-to-end. Ciò significa che un intermediario può utilizzarlo per controllare l'utilizzo delle risorse e implementare meccanismi di allocazione delle risorse in base a propri criteri e euristiche.

HTTP/2 non specifica alcun algoritmo particolare per l'implementazione del controllo del flusso. Fornisce invece i semplici componenti di base e rinvia l'implementazione al client e al server, che possono utilizzarla per implementare strategie personalizzate per regolare l'utilizzo e l'allocazione delle risorse, nonché implementare nuove funzionalità di distribuzione che possono aiutare a migliorare le prestazioni reali e percepite (vedi Velocità, prestazioni e percezione umana) delle nostre applicazioni web.

Ad esempio, il controllo del flusso a livello di applicazione consente al browser di recuperare solo una parte di una determinata risorsa, sospendere il recupero riducendo a zero la finestra di controllo del flusso del flusso e riprenderla in un secondo momento. In altre parole, consente al browser di recuperare un'anteprima o una prima scansione di un'immagine, di visualizzarla e di consentire l'esecuzione di altri recuperi ad alta priorità, nonché di riprendere il recupero una volta completato il caricamento delle risorse critiche.

Push del server

Un'altra nuova e potente funzionalità di HTTP/2 è la possibilità del server di inviare più risposte per una singola richiesta del client. Ciò significa che, oltre alla risposta alla richiesta originale, il server può eseguire il push di risorse aggiuntive al client (Figura 12-5), senza che il client debba richiederle in modo esplicito.

Il server avvia nuovi flussi (promesse) per le risorse push

Perché avremmo bisogno di un meccanismo di questo tipo in un browser? Una tipica applicazione web è composta da decine di risorse, che vengono tutte scoperte dal client esaminando il documento fornito dal server. Come risultato, perché non eliminare la latenza aggiuntiva e consentire al server di eseguire in anticipo il push delle risorse associate? Il server sa già quali risorse saranno richieste dal client: si tratta del push del server.

Infatti, se hai incorporato un CSS, JavaScript o qualsiasi altra risorsa tramite un URI di dati (vedi Risorse incorporate), hai già un'esperienza pratica con il push del server. Incorporando manualmente la risorsa nel documento, la stiamo inviando al client senza attendere che il client la richieda. Con HTTP/2 possiamo ottenere gli stessi risultati, ma con ulteriori vantaggi in termini di prestazioni. Le risorse push possono essere:

  • Memorizzata nella cache dal client
  • Riutilizzati in diverse pagine
  • multiplexing insieme ad altre risorse
  • Con priorità dal server
  • Rifiutato dal cliente

Guida introduttiva PUSH_PROMISE

Tutti i flussi di push del server vengono avviati tramite frame PUSH_PROMISE, che indicano l'intenzione del server di eseguire il push delle risorse descritte al client e devono essere consegnati prima dei dati di risposta che richiedono le risorse trasferite. Questo ordine di consegna è fondamentale: il client deve sapere a quali risorse il server intende eseguire il push per evitare di creare richieste duplicate per queste risorse. La strategia più semplice per soddisfare questo requisito è inviare tutti i frame PUSH_PROMISE, che contengono solo le intestazioni HTTP della risorsa promessa, prima della risposta del padre (in altre parole, DATA frame).

Quando il client riceve un frame PUSH_PROMISE, può scegliere di rifiutare lo stream (tramite un frame RST_STREAM), se vuole. (ad esempio perché la risorsa è già nella cache). Questo è un miglioramento importante rispetto a HTTP/1.x. Al contrario, l'utilizzo dell'incorporamento delle risorse, una popolare "ottimizzazione" per HTTP/1.x, equivale a un "push forzato": il client non può disattivarlo, annullarlo o elaborare singolarmente la risorsa incorporata.

Con HTTP/2, il client mantiene il pieno controllo del modo in cui viene utilizzato il push del server. Il client può limitare il numero di flussi inviati contemporaneamente, regolare la finestra di controllo del flusso iniziale per controllare la quantità di dati inviati alla prima apertura del flusso o disattivare completamente il push del server. Queste preferenze vengono comunicate tramite i frame SETTINGS all'inizio della connessione HTTP/2 e possono essere aggiornate in qualsiasi momento.

Ogni risorsa di cui viene eseguito il push è un flusso che, a differenza di una risorsa incorporata, consente di essere multiplexata, assegnata la priorità ed elaborata singolarmente dal client. L'unica limitazione di sicurezza, applicata dal browser, è che le risorse di cui viene eseguito il push devono rispettare il criterio della stessa origine: il server deve essere autorevole per i contenuti forniti.

Compressione delle intestazioni

Ogni trasferimento HTTP trasporta un insieme di intestazioni che descrivono la risorsa trasferita e le sue proprietà. In HTTP/1.x, questi metadati vengono sempre inviati come testo normale e aggiungono da 500 a 800 byte di overhead per trasferimento e, a volte, ulteriori kilobyte se vengono utilizzati cookie HTTP. Per ridurre questo overhead e migliorare le prestazioni, HTTP/2 comprime i metadati dell'intestazione di richiesta e risposta utilizzando il formato di compressione HPACK che utilizza due tecniche semplici ma potenti:

  1. Consente la codifica dei campi di intestazione trasmessi tramite un codice Huffman statico, il che riduce le dimensioni dei singoli trasferimenti.
  2. Richiede che sia il client che il server mantengano e aggiornino un elenco indicizzato di campi di intestazione visualizzati in precedenza (in altre parole, stabilisce un contesto di compressione condiviso), che viene quindi utilizzato come riferimento per codificare in modo efficiente valori trasmessi in precedenza.

La codifica Huffman consente di comprimere i singoli valori al momento del trasferimento e l'elenco indicizzato dei valori trasferiti in precedenza ci permette di codificare i valori duplicati trasferendo valori di indice che possono essere utilizzati per cercare e ricostruire in modo efficiente chiavi e valori di intestazione completi.

HPACK: Compressione delle intestazioni per HTTP/2

Per un'ulteriore ottimizzazione, il contesto di compressione HPACK è costituito da una tabella statica e dinamica: la tabella statica è definita nella specifica e fornisce un elenco dei campi di intestazione HTTP comuni che tutte le connessioni potrebbero utilizzare (ad es.nomi di intestazione validi). La tabella dinamica è inizialmente vuota e viene aggiornata in base ai valori scambiati all'interno di una determinata connessione. Di conseguenza, le dimensioni di ogni richiesta vengono ridotte utilizzando la codifica statica di Huffman per valori che non erano mai stati visualizzati in precedenza e la sostituzione degli indici con i valori già presenti nelle tabelle statiche o dinamiche su ogni lato.

Sicurezza e prestazioni di HPACK

Le prime versioni di HTTP/2 e SPDY utilizzavano zlib, con un dizionario personalizzato, per comprimere tutte le intestazioni HTTP. Ciò ha consentito una riduzione dall'85% all'88% delle dimensioni dei dati dell'intestazione trasferiti e un miglioramento significativo della latenza del tempo di caricamento delle pagine:

Nel collegamento DSL a larghezza di banda inferiore, in cui il link di caricamento è di soli 375 Kbps, la richiesta di compressione dell'intestazione in particolare ha portato a miglioramenti significativi del tempo di caricamento della pagina per alcuni siti (in altre parole, quelli che inviavano un elevato numero di richieste di risorse). Abbiamo rilevato una riduzione di 45-1142 ms nel tempo di caricamento della pagina semplicemente a causa della compressione delle intestazioni. (white paper SPDY, chromium.org)

Tuttavia, nell'estate del 2012, è stato pubblicato un attacco alla sicurezza "CRIME" contro gli algoritmi di compressione TLS e SPDY, che potrebbe causare la compromissione della sessione. Di conseguenza, l'algoritmo di compressione zlib è stato sostituito da HPACK, progettato appositamente per risolvere i problemi di sicurezza rilevati, essere efficiente e semplice da implementare correttamente e, naturalmente, consentire una buona compressione dei metadati delle intestazioni HTTP.

Per informazioni dettagliate sull'algoritmo di compressione HPACK, consulta la pagina IETF HPACK - Header Compression for HTTP/2.

Per approfondire

Feedback