Salta al contenuto
Note per Studenti WebSocket, QUIC e HTTP-3

WebSocket, QUIC e HTTP-3

In questa pagina 6

Questa nota chiude il percorso sulle applicazioni Web con due evoluzioni che superano i limiti del modello richiesta-risposta di HTTP/1.1 (HTTP 1.1 - connessioni persistenti, Content-Length e chunked transfer encodingHTTP/1.1 (oggi RFC 9110 e 9112) rende la connessione persistente di default (si chiude solo con "Connection: close"), rende obbligatorio l'header Host (virtual hosting) e introduce i nuovi metodi PUT, DELETE, OPTIONS, TRACE, Expect: 100-continue, richieste di intervalli (206) e Transfer-Encoding: chunked; con la connessione persistente il client deve sapere dove finisce ogni risposta: lunghezza del corpo nell'ordine HEAD/1xx/204/304 senza corpo, Transfer-Encoding chunked, Content-Length, altrimenti fino alla chiusura; il chunked divide il corpo in blocchi preceduti dalla lunghezza in esadecimale, termina con un chunk 0 e un trailer facoltativo, e si decodifica contando i byte dichiarati (non cercando CRLF).HTTP 1.1 - connessioni persistenti, Content-Length e chunked transfer encoding →): WebSocket, che dà alle applicazioni un canale bidirezionale persistente, e QUIC con HTTP/3, che cambiano il trasporto sotto HTTP per ridurre la latenza.

WebSocket

Perché

