Introduzione a HTTP/2

HTTP/2 renderà le nostre applicazioni più veloci, più semplici e più solide (una combinazione rara) consentendoci di annullare molte delle soluzioni HTTP/1.1 precedentemente eseguite nelle 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 per 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 di intestazione HTTP e aggiungere il supporto per la prioritizzazione delle richieste e il push del server. Per implementare questi requisiti, è disponibile un ampio cast di supporto di altri miglioramenti del protocollo, come nuovi meccanismi di controllo del flusso, gestione degli errori e 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 principali, ad esempio i metodi HTTP, i codici di stato, gli URI e i campi di intestazione, rimangono invariati. Al contrario, HTTP/2 modifica il modo in cui i dati vengono formattati (frame) e trasferiti tra il client e il server, entrambi che gestiscono l'intero processo e nasconde tutta la complessità delle nostre applicazioni all'interno del nuovo livello di inquadratura. Di conseguenza, tutte le applicazioni esistenti possono essere pubblicate senza modifiche.

Perché non HTTP/1.2?

Per raggiungere gli obiettivi di prestazioni stabiliti dall'HTTP Working Group, HTTP/2 introduce un nuovo livello di inquadratura binaria non compatibile con le versioni precedenti dei server e dei client HTTP/1.x precedenti, da cui è il principale incremento di versione del protocollo su HTTP/2.

Detto questo, a meno che non implementi un server web (o un client personalizzato) lavorando con socket TCP non elaborati, non noterai alcuna differenza: tutta la nuova inquadratura 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.

Una breve storia di SPDY e HTTP/2

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

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

Poco dopo l'annuncio iniziale, Mike Belshe e Roberto Peon, entrambi software engineer di Google, hanno condiviso i primi risultati, la documentazione e il 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 primi 25 siti web tramite connessioni di rete domestiche simulate, notiamo un miglioramento significativo nelle prestazioni, ossia le pagine vengono caricate fino al 55% più velocemente. (Blog di Chromium)

Nel 2012 il nuovo protocollo sperimentale era supportato su Chrome, Firefox e Opera e un numero di siti in rapida crescita, sia di grandi dimensioni (ad esempio Google, Twitter, Facebook) che di piccole dimensioni, eseguiva il deployment di SPDY all'interno della propria infrastruttura. SPDY era sulla buona strada per diventare di fatto uno standard attraverso la crescita dell'adozione da parte del settore.

Osservando questa tendenza, l'HTTP Working Group (HTTP-WG) ha dato il via a un nuovo impegno per prendere le lezioni apprese da SPDY, realizzarle e migliorarle e fornire uno standard "HTTP/2" ufficiale. È stata redatta una nuova carta, è stato presentato un invito aperto a presentare proposte per HTTP/2 e, dopo molte 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 coevolgere in parallelo, mentre SPDY ha funzionato come ramo sperimentale utilizzato per testare nuove funzionalità e proposte per lo standard HTTP/2. Ciò che sembra buono sulla carta potrebbe non funzionare nella pratica e viceversa. SPDY ha offerto un percorso per testare e valutare ogni proposta prima di essere inclusa nello standard HTTP/2. Alla fine, questo processo è durato tre anni e ha portato a oltre 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: vengono pubblicate HTTP/2 bozza-17 e HPACK bozza-12
  • Agosto 2014: ultima chiamata al gruppo di lavoro per HTTP/2
  • Febbraio 2015: IESG approva le bozze HTTP/2 e HPACK
  • Maggio 2015: vengono pubblicate RFC 7540 (HTTP/2) e RFC 7541 (HPACK)

All'inizio del 2015, 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 modifiche principali 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 dal lavoro svolto in un protocollo aperto precedente ma non standard denominato SPDY. Chrome supporta SPDY sin dalla versione 6 di Chrome, ma poiché la maggior parte dei vantaggi è presente in HTTP/2, è il momento di dire addio. Abbiamo intenzione di rimuovere il supporto per SPDY all'inizio del 2016 e di rimuovere allo stesso tempo il supporto per l'estensione TLS denominata NPN a favore di ALPN in Chrome. Consigliamo vivamente agli sviluppatori di server di passare a HTTP/2 e ALPN.

