Introduzione a HTTP/2

HTTP/2 renderà le nostre applicazioni più veloci, semplici e solide (una rara combinazione) consentendoci di annullare molte delle soluzioni alternative per HTTP/1.1 eseguite in precedenza 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 di HTTP/2 sono ridurre la latenza abilitando il multiplexing completo di richieste e risposte, ridurre al minimo l'overhead del protocollo tramite la compressione efficiente dei campi di intestazione HTTP e aggiungere il supporto per l'assegnazione della priorità delle richieste e il push del server. Per implementare questi requisiti, è disponibile una vasta gamma di altri miglioramenti dei protocolli, come il nuovo controllo del flusso, la gestione degli errori e i meccanismi di upgrade, ma queste sono le funzionalità più importanti che ogni sviluppatore web dovrebbe 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, rimangono invariati. HTTP/2 modifica invece il modo in cui i dati vengono formattati (framed) e trasferiti tra client e server, entrambi i quali gestiscono l'intero processo, e nasconde tutta la complessità alle nostre applicazioni all'interno del nuovo livello di frame. 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 frame binario non retrocompatibile con i precedenti server e client HTTP/1.x, per cui la versione principale del protocollo viene incrementata a HTTP/2.

Detto questo, a meno che tu non implementi un server web (o un client personalizzato) utilizzando 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.

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 cercare di ridurre la latenza di caricamento delle pagine web risolvendo alcune delle note limitazioni 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, 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 25 siti web principali tramite connessioni di rete di casa simulate, notiamo un miglioramento significativo delle prestazioni: le pagine vengono caricate fino al 55% più velocemente. (Blog di Chromium)

Nel 2012 il nuovo protocollo sperimentale era supportato in Chrome, Firefox e Opera e un numero in rapida crescita di siti, sia grandi (ad esempio Google, Twitter, Facebook) che piccoli, stava implementando SPDY all'interno della propria infrastruttura. Di fatto, SPDY era sulla buona strada per diventare uno standard di fatto grazie alla crescente adozione del settore.

Osservando questa tendenza, l'HTTP Working Group (HTTP-WG) ha intrapreso un nuovo impegno per sfruttare 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 per proposte su HTTP/2 e, dopo un'ampia discussione 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 coevolversi in parallelo, con SPDY che fungeva da ramo sperimentale utilizzato per testare nuove funzionalità e proposte per lo standard HTTP/2. Ciò che sembra corretto sulla carta potrebbe non funzionare nell'esercizio pratico 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 portato a 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 la bozza 17 di HTTP/2 e la bozza HPACK-12
  • Agosto 2014: ultima chiamata di Working Group per HTTP/2
  • Febbraio 2015: bozze di HTTP/2 e HPACK approvate da IESG
  • 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 delle estensioni 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 di protocolli, si sono evolute dal lavoro svolto in un protocollo aperto precedente, 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 di rimuovere contemporaneamente 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 aperto che ha portato a HTTP/2 e speriamo di vederne un'ampia adozione dato l'ampio impegno del settore in termini di standardizzazione e implementazione. (Blog di Chromium)

La coevoluzione di sviluppatori di server, browser e siti abilitati per SPDY e HTTP/2 al fine di ottenere un'esperienza reale con il nuovo protocollo durante lo sviluppo. Di conseguenza, lo standard HTTP/2 è uno degli standard migliori e più testati fin da subito. 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 ne usufruivano già dei vantaggi perché 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 semplificare l'implementazione: HTTP/0.9 era un protocollo di una riga per eseguire il bootstrap del World Wide Web, HTTP/1.0 documentava le popolari estensioni a HTTP/0.9 in uno standard informativo; HTTP/1.1 ha introdotto uno standard IETF ufficiale. Consulta Breve storia di HTTP. Pertanto, HTTP/0.9-1.x ha fornito esattamente ciò che voleva: HTTP è uno dei protocolli applicativi più diffusi su internet.

Sfortunatamente, la semplicità dell'implementazione comportava anche un costo in termini di prestazioni dell'applicazione: i client HTTP/1.x devono utilizzare più connessioni per ottenere la contemporaneità e ridurre la latenza, HTTP/1.x non comprime le intestazioni di richieste e risposte, causando traffico di rete non necessario, HTTP/1.x non consente un'efficace prioritizzazione delle risorse, con un conseguente utilizzo scadente della connessione TCP sottostante e così via.