In HTTP è sempre il client a parlare per primo: il server non può mandare dati di sua iniziativa (Modello ISO-OSI, data plane e control plane, architetture client-server e publish-subscribeIl modello ISO/OSI (7 livelli) e la pila TCP/IP (4 livelli) dividono la comunicazione in strati; ogni livello riceve una SDU dal livello superiore, aggiunge la sua intestazione e produce una PDU; il confine fra due livelli adiacenti e' il SAP, identificato da un indirizzo (EtherType per Ethernet, numero di protocollo per IP, numero di porta per TCP e UDP) e per un programma C il SAP del trasporto e' l'API delle socket; il data plane inoltra i pacchetti seguendo le tabelle, il control plane le costruisce con i protocolli di instradamento; client/server funziona come una chiamata di funzione (richiesta e risposta), peer-to-peer mette tutti i nodi alla pari, publish/subscribe funziona come un interrupt: un broker inoltra gli eventi agli iscritti, con disaccoppiamento nello spazio, nel tempo e nella sincronizzazione.Modello ISO-OSI, data plane e control plane, architetture client-server e publish-subscribe →: è client/server puro). Per applicazioni che hanno bisogno di notifiche immediate (chat, giochi, quotazioni, editor condivisi) si sono usate varie soluzioni:

tecnica funzionamento limite
polling il client chiede ogni T secondi traffico inutile se non ci sono novità; ritardo fino a T
long polling il server tiene la richiesta aperta finché ha qualcosa da dire una richiesta (con tutti gli header) per ogni evento
Server-Sent Events una risposta text/event-stream che non finisce mai solo dal server al client
WebSocket un canale bidirezionale sulla stessa connessione TCP richiede un server dedicato

L'overhead conta: ogni richiesta HTTP porta con sé centinaia di byte di header (con i cookie anche migliaia), mentre un frame WebSocket ha un'intestazione di 2-14 byte. Con un polling a 1 s con circa 800 byte fra richiesta e risposta (500 + 300) un client consuma 800 byte al secondo anche senza novità; con WebSocket resta un frame di ping ogni tanto.

Handshake

WebSocket (RFC 6455) comincia come HTTP: il client apre una connessione TCP (porta 80 per ws://, 443 per wss://, cioè WebSocket su TLS) e invia una richiesta GET HTTP/1.1 che chiede di cambiare protocollo (Upgrade):

http
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com

Se accetta, il server risponde con 101 Switching Protocols:

http
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Dopo la riga vuota la connessione non è più HTTP: i byte successivi sono frame WebSocket. Gli header:

header significato
Upgrade: websocket, Connection: Upgrade richiesta di passaggio di protocollo
Sec-WebSocket-Key 16 byte casuali codificati in Base64 (24 caratteri), scelti dal client a ogni connessione
Sec-WebSocket-Version: 13 versione del protocollo
Origin sito che ha originato la richiesta (il server può rifiutare origini non permesse)
Sec-WebSocket-Protocol, Sec-WebSocket-Extensions sottoprotocolli ed estensioni negoziati (es. permessage-deflate per la compressione)
Sec-WebSocket-Accept prova che il server ha capito WebSocket (sotto)

Se il server non accetta risponde con un normale errore HTTP (400, 404, 426 Upgrade Required).

Calcolo di Sec-WebSocket-Accept. Il server concatena la chiave del client (come stringa, così com'è) con il GUID fisso 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, calcola il SHA-1 (20 byte) e lo codifica in Base64 (28 caratteri):

chiave  = "dGhlIHNhbXBsZSBub25jZQ=="
cat     = "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
sha1    = b3 7a 4f 2c c0 62 4f 16 90 f6 46 06 cf 38 59 45 b2 be c4 ea
accept  = base64(sha1) = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="

Il client ricalcola il valore e lo confronta: se non coincide chiude. Lo scopo non è la sicurezza (SHA-1 qui è solo un modo per mescolare i dati) ma evitare che un server HTTP o una cache non WebSocket risponda per errore con un messaggio che sembra un'accettazione.

Base64

Base64 (RFC 4648 par. 4) rappresenta byte arbitrari con 64 caratteri stampabili (A-Z a-z 0-9 + /): serve dove il canale accetta solo testo (qui l'header HTTP, ma anche Authorization: Basic e la posta MIME). Si leggono 3 byte (24 bit) alla volta e si dividono in 4 gruppi da 6 bit, ciascuno un indice nell'alfabeto (0-25 A-Z, 26-51 a-z, 52-61 0-9, 62 +, 63 /).

Esempio. Man = 4D 61 6E = 01001101 01100001 01101110 → 010011 010110 000101 101110 = 19, 22, 5, 46 → T W F u: TWFu. Se i byte finali non riempiono un gruppo si aggiunge il riempimento =: due byte (Ma) → TWE=, un byte (M) → TQ==. Quindi la lunghezza codificata è sempre multipla di 4 e vale 4 · ceil(n / 3): 16 byte casuali (la chiave) → 24 caratteri con == finale; i 20 byte di SHA-1 → 28 caratteri con un =. La codifica ingrandisce i dati di un terzo (3 byte → 4 caratteri).

Le funzioni base64_encode e sha1 in C, con test sui valori della RFC, sono in Esercizio - Client WebSocket con handshake, SHA-1 e Base64 (sul modello della prova pratica).

Frame

Dopo l'handshake i dati viaggiano in frame (RFC 6455 par. 5.2):

  byte 0                  byte 1
 +-+-+-+-+-------+     +-+-------------+
 |F|R|R|R| opcode|     |M| lunghezza   |   [lunghezza estesa 2 o 8 byte] [chiave di mascheramento 4 byte]  dati
 |I|S|S|S|  (4)  |     |A|    (7)      |
 |N|V|V|V|       |     |S|             |
 | |1|2|3|       |     |K|             |
 +-+-+-+-+-------+     +-+-------------+
campo significato
FIN (1 bit) 1 = ultimo frame del messaggio
RSV1-3 riservati alle estensioni (0 senza estensioni)
opcode (4 bit) 0x0 continuazione, 0x1 testo (UTF-8), 0x2 binario, 0x8 close, 0x9 ping, 0xA pong
MASK (1 bit) 1 = il payload è mascherato, e segue la chiave di 4 byte
lunghezza (7 bit) 0-125: la lunghezza vera; 126: la lunghezza è nei 2 byte seguenti (big endian); 127: è nei 8 byte seguenti
chiave di mascheramento 4 byte, presente solo se MASK = 1
payload i dati

Quindi l'intestazione occupa 2 byte per payload fino a 125 byte, 4 fino a 65535, 10 oltre; con la maschera, altri 4 byte.

Mascheramento. Tutti i frame dal client al server devono essere mascherati con una chiave casuale di 4 byte nuova per ogni frame: trasformato[i] = originale[i] XOR chiave[i mod 4] (la stessa operazione rimaschera e smaschera). I frame dal server al client non sono mascherati. Un frame non conforme fa chiudere la connessione con errore di protocollo. Il motivo non è la riservatezza (la chiave viaggia accanto ai dati) ma la sicurezza di intermediari come i proxy: senza maschera uno script malevolo potrebbe costruire dati che sembrano una richiesta HTTP e avvelenare una cache.

Gli esempi della RFC 6455 par. 5.7 per il testo Hello:

frame byte
non mascherato (server → client) 81 05 48 65 6c 6c 6f
mascherato (client → server), chiave 37 fa 21 3d 81 85 37 fa 21 3d 7f 9f 4d 51 58
in due frammenti: Hel poi lo 01 03 48 65 6c + 80 02 6c 6f
ping non mascherato con Hello 89 05 48 65 6c 6c 6f
256 byte binari 82 7e 01 00 + dati
65536 byte binari 82 7f 00 00 00 00 00 01 00 00 + dati

Per il primo frame mascherato: 0x81 = FIN 1, opcode 1 (testo); 0x85 = MASK 1, lunghezza 5; poi la chiave; poi 48^37 = 7f, 65^fa = 9f, 6c^21 = 4d, 6c^3d = 51, 6f^37 = 58... (il quarto byte si maschera con chiave[3], il quinto di nuovo con chiave[0]).

Messaggi, frammentazione e controllo

  • Un messaggio può essere diviso in più frame: il primo ha l'opcode del tipo (testo o binario) e FIN = 0, i successivi opcode 0x0 (continuazione), l'ultimo FIN = 1. Serve per inviare dati la cui lunghezza non è nota in anticipo, ed è il corrispondente a livello applicativo del chunked.
  • I frame di controllo (close, ping, pong) hanno payload al massimo di 125 byte, non si frammentano e possono comparire in mezzo ai frammenti di un messaggio.
  • Ping/pong: chi riceve un ping risponde subito con un pong con lo stesso payload; si usa per verificare che il peer sia vivo (keepalive) e per tenere aperti i NAT.
  • Chiusura. Chi vuole chiudere invia un frame close con un codice a 2 byte (e un testo facoltativo); l'altro risponde con un close; poi si chiude la connessione TCP.
codice significato
1000 chiusura normale
1001 l'endpoint se ne va (il server si spegne, la pagina viene chiusa)
1002 errore di protocollo
1003 tipo di dati non accettabile
1006 chiusura anomala (valore riservato: non si trasmette, lo vede l'applicazione quando la TCP cade senza close)
1007 dati incoerenti (testo non UTF-8)
1008 violazione di una policy
1009 messaggio troppo grande
1011 errore interno del server

La sicurezza si cura con wss:// (TLS), il controllo dell'Origin lato server, un limite alla dimensione dei messaggi (codice 1009) e la verifica dell'autenticazione durante l'handshake (le credenziali di un cookie o di un token nell'URL: dopo l'Upgrade non c'è più Authorization per ogni messaggio).

Un client WebSocket completo in C (handshake, frame mascherati, ping/pong, close) è in Esercizio - Client WebSocket con handshake, SHA-1 e Base64 (sul modello della prova pratica). Il Web usa WebSocket come substrato del modello publish/subscribe: il server pubblica eventi su canali a cui il client si è iscritto con un messaggio applicativo (Modello ISO-OSI, data plane e control plane, architetture client-server e publish-subscribeIl modello ISO/OSI (7 livelli) e la pila TCP/IP (4 livelli) dividono la comunicazione in strati; ogni livello riceve una SDU dal livello superiore, aggiunge la sua intestazione e produce una PDU; il confine fra due livelli adiacenti e' il SAP, identificato da un indirizzo (EtherType per Ethernet, numero di protocollo per IP, numero di porta per TCP e UDP) e per un programma C il SAP del trasporto e' l'API delle socket; il data plane inoltra i pacchetti seguendo le tabelle, il control plane le costruisce con i protocolli di instradamento; client/server funziona come una chiamata di funzione (richiesta e risposta), peer-to-peer mette tutti i nodi alla pari, publish/subscribe funziona come un interrupt: un broker inoltra gli eventi agli iscritti, con disaccoppiamento nello spazio, nel tempo e nella sincronizzazione.Modello ISO-OSI, data plane e control plane, architetture client-server e publish-subscribe →).

QUIC

I limiti di TCP per il Web

Anche con HTTP/2 (RFC 9113: un'unica connessione TCP su cui viaggiano molti flussi paralleli, con header compressi) restano problemi che stanno nel trasporto:

Che cos'è QUIC

QUIC (RFC 9000, con TLS in RFC 9001 e recupero delle perdite in RFC 9002) è un protocollo di trasporto sopra UDP, implementato in user space (nelle librerie del browser e del server, non nel kernel), con sicurezza TLS 1.3 integrata e sempre attiva. Vuol dire che dal punto di vista della rete sono datagrammi UDP (di solito sulla porta 443), ma QUIC si assume ciò che UDP non dà: affidabilità, ordine per flusso, controllo di flusso e di congestione (Protocollo UDPUDP (User Datagram Protocol) è il protocollo di trasporto senza connessione e inaffidabile: rispetto a IP aggiunge soltanto la comunicazione processo-processo (numeri di porta) e un controllo d'errore facoltativo. L'intestazione è di soli 8 byte (porta sorgente, porta destinazione, lunghezza, checksum). Il checksum copre pseudo-intestazione (indirizzi IP, protocollo 17, lunghezza), intestazione e dati, ed è il complemento a uno della somma a 16 bit; se vale 0 significa "non calcolato", e un risultato 0 si trasmette come 0xFFFF. UDP non ha connessione, numeri di sequenza, controllo di flusso, di errore né di congestione: si sceglie per i messaggi brevi (DNS, DHCP, RIP, SNMP) e per le applicazioni in tempo reale, dove conta non aggiungere ritardo.Protocollo UDP →, TCP - controllo di congestioneLa congestione nasce quando collegamenti veloci alimentano un collegamento lento: le code dei router si riempiono, i pacchetti si perdono o ritardano e, nel caso peggiore, la rete collassa (quasi solo ritrasmissioni). TCP controlla la propria finestra di congestione cwnd con il feedback delle perdite (timeout o tre ACK duplicati): slow start (cwnd raddoppia a ogni RTT) fino alla soglia ssthresh, poi congestion avoidance (+1 MSS per RTT); a ogni perdita ssthresh = W/2. Le varianti si distinguono per come reagiscono ai tre dupACK: Tahoe riparte da cwnd = 1 dopo la ritrasmissione rapida; Reno usa il fast recovery (ssthresh = cwnd/2, cwnd = ssthresh + 3, +1 per ogni altro dupACK); NewReno gestisce gli ACK parziali e recupera più perdite nella stessa finestra; SACK riscontra i blocchi ricevuti e ritrasmette solo quello che manca.TCP - controllo di congestione →).

caratteristica come funziona
flussi indipendenti una connessione contiene molti stream; una perdita ferma solo lo stream interessato, gli altri proseguono
handshake veloce trasporto e crittografia nello stesso scambio: 1 RTT (e 0 RTT in ripresa)
sempre cifrato quasi tutto il pacchetto è cifrato e autenticato, anche gran parte degli header: gli intermediari non possono interferire
Connection ID la connessione è identificata da un ID, non dalla quaterna IP/porte: cambiare rete non interrompe la connessione (connection migration)
controllo di flusso per stream e per connessione
perdite e congestione numeri di pacchetto sempre crescenti (niente ambiguità fra pacchetto e ritrasmissione), ACK, algoritmi simili a quelli di TCP (NewReno, CUBIC, BBR)
evoluzione essendo in user space si aggiorna con il software, senza attendere i sistemi operativi

Flussi. Ogni stream ha un identificatore a 62 bit; i due bit meno significativi dicono il tipo: bit 0 chi l'ha aperto (0 client, 1 server), bit 1 la direzione (0 bidirezionale, 1 unidirezionale). Quindi gli stream bidirezionali aperti dal client hanno ID 0, 4, 8, 12, ....

Pacchetti. All'inizio (handshake) i pacchetti hanno un'intestazione lunga (tipo Initial, 0-RTT, Handshake, Retry, con versione e Connection ID), poi quella corta (1-RTT). Un pacchetto contiene frame (STREAM con i dati, ACK, CRYPTO per il TLS, MAX_DATA e MAX_STREAM_DATA per il flusso, NEW_CONNECTION_ID, CONNECTION_CLOSE...). Il datagramma UDP con un pacchetto Initial del client deve essere di almeno 1200 byte (con PADDING): serve a limitare gli attacchi di amplificazione (il server non risponde con più dati di quelli ricevuti prima della validazione dell'indirizzo).

Quanto si risparmia

Tempo prima del primo byte della risposta (RTT = 50 ms, trascurando elaborazione e trasmissione; si conta il numero di andata e ritorno):

configurazione scambi prima della richiesta primo byte con RTT = 50 ms
HTTP su TCP (senza TLS) 1 (TCP) 2 RTT 100 ms
HTTPS, TCP + TLS 1.2 1 + 2 4 RTT 200 ms
HTTPS, TCP + TLS 1.3 1 + 1 3 RTT 150 ms
HTTP/3, QUIC 1-RTT 1 (trasporto + TLS) 2 RTT 100 ms
HTTP/3, QUIC 0-RTT (ripresa) 0 1 RTT 50 ms

Con 0-RTT i dati della richiesta partono nel primo volo di pacchetti, usando chiavi di una sessione precedente: a costo di un rischio di replay (un attaccante può ripetere la richiesta), quindi si ammette solo per richieste idempotenti.

HTTP/3

HTTP/3 (RFC 9114) è HTTP trasportato da QUIC. La semantica (metodi, header, codici, cache, Caching, autenticazione, tipi MIME e URI in HTTPLa cache HTTP riusa le risposte senza contattare il server finche' sono fresche (Cache-Control: max-age, Expires; in mancanza una durata euristica pari a circa il 10% del tempo trascorso da Last-Modified) e, quando sono scadute, le rivalida con una richiesta condizionale (If-None-Match con ETag, If-Modified-Since con Last-Modified) alla quale il server risponde 304 senza corpo; no-store vieta di memorizzare, no-cache obbliga a rivalidare, private esclude le cache condivise. L'autenticazione usa 401 con WWW-Authenticate e la risposta Authorization: Basic (base64 di utente:password, solo sopra TLS) o Digest (hash con nonce); 407 vale per i proxy. Content-Type porta il tipo MIME (tipo/sottotipo; parametri come charset e boundary), Accept e gli altri header Accept-* guidano la negoziazione. Un URI (RFC 3986) e' scheme://userinfo@host:porta/percorso?query#frammento, con percent-encoding %HH per i byte non ammessi e regole per risolvere i riferimenti relativi.Caching, autenticazione, tipi MIME e URI in HTTP →) non cambia: cambia come i messaggi viaggiano.

  • Una richiesta, uno stream. Ogni coppia richiesta/risposta usa uno stream bidirezionale aperto dal client. Richieste diverse sono indipendenti: una perdita blocca solo la sua. Il head-of-line blocking del trasporto sparisce.
  • Frame. I messaggi sono composti da frame HTTP/3: HEADERS (tipo 0x01, gli header), DATA (tipo 0x00, il corpo), SETTINGS (0x04, parametri, sul flusso di controllo), GOAWAY (0x07, chiusura ordinata). I messaggi non usano Content-Length o chunked per la delimitazione del corpo, perché lo stream ha già un inizio e una fine.
  • Flusso di controllo. Un flusso unidirezionale per direzione (stream type 0x00) porta i SETTINGS.
  • QPACK (RFC 9204): la compressione degli header, ripensata rispetto all'HPACK di HTTP/2 per funzionare con consegna fuori ordine: la tabella dinamica si aggiorna su flussi dedicati e le richieste dichiarano da quale stato della tabella dipendono.
  • Scoperta. Un client non sa in anticipo che un server parla HTTP/3: lo scopre con l'header di risposta Alt-Svc: h3=":443"; ma=86400 (anche via record DNS HTTPS) ricevuto su una connessione HTTP/1.1 o HTTP/2, o lo prova direttamente. La negoziazione dell'applicazione TLS usa l'identificatore ALPN h3. Se UDP è bloccato dalla rete, il client ripiega su TCP con HTTP/2 o HTTP/1.1.
HTTP/1.1 HTTP/2 HTTP/3
trasporto TCP TCP QUIC su UDP
formato testo binario, frame binario, frame su stream QUIC
richieste in parallelo più connessioni (6 per host) multiplexing su una connessione multiplexing su una connessione QUIC
head-of-line blocking per connessione (pipelining) in TCP, fra tutti i flussi solo nello stesso stream
header non compressi HPACK QPACK
cifratura opzionale (HTTPS) di fatto TLS (i browser) sempre (TLS 1.3 integrato)
avvio connessione TCP + TLS (3-4 RTT) TCP + TLS 1 RTT, 0 RTT in ripresa
cambio di rete connessione persa connessione persa migrazione con Connection ID

Programmare QUIC. Una applicazione non usa connect, read e write su un socket TCP: gli stream sono forniti da una libreria (quiche, ngtcp2, msquic, picoquic) che usa socket UDP con sendto e recvfrom (System call POSIX, file descriptor e API delle socketLe system call sono le funzioni con cui un programma in user space chiede servizi al kernel (open, read, write, close, fork, pipe, dup2, socket, bind, listen, accept, connect); restituiscono -1 e impostano errno in caso di errore; un file descriptor e' un intero che indicizza la tabella dei file aperti del processo (0 stdin, 1 stdout, 2 stderr) e vale per file, pipe e socket; read e write possono trasferire MENO byte del richiesto (e sui socket TCP non c'e' nessun confine fra i messaggi), quindi servono cicli write_all e read_exact; l'API delle socket crea un punto finale di comunicazione (socket), lo lega a indirizzo e porta (bind), lo rende passivo (listen), accetta connessioni (accept) o si connette (connect); getaddrinfo traduce nomi e porte in indirizzi (IPv4 e IPv6); UDP usa sendto e recvfrom e conserva i confini dei datagrammi, TCP e' uno stream affidabile.System call POSIX, file descriptor e API delle socket →) e implementa in user space ritrasmissioni, controllo di flusso, TLS e frame. Il modello di programmazione resta ad eventi: i dati di uno stream arrivano come eventi e si scrivono con chiamate della libreria.

Esercizi collegati

Errori tipici

  • Dimenticare che i frame dal client vanno mascherati, o mascherare quelli del server.
  • Confondere la lunghezza 126/127 con la lunghezza vera: sono indicatori che dicono quanti byte di lunghezza estesa seguono (e sono in big endian).
  • Calcolare Sec-WebSocket-Accept con la chiave decodificata dal Base64 invece che con la stringa, o dimenticare il GUID, o codificare in Base64 l'esadecimale del SHA-1 invece dei 20 byte.
  • Credere che la maschera protegga i dati (non cifra; serve wss://).
  • Trattare un messaggio come un solo frame: può essere frammentato; e dimenticare di rispondere a un ping.
  • Scrivere HTTP/1.1 101 senza riga vuota finale: il client non vede la fine degli header.
  • Credere che QUIC sia "TCP più veloce": è un altro protocollo, sopra UDP, con TLS integrato e flussi indipendenti.
  • Dire che HTTP/3 cambia metodi e codici di stato: cambia solo il trasporto e la rappresentazione dei messaggi.
  • Contare male i tempi di avvio: TCP 1 RTT, TLS 1.2 2 RTT, TLS 1.3 1 RTT, QUIC 1 RTT totale.

Domande d'esame

1. Descrivere l'handshake WebSocket e come si calcola Sec-WebSocket-Accept. Traccia. Il client invia una GET HTTP/1.1 con Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Key (16 byte casuali in Base64) e Sec-WebSocket-Version: 13. Il server risponde 101 Switching Protocols con Upgrade, Connection e Sec-WebSocket-Accept = Base64(SHA-1(chiave + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")). Per la chiave dGhlIHNhbXBsZSBub25jZQ== si ottiene s3pPLMBiTxaQ9kYGzzhZRbK+xOo=. Il client verifica il valore; poi la connessione trasporta frame WebSocket.

2. Decodificare il frame 81 85 37 fa 21 3d 7f 9f 4d 51 58. Traccia. 81: FIN = 1, opcode 1 (testo). 85: MASK = 1, lunghezza 5. Chiave 37 fa 21 3d. Dati mascherati 7f 9f 4d 51 58; in XOR con la chiave 37 fa 21 3d 37: 48 65 6c 6c 6f = Hello.

3. Perché HTTP/3 usa QUIC e non TCP? Elencare tre vantaggi e il tempo prima del primo byte con RTT = 80 ms per HTTPS su TCP+TLS 1.3 e per QUIC 1-RTT. Traccia. TCP consegna in ordine, quindi una perdita blocca tutti i flussi (head-of-line blocking); QUIC ha flussi indipendenti. Vantaggi: handshake con TLS integrato in 1 RTT (0 RTT in ripresa), nessun blocco fra richieste, migrazione fra reti con Connection ID, evoluzione in user space, cifratura sempre attiva. Primo byte: TCP + TLS 1.3 = (1 + 1 + 1) RTT = 3 × 80 = 240 ms; QUIC 1-RTT = 2 RTT = 160 ms.

Versione ripasso

primo byte (RTT = 50 ms) RTT tempo
HTTP su TCP 2 100 ms
TCP + TLS 1.2 4 200 ms
TCP + TLS 1.3 3 150 ms
QUIC 1-RTT 2 100 ms
QUIC 0-RTT 1 50 ms
  • HTTP/3 (RFC 9114). Stessa semantica di HTTP, trasporto QUIC. Una richiesta = uno stream bidirezionale del client; frame HEADERS (0x01), DATA (0x00), SETTINGS (0x04, su flusso di controllo), GOAWAY (0x07); niente Content-Length/chunked per delimitare; QPACK (RFC 9204) per gli header con consegna fuori ordine. Scoperta con Alt-Svc: h3=":443"; ma=86400, ALPN h3; ripiego su TCP se UDP è bloccato.
  • Confronto. HTTP/1.1: testo, più connessioni, HOL per connessione. HTTP/2: binario, multiplexing su TCP, HPACK, HOL in TCP. HTTP/3: QUIC su UDP, HOL solo nello stesso stream, QPACK, sempre cifrato, 1 RTT, migrazione. QUIC si programma con librerie su socket UDP (sendto/recvfrom).
  • Errori tipici: frame del client non mascherati; lunghezza 126/127 presa per la lunghezza vera; Sec-WebSocket-Accept calcolato con la chiave decodificata, senza GUID o con l'esadecimale; maschera creduta cifratura; ping ignorato; riga vuota mancante dopo il 101; QUIC creduto "TCP più veloce"; HTTP/3 creduto un cambio di semantica; tempi di avvio contati male.

Esercizi su questo argomento

Teoria collegata