Siamo felici di aver contribuito al processo relativo agli standard aperti che ha portato a HTTP/2 e speriamo di vederne un'ampia adozione, dato l'ampio impegno del settore in materia di standardizzazione e implementazione. (Blog di Chromium)

La coevoluzione di SPDY e di sviluppatori di siti, browser e server abilitati per HTTP/2 per ottenere un'esperienza reale con il nuovo protocollo mentre veniva sviluppato. Di conseguenza, lo standard HTTP/2 è uno degli standard migliori e più ampiamente testati fin dal primo avvio. Al momento dell'approvazione di HTTP/2 da parte di 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 stavano già beneficiando dei vantaggi poiché diversi browser popolari (e molti siti) eseguivano il deployment del supporto HTTP/2 completo.

Obiettivi di progettazione e tecnici

Le versioni precedenti del protocollo HTTP erano state progettate intenzionalmente per semplificare l'implementazione: HTTP/0.9 era un protocollo a una riga per il bootstrap del World Wide Web; HTTP/1.0 documentava le estensioni popolari per HTTP/0.9 in uno standard informativo; HTTP/1.1 ha introdotto uno standard ufficiale IETF; consulta Brief History of HTTP. Pertanto, HTTP/0.9-1.x ha fornito esattamente ciò che intendeva fare: HTTP è uno dei protocolli di applicazione più ampiamente adottati su Internet.

Purtroppo, la semplicità dell'implementazione ha a sua volta costo 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 conseguente utilizzo scadente della connessione TCP sottostante. e così via.

Questi limiti non sono stati fatali, ma poiché le applicazioni web hanno continuato a crescere nella loro portata, nella loro complessità e nella nostra importanza nella vita di tutti i giorni, hanno imposto un onere crescente sia agli sviluppatori che agli utenti del web, che è l'esatta lacuna che HTTP/2 era stato progettato per risolvere:

HTTP/2 consente un uso più efficiente delle risorse di rete e una minore percezione della latenza grazie alla compressione dei campi di intestazione e alla possibilità di più scambi simultanei sulla stessa connessione. Nello specifico, consente interleaving di messaggi di richiesta e risposta sulla stessa connessione e utilizza una codifica efficiente per i campi di intestazione HTTP. Inoltre, consente di dare priorità alle richieste, consentendo di completare più rapidamente quelle più importanti, migliorando ulteriormente le prestazioni.

Il protocollo risultante è più compatibile con la rete, perché è possibile utilizzare meno connessioni TCP rispetto a HTTP/1.x. Ciò significa meno competizione con altri flussi e connessioni di maggiore durata, il che a sua volta porta a un migliore utilizzo della capacità di rete disponibile. Infine, HTTP/2 consente anche un'elaborazione più efficiente dei messaggi grazie all'uso dell'inquadratura dei 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 per HTTP è la stessa e non sono state apportate modifiche alle funzionalità offerte o ai concetti fondamentali, come i metodi HTTP, i codici di stato, gli URI e i campi di intestazione. Queste modifiche non rientravano esplicitamente nell'ambito di 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 ai limiti di prestazioni dei protocolli precedenti. Facciamo un breve tour del livello di frame binari e delle sue funzionalità.

Livello di inquadratura binaria

Il fulcro di tutti i miglioramenti delle prestazioni di HTTP/2 è il nuovo livello di inquadratura binaria, che determina il modo in cui i messaggi HTTP vengono incapsulati e trasferiti tra il client e il server.

Livello di inquadratura binaria HTTP/2

Il "livello" si riferisce 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 con testo normale delimitato da nuova riga, tutte le comunicazioni HTTP/2 sono 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 è in grado di comprendere un server solo HTTP/2 e viceversa. Fortunatamente, le nostre applicazioni rimangono piacevolmente inconsapevoli di tutti questi cambiamenti, dato che il client e il server eseguono tutte le operazioni di inquadratura necessarie per nostro conto.

Stream, messaggi e frame

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

  • Flusso: 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 un messaggio di richiesta o risposta logico.
  • Frame: l'unità di comunicazione più piccola in HTTP/2, ciascuna contenente un'intestazione frame che identifica almeno 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 bidirezionali.
  • Ogni flusso ha un identificatore univoco e informazioni facoltative sulla priorità utilizzate per trasferire messaggi bidirezionali.
  • Ogni messaggio è un messaggio HTTP logico, come una richiesta o una risposta, composto da uno o più frame.
  • Il frame è l'unità di comunicazione più piccola che trasporta un tipo specifico di dati, ad esempio Intestazioni HTTP, payload dei messaggi e così via. I frame di flussi diversi possono essere interlacciati e poi riassemblati tramite l'identificatore di stream incorporato nell'intestazione di ciascun frame.