Queste limitazioni non sono state fatali, ma poiché le applicazioni web hanno continuato a crescere in ambito, complessità e importanza nella nostra vita quotidiana, hanno imposto un onere crescente sia per gli sviluppatori che per gli utenti del web, che è l'esatto divario che HTTP/2 era stato progettato per risolvere:

HTTP/2 consente un uso più efficiente delle risorse di rete e una percezione ridotta della latenza grazie all'introduzione della compressione dei campi di intestazione e alla possibilità di più scambi contemporanei sulla stessa connessione... In particolare, consente l'interleaving dei messaggi di richiesta e di risposta sulla stessa connessione e utilizza una codifica efficiente per i campi di intestazione HTTP. Consente inoltre di dare priorità alle richieste, consentendo di completare più rapidamente quelle più importanti e di migliorare ulteriormente le prestazioni.

Il protocollo risultante è più compatibile con la rete, in quanto possono essere utilizzate meno connessioni TCP rispetto a HTTP/1.x. Ciò significa meno concorrenza 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 tramite frame di 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 erano incluse esplicitamente nell'ambito dell'iniziativa HTTP/2. Detto questo, sebbene l'API di alto livello rimanga la stessa, è importante capire in che modo le modifiche di basso livello risolvono i limiti delle prestazioni dei protocolli precedenti. Facciamo un breve tour dello strato di inquadratura binaria e delle sue caratteristiche.

Livello di inquadratura binaria

Alla base di tutti i miglioramenti delle prestazioni di HTTP/2 c'è il nuovo livello di inquadratura binaria, che determina le modalità di incapsulamento e trasferimento dei messaggi HTTP tra client e server.

Livello di inquadratura binaria 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 di livello 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 non crittografato delimitato da nuova riga, tutta la comunicazione HTTP/2 è suddivisa 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: un client HTTP/1.x non è in grado di comprendere un server solo HTTP/2 e viceversa. Fortunatamente, le nostre applicazioni rimangono perfettamente inconsapevoli di tutte queste modifiche, poiché il client e il server eseguono tutte le operazioni di inquadratura necessarie per nostro conto.

Flussi, messaggi e frame

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

  • Flusso: un flusso bidirezionale di byte all'interno di una connessione stabilita, che può trasferire uno o più messaggi.
  • Messaggio: una sequenza completa di frame mappati a una richiesta logico o a un messaggio di risposta.
  • 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 illimitato di flussi bidirezionali.
  • Ogni flusso ha un identificatore univoco e informazioni facoltative sulla priorità che viene utilizzato per trasmettere i messaggi bidirezionali.
  • Ciascun messaggio è un messaggio HTTP logico, ad esempio una richiesta o risposta, composta da uno o più frame.
  • Il frame è l'unità di comunicazione più piccola che trasporta un tipo specifico di dati, ad esempio le intestazioni HTTP, il payload dei messaggi e così via. I frame di stream diversi possono essere interlacciati e poi riassemblati tramite l'identificatore di stream incorporato nell'intestazione di ogni frame.

Flussi, 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 poi mappati ai messaggi appartenenti a un determinato flusso, tutti multiplexati all'interno di un'unica 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 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 recapitare una sola risposta alla volta (coda delle risposte) per connessione. Peggio ancora, questo comporta anche un blocco head-of-line e un utilizzo 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 a client e server di suddividere un messaggio HTTP in frame indipendenti, eseguire l'interleaving e quindi riassemblarli dall'altro capo.

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 trasmette un frame DATA (stream 5) al server, mentre il server trasmette una sequenza di frame con interleaving al client per i flussi 1 e 3. Di conseguenza, si verificano tre stream paralleli.

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

  • Esegui l'interfoliazione di più richieste in parallelo senza bloccarne una.
  • Incrocia più risposte in parallelo senza bloccarne una.
  • Utilizza un'unica connessione per fornire più richieste e risposte in parallelo.
  • Rimuovi le soluzioni alternative HTTP/1.x non necessarie (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 latenza non necessarie 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 che si trova in HTTP/1.x ed elimina la necessità di più connessioni per abilitare l'elaborazione e la distribuzione in parallelo di richieste e risposte. Di conseguenza, questo rende le nostre applicazioni più veloci, semplici ed economiche da implementare.

Assegnazione delle priorità ai flussi

Una volta che un messaggio HTTP può essere suddiviso in più frame singoli e consentiamo la multiplexazione dei frame di più stream, l'ordine in cui i frame sono interleaving e pubblicati sia dal client che dal server diventa una considerazione fondamentale delle prestazioni. Per facilitare ciò, 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 flusso può essere assegnata una dipendenza esplicita da un altro.

La combinazione di dipendenze e ponderazioni dei flussi consente al client di creare e comunicare un "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, una volta disponibili i dati di risposta, l'allocazione della larghezza di banda per garantire la distribuzione ottimale delle risposte ad alta priorità al client.

Dipendenze e ponderazioni del flusso HTTP/2

Una dipendenza flusso in HTTP/2 viene dichiarata facendo riferimento all'identificatore univoco di un altro flusso come principale. Se l'identificatore viene omesso, il flusso si dice dipenderà dal "flusso principale". La dichiarazione di una dipendenza del flusso indica che, se possibile, le risorse del flusso padre devono essere allocate prima delle sue dipendenze. In altre parole, "Elabora e invia 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 base alla relativa 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 di risorse che ciascuno di questi flussi deve 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 alcuni esempi pratici nell'immagine sopra. Da sinistra a destra:

  1. Né i flussi A né B specificano una dipendenza padre e si dice che dipendano dal "flusso principale" implicito. A ha una ponderazione pari a 12, mentre la ponderazione di B è pari a 4. Pertanto, in base alle ponderazioni proporzionali, il flusso B dovrebbe ricevere un terzo delle risorse allocate per il flusso A.
  2. Il flusso D dipende dal flusso principale; 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; lo stream B dovrebbe ricevere un terzo delle risorse allocate al flusso A.
  4. I flussi D devono 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 devono ricevere l'allocazione proporzionale in base alle relative ponderazioni.

Come mostrano gli esempi precedenti, la combinazione di dipendenze e ponderazioni dei flussi fornisce un linguaggio espressivo per l'assegnazione della priorità alle risorse, che è una funzionalità fondamentale per migliorare le prestazioni di navigazione in cui sono disponibili molti tipi di risorse con dipendenze e ponderazioni diverse. Meglio ancora, 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 cambiare le dipendenze e riallocare le ponderazioni in risposta all'interazione dell'utente e ad altri indicatori.

Una connessione per origine

Con l'implementazione del nuovo meccanismo di framing binario, HTTP/2 non necessita più di più connessioni TCP in parallelo con flussi multiplex; 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 ben controllato dalla congestione. Mi stupisce quanto sia importante e come funzioni. Un'ottima metrica che mi è piaciuta è la frazione di connessioni create che eseguono una sola transazione HTTP (e fa sì che questa transazione sollevi l'intero 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 scende al 25%. Una grande vittoria per la riduzione dei costi generali. (HTTP/2 è disponibile su Firefox, Patrick McManus)

La maggior parte dei trasferimenti HTTP è breve e bursty, mentre TCP è ottimizzato per trasferimenti di dati collettivi di lunga durata. Riutilizzando la stessa connessione, HTTP/2 è in grado di utilizzare in modo più efficiente ogni connessione TCP e di ridurre notevolmente il sovraccarico complessivo del protocollo. Inoltre, l'utilizzo di meno connessioni riduce l'utilizzo di memoria ed elaborazione lungo l'intero percorso di connessione (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 dovrebbe solo ridurre la latenza di rete, ma anche contribuire a migliorare la velocità effettiva e i costi operativi.

Controllo del flusso

Il controllo del flusso è un meccanismo che impedisce al mittente di sovraccaricare il ricevitore con dati che potrebbe non volere o essere in grado di elaborare: il ricevitore potrebbe essere occupato, sotto carico 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 il client vuole mettere in pausa o limitare la pubblicazione dal server per evitare di recuperare e memorizzare il buffering dei dati non necessari. In alternativa, un server proxy potrebbe avere connessioni upstream veloci e lente e, in modo analogo, vuole regolare la velocità con cui il downstream fornisce i dati in modo che corrisponda a quella dell'upstream per controllare l'utilizzo delle risorse e così via.

I requisiti di cui sopra ti ricordano il controllo del flusso TCP? Dovrebbero, poiché il problema è sostanzialmente 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 è sufficientemente 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 stream e connessione:

  • Il controllo del flusso è direzionale. Ogni ricevitore può scegliere di impostare le dimensioni della finestra desiderate per ogni stream e l'intera connessione.
  • Il controllo del flusso è basato sul credito. Ogni ricevitore pubblicizza la 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 incrementata tramite un frame WINDOW_UPDATE inviato dal ricevitore.
  • 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 delle finestre 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 dimensione massima della finestra grande (2^31-1 byte) e gestirla inviando un frame WINDOW_UPDATE ogni volta che vengono ricevuti dati.
  • Il controllo del flusso è hop-by-hop, non end-to-end. In altre parole, un intermediario può utilizzarlo per controllare l'uso delle risorse e implementare meccanismi di allocazione delle risorse in base a criteri ed euristiche propri.

HTTP/2 non specifica alcun algoritmo particolare per l'implementazione del controllo del flusso. Fornisce invece i semplici componenti di base e supporta l'implementazione al client e al server, che possono utilizzarla per implementare strategie personalizzate per regolare l'utilizzo e l'allocazione delle risorse, oltre a 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, 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 che altri recuperi ad alta priorità proseguano e riprendere il recupero una volta completato il caricamento di altre risorse fondamentali.

Push del server

Un'altra nuova e potente funzionalità di HTTP/2 è la capacità del server di inviare più risposte per una singola richiesta client. In altre parole, oltre alla risposta alla richiesta originale, il server può eseguire il push di ulteriori risorse al client (Figura 12-5), senza che il client debba richiederle esplicitamente.

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 è costituita da decine di risorse, che vengono tutte scoperte 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 necessarie per il client; si tratta del push del server.

Infatti, se ti è già capitato di incorporare un CSS, un codice JavaScript o qualsiasi altro asset tramite un URI di dati (vedi Risorse incorporate), puoi già provare in prima persona 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 per il push possono essere:

  • Memorizzata nella cache dal client
  • Riutilizzo in pagine diverse
  • multiplexato insieme ad altre risorse
  • Assegnato come priorità dal 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'intenzione del server di eseguire il push delle risorse descritte al client e devono essere inviate prima dei dati di risposta che richiedono le risorse sottoposte a push. Questo ordine di consegna è fondamentale: il client deve sapere 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 dell'elemento padre (in altre parole, i frame DATA).

Quando il client riceve un frame PUSH_PROMISE, se vuole, può scegliere di rifiutare lo stream (tramite un frame RST_STREAM). (Questo può accadere, 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, un'"ottimizzazione" popolare per HTTP/1.x, equivale a un "push forzato": il client non può disattivare, annullare o elaborare la risorsa incorporata individualmente.

Con HTTP/2, il client mantiene il pieno controllo dell'utilizzo del 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 sottoposta a push è un flusso che, a differenza di una risorsa in linea, consente di essere multiplexato, prioritizzato ed elaborato 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, kilobyte in più se vengono utilizzati cookie HTTP. (Consulta la pagina relativa alla misurazione e il controllo dell'overhead del protocollo). Per ridurre questo sovraccarico 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 singole dimensioni di trasferimento.
  2. Richiede che sia il client sia il server mantengano e aggiornino un elenco indicizzato di campi di intestazione visti 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 programmazione di Huffman consente di comprimere i singoli valori al momento del trasferimento, mentre 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 le chiavi e i 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 in una determinata connessione. Di conseguenza, le dimensioni di ogni richiesta vengono ridotte utilizzando la codifica statica di Huffman per valori mai visti prima e la sostituzione degli indici con 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 comportato una riduzione dall'85% all'88% delle dimensioni dei dati di intestazione trasferiti e un miglioramento significativo della latenza del tempo di caricamento delle pagine:

Per il link DSL a larghezza di banda inferiore, in cui il link di caricamento dura solo 375 Kbps, la compressione dell'intestazione della richiesta in particolare, ha portato a miglioramenti significativi del tempo di caricamento della pagina per alcuni siti (in altre parole, quelli che hanno emesso un numero elevato di richieste di risorse). Abbiamo rilevato una riduzione di 45-1142 ms nel tempo di caricamento della pagina semplicemente dovuto alla compressione dell'intestazione. (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 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, ovviamente, consentire una buona compressione dei metadati delle intestazioni HTTP.

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

Per approfondire