Flussi, messaggi e frame HTTP/2

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

multiplexing di richieste e risposte

Con HTTP/1.x, se il client desidera effettuare più richieste parallele per migliorare le prestazioni, è necessario utilizzare più connessioni TCP (consulta la pagina 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 head-of-line e un uso inefficiente della connessione TCP sottostante.

Il nuovo livello di inquadratura binaria in HTTP/2 rimuove queste limitazioni e consente il multiplexing completo di richieste e risposte, consentendo al client e al server di suddividere un messaggio HTTP in frame indipendenti, interfonderli e quindi riassemblarli dall'altra parte.

multiplexing di richiesta e risposta HTTP/2 all'interno di una connessione condivisa

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

La possibilità di suddividere un messaggio HTTP in frame indipendenti, interlacciarli e quindi riassemblarli all'altra estremità è l'unico miglioramento più importante di HTTP/2. Di fatto, introduce un effetto a onde di numerosi vantaggi in termini di prestazioni sull'intero stack di tutte le tecnologie web, consentendoci di:

  • Interlea più richieste in parallelo senza bloccarne nessuna.
  • Interlea più risposte in parallelo senza bloccarne nessuna.
  • Utilizza un'unica connessione per consegnare più richieste e risposte in parallelo.
  • Rimuovi le soluzioni alternative HTTP/1.x non necessarie (consulta 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 la latenza non necessaria e migliorando l'utilizzo della capacità di rete disponibile.
  • E molto altro ancora...

Il nuovo livello di inquadratura binaria in HTTP/2 risolve il problema di blocco head-of-line in HTTP/1.x ed elimina la necessità di connessioni multiple per consentire l'elaborazione parallela e la consegna di richieste e risposte. Di conseguenza, il deployment delle nostre applicazioni è più veloce, semplice ed economico.

Assegnazione della priorità ai flussi

Una volta che un messaggio HTTP può essere suddiviso in più frame singoli e se consentiamo il multiplexing dei frame di più flussi, l'ordine in cui i frame sono interlealati e pubblicati sia dal client che dal server diventa una considerazione fondamentale per le prestazioni. Per facilitare questa operazione, lo standard HTTP/2 consente a ogni flusso di avere un peso e una dipendenza associati:

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

La combinazione di dipendenze e ponderazioni del flusso consente al client di costruire e comunicare un "albero di assegnazione delle priorità" che esprime la preferenza per la ricezione delle risposte. A sua volta, il server può utilizzare queste informazioni per dare la priorità all'elaborazione dei flussi controllando l'allocazione di CPU, memoria e altre risorse e, una volta disponibili i dati di risposta, l'allocazione della larghezza di banda per garantire la consegna ottimale di risposte ad alta priorità al client.

Dipendenze e pesi dei flussi HTTP/2

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

Ai flussi che condividono lo stesso padre (in altre parole, flussi di pari livello) devono essere allocate le risorse in proporzione al loro peso. Ad esempio, se il flusso A ha un peso pari a 12 e l'unico flusso di pari livello B ha un peso pari a 4, devi determinare la proporzione delle risorse che ciascuno di questi flussi dovrebbe ricevere:

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

Di conseguenza, il flusso A deve ricevere tre quarti e il flusso B deve ricevere un quarto delle risorse disponibili; il flusso B deve ricevere un terzo delle risorse assegnate al flusso A. Vediamo qualche altro esempio pratico nell'immagine qui sopra. Da sinistra a destra:

  1. Né il flusso A né il flusso B specificano una dipendenza padre e si dice che siano dipendenti dal "flusso principale implicito". A ha un peso pari a 12, mentre B ha un peso pari a 4. Pertanto, in base a ponderazioni proporzionali, il flusso B dovrebbe ricevere un terzo delle risorse allocate per il flusso A.
  2. Il flusso D dipende dallo stream principale, mentre C dipende da D. Di conseguenza, D dovrebbe ricevere l'allocazione completa delle risorse prima di C. Le ponderazioni sono irrilevanti perché la dipendenza di C comunica una preferenza più forte.
  3. Il flusso D deve ricevere l'allocazione completa delle risorse prima di C; C deve ricevere l'allocazione completa delle risorse prima di A e B; il flusso B deve ricevere un terzo delle risorse assegnate per il flusso A.
  4. Il flusso D deve ricevere l'allocazione completa delle risorse prima di E e C; E e C devono ricevere la stessa allocazione prima di A e B; A e B dovrebbero ricevere un'allocazione proporzionale in base ai rispettivi pesi.

Come illustrato negli esempi precedenti, la combinazione di dipendenze e ponderazioni dei flussi fornisce un linguaggio espressivo per l'assegnazione della priorità delle risorse, una funzionalità fondamentale per migliorare le prestazioni di esplorazione in cui sono presenti molti tipi di risorse con dipendenze e pesi diversi. Inoltre, il protocollo HTTP/2 consente al client di aggiornare queste preferenze in qualsiasi momento, il che consente ulteriori ottimizzazioni nel browser. Possiamo modificare le dipendenze e riallocare i pesi in risposta all'interazione dell'utente.

Una connessione per origine

Con il nuovo meccanismo di inquadratura binaria, HTTP/2 non ha più bisogno di connessioni TCP multiple a flussi multiplex in parallelo; ogni flusso è suddiviso in più frame, che possono essere interlacciati e organizzati in base alla priorità. 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 dalla congestione del pozzo. Mi sorprende quanto sia importante e quanto funzioni bene. Un'ottima metrica attorno a questo aspetto che adoro è la frazione di connessioni create che effettuano una sola transazione HTTP e che, di conseguenza, fa sì che questa transazione sopporti tutto l'overhead. Per HTTP/1, il 74% delle nostre connessioni attive comporta una sola transazione, le connessioni persistenti non sono utili come vogliamo. Ma in HTTP/2 questo numero precipita al 25%. Si tratta di un ottimo risultato per la riduzione dei costi generali. (HTTP/2 è disponibile su Firefox, Patrick McManus)

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

Controllo del flusso

Il controllo del flusso è un meccanismo per evitare che il mittente sovraccarichi il destinatario di 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 potrebbe essere disposto solo ad allocare 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 il client ora vuole mettere in pausa o limitare la pubblicazione del video dal server per evitare di recuperare e inserire nel buffering i dati non necessari. In alternativa, un server proxy può avere connessioni downstream veloci e upstream lente e allo stesso modo desidera regolare la velocità con cui il downstream invia i dati in modo che corrispondano alla velocità upstream per controllare l'utilizzo delle risorse e così via.

I requisiti riportati sopra ricordano il controllo del flusso TCP? poiché il problema è effettivamente identico (vedi Controllo dei flussi). Tuttavia, poiché i flussi HTTP/2 vengono 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 dei singoli flussi. Per risolvere questo problema, HTTP/2 fornisce un insieme di semplici componenti di base che consentono a client e server di implementare il proprio controllo del flusso a livello di flusso e connessione:

  • Il controllo del flusso è direzionale. Ciascun destinatario può scegliere di impostare la dimensione della finestra che desidera per ogni stream e l'intera connessione.
  • Il controllo del flusso è basato sul credito. Ogni ricevitore annuncia la sua connessione iniziale e la finestra di controllo del flusso del flusso (in byte), che viene ridotta ogni volta che il mittente invia un frame DATA e viene incrementata tramite un frame WINDOW_UPDATE inviato dal ricevitore.
  • Il controllo del flusso non può essere disattivato. Una volta stabilita la connessione HTTP/2, il client e il server si scambiano i 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 ricevitore può impostare una grande 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 ed euristiche.

HTTP/2 non specifica alcun algoritmo specifico per l'implementazione del controllo del flusso. Fornisce invece i semplici componenti di base e rimanda l'implementazione al client e al server, che possono utilizzarli per implementare strategie personalizzate per regolare l'utilizzo e l'allocazione delle risorse, nonché implementare nuove funzionalità di distribuzione che possono contribuire 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, mettere in attesa il recupero riducendo a zero la finestra di controllo del flusso del flusso, per poi riprenderla in un secondo momento. In altre parole, consente al browser di recuperare un'anteprima o la prima scansione di un'immagine, di visualizzarla e di consentire altri recuperi ad alta priorità di procedere e di riprendere il recupero al termine del caricamento delle risorse critiche.

Push del server

Un'altra nuova potente funzionalità di HTTP/2 è la capacità 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 esplicitamente tutte.

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

Perché dovremmo avere questo meccanismo in un browser? Una tipica applicazione web è costituita da decine di risorse, tutte rilevate dal client esaminando il documento fornito dal server. Di conseguenza, perché non eliminare la latenza aggiuntiva e lasciare che il server esegua 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 in passato hai incorporato un CSS, JavaScript o qualsiasi altro asset tramite un URI di dati (vedi Risorse incorporate), allora hai già esperienza pratica con il push del server. Se incorpori manualmente la risorsa nel documento, trasferiamo quella risorsa al client, senza attendere che venga richiesta da quest'ultimo. Con HTTP/2 possiamo ottenere gli stessi risultati, ma con ulteriori vantaggi in termini di prestazioni. Le risorse push possono essere:

  • Memorizzati nella cache dal client
  • Riutilizzato in pagine diverse
  • multiplex insieme ad altre risorse
  • Con priorità del server
  • Rifiutato dal cliente

Guida introduttiva a PUSH_PROMISE

Tutti i flussi di push del server vengono avviati tramite frame PUSH_PROMISE, che indicano l'intent del server di eseguire il push delle risorse descritte al client e devono essere consegnati prima dei dati di risposta che richiedono le risorse sottoposte a push. Questo ordine di consegna è fondamentale: il client deve conoscere le risorse di cui 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 dell'elemento padre (in altre parole, DATA frame).

Una volta che il client riceve un frame PUSH_PROMISE, può rifiutare lo stream (tramite un frame RST_STREAM), se lo desidera. Ciò potrebbe verificarsi, ad esempio, perché la risorsa è già nella cache. Si tratta di un importante miglioramento 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ò disattivare, annullare o elaborare la risorsa integrata singolarmente.

Con HTTP/2 il client mantiene il pieno controllo dell'utilizzo del push del server. Il client può limitare il numero di flussi di dati inviati contemporaneamente, regolare la finestra iniziale di controllo del flusso per controllare la quantità di dati inviata alla prima apertura del flusso o disabilitare 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 inviata tramite push è un flusso che, a differenza di una risorsa incorporata, consente di essere multiplexati, assegnati priorità ed elaborati dal client. L'unica limitazione di sicurezza, applicata dal browser, è che le risorse sottoposte a push devono rispettare il criterio della stessa origine: il server deve essere autorevole per i contenuti forniti.

Compressione intestazione

Ogni trasferimento HTTP include 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 vengono aggiunti in qualsiasi punto compreso tra 500 e 800 byte di overhead per trasferimento e, a volte, kilobyte in più se vengono utilizzati i cookie HTTP. Per ridurre questo overhead e migliorare le prestazioni, HTTP/2 comprime i metadati delle intestazioni di richiesta e risposta utilizzando il formato di compressione HPACK, che utilizza due tecniche semplici ma potenti:

  1. Consente di codificare i campi di intestazione trasmessi tramite un codice Huffman statico, che riduce le singole dimensioni di trasferimento.
  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 poi utilizzato come riferimento per codificare in modo efficiente i 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 consente di codificare i valori duplicati trasferendo i valori di indice che possono essere utilizzati per cercare e ricostruire in modo efficiente le chiavi e i valori di intestazione completi.

HPACK: compressione delle intestazioni per HTTP/2

Come ulteriore ottimizzazione, il contesto di compressione HPACK è costituito da una tabella statica e dinamica: la tabella statica è definita nella specifica e fornisce un elenco di 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 Huffman statica per i valori mai visti prima e la sostituzione degli indici per i valori già presenti nelle tabelle statiche o dinamiche su ciascun 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 portato a una riduzione dall'85% all'88% delle dimensioni dei dati di intestazione trasferiti e a un miglioramento significativo della latenza del tempo di caricamento delle pagine:

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

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

Per i dettagli completi sull'algoritmo di compressione HPACK, consulta IETF HPACK - Header Compression for HTTP/2.

Per approfondire