HTTP 1.1 - connessioni persistenti, Content-Length e chunked transfer encoding
In questa pagina 12
HTTP/1.1 è l'evoluzione di HTTP/1.0 (HTTP 0.9 e 1.0 - struttura dei messaggi, header e parsingHTTP/0.9 (1991) ha una sola riga di richiesta "GET /percorso" e la risposta e' solo il documento HTML, senza codici di stato ne' header, e la connessione si chiude alla fine; HTTP/1.0 (RFC 1945) aggiunge Request-Line con versione, Status-Line con codice, header (generali, di richiesta, di risposta, di entita'), i metodi HEAD e POST, tipi di contenuto diversi dall'HTML; un messaggio e' riga iniziale, header "Nome: valore" a righe CRLF, riga vuota, corpo; in HTTP/1.0 il corpo della risposta finisce quando il server chiude la connessione, mentre per POST serve Content-Length; il parsing legge la prima riga, poi gli header fino alla riga vuota, poi il corpo; i nomi degli header non distinguono maiuscole e minuscole, e un client robusto accetta LF semplice e spazi variabili.HTTP 0.9 e 1.0 - struttura dei messaggi, header e parsing →) e per quasi vent'anni è stato lo standard del Web. Rimane testuale, senza stato, su TCP. È definito da RFC 2068 (1997), RFC 2616 (1999), poi RFC 7230-7235 (2014) e infine dalle RFC 9110 (HTTP Semantics) e RFC 9112 (HTTP/1.1) del 2022, che sono il riferimento attuale. La sintassi dei messaggi è la stessa di HTTP/1.0; cambiano come si usa la connessione e come si delimita il corpo.
Le novità in sintesi
Connessioni persistenti
In HTTP/1.0 il server chiude la connessione dopo ogni risposta: ogni oggetto costa un nuovo handshake TCP a tre vie e riparte da una finestra di congestione piccola (slow start, 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 →). Una pagina di oggi ha decine di oggetti (fogli di stile, script, immagini).
Definizione (connessione persistente). In HTTP/1.1 la connessione resta aperta dopo la risposta e si riusa per le richieste successive, senza che il client lo chieda. Chi vuole chiuderla dopo il messaggio corrente lo dichiara con l'header
Connection: close, nella richiesta (il server chiude dopo la risposta) o nella risposta (il server chiude e lo dice prima).
| modalità | tempo | calcolo |
|---|---|---|
| connessioni non persistenti (HTTP/1.0) | 600 ms | 2 (N + 1) RTT = 2 * 6 * 50 |
| persistente, richieste in sequenza | 350 ms | (2 + N) RTT = 7 * 50 |
| persistente con pipelining | 150 ms | 3 RTT |
Il riuso evita il consumo di banda e latenza dell'handshake a ogni oggetto, e la finestra TCP, già "calda", non ricomincia da capo. Dettagli:
- Il server può chiudere in qualunque momento (timeout di inattività, limite di richieste): il client deve essere pronto a trovare la connessione chiusa e a riaprirla. Può ritentare automaticamente solo le richieste idempotenti (
GET,HEAD,PUT,DELETE): per unaPOSTripetuta si rischia di duplicare l'operazione (RFC 9112 par. 9.3.1). - In HTTP/1.0 esisteva una estensione non standard,
Connection: keep-alive, usata per chiedere la persistenza; un client HTTP/1.1 non ne ha bisogno. L'header non standardKeep-Alive: timeout=5, max=100informa il client su timeout e numero massimo di richieste. - Pipelining. Il client potrebbe inviare più richieste senza attendere la risposta; le risposte devono tornare nello stesso ordine (RFC 9112 par. 9.3.2). Una risposta lenta blocca tutte le successive (head-of-line blocking) e molti server e intermediari non lo gestiscono bene: i browser lo hanno abbandonato e aprono invece più connessioni parallele per host (di solito 6).
- Si trova il confronto fra le versioni del protocollo (HTTP/2 con il multiplexing, HTTP/3 su QUIC) in WebSocket, QUIC e HTTP-3HTTP e' richiesta e risposta, quindi poco adatto a notifiche e dati in tempo reale (polling, long polling, SSE); WebSocket (RFC 6455) apre con un handshake HTTP/1.1 Upgrade (Sec-WebSocket-Key a 16 byte casuali in Base64, risposta 101 con Sec-WebSocket-Accept = Base64 di SHA-1 di chiave piu' GUID fisso) un canale bidirezionale persistente sulla stessa connessione TCP, con frame di 2-14 byte di intestazione (FIN, opcode, MASK, lunghezza su 7, 16 o 64 bit, chiave di mascheramento obbligatoria dal client al server) e messaggi di testo, binari, close, ping e pong. QUIC (RFC 9000) e' un protocollo di trasporto sopra UDP con TLS 1.3 integrato, flussi indipendenti (niente head-of-line blocking fra flussi), connection ID che permettono di cambiare rete, handshake in 1 RTT e ripresa in 0 RTT; HTTP/3 (RFC 9114) mappa HTTP su QUIC con un flusso per richiesta e QPACK per gli header, ed e' annunciato con Alt-Svc.WebSocket, QUIC e HTTP-3 →.
Cosa cambia in un programma
Con la connessione chiusa dal server il client sapeva che la risposta era finita. Con la connessione persistente non è più così: il client deve sapere da solo dove finisce ogni risposta, altrimenti resta bloccato in read o legge l'inizio della risposta seguente come fosse corpo. Questo problema occupa il resto della nota.
L'header Host e i virtual host
Un solo server (un solo indirizzo IP) può ospitare molti siti (virtual hosting). Il DNS traduce www.example.com e www.example.org nello stesso indirizzo, ma poi la richiesta contiene solo il percorso: senza altro, il server non sa quale sito si intenda. L'header Host lo dice.
GET /index.html HTTP/1.1
Host: www.example.comIn HTTP/1.1 Host è obbligatorio in ogni richiesta: un server deve rispondere 400 Bad Request a una richiesta HTTP/1.1 senza Host (RFC 9112 par. 3.2). Il valore è nome e porta dell'URI di destinazione (Host: www.example.com:8080 se la porta non è quella predefinita). Quando un client parla con un proxy, può scrivere nella request line l'URI assoluto (GET http://www.example.com/index.html HTTP/1.1); il server deve accettare anche questa forma.
Dove finisce un messaggio: la lunghezza del corpo
RFC 9112 par. 6.3 stabilisce, nell'ordine, come si determina la lunghezza del corpo di un messaggio. Per una risposta:
Le conseguenze pratiche per i programmi:
HEADè un tranello. La risposta aHEADcontieneContent-Length(la lunghezza che avrebbe il corpo di unGET), ma non il corpo: un client che aspettaContent-Lengthbyte dopo unaHEADresta bloccato per sempre. Lo stesso per304e204.- Le richieste non sono mai delimitate dalla chiusura: se una
POSTnon dichiara néContent-Lengthnéchunked, il server può rispondere411 Length Required. - Un solo modo vale per messaggio: se arrivano sia
Content-LengthsiaTransfer-Encoding: chunkedconta il secondo; un server o un proxy che "sceglie" in modo diverso dall'altro permette il request smuggling (RFC 9112 par. 11.2): un attaccante nasconde una seconda richiesta dentro il corpo della prima. Per questo si rifiuta o si normalizza il messaggio ambiguo. - Le risposte delimitate dalla chiusura sono ancora lecite ma non permettono di riusare la connessione e non permettono di distinguere una risposta completa da una interrotta dalla rete: un server dovrebbe sempre generare messaggi con lunghezza dichiarata.
Content-Length
L'header Content-Length: n dice quanti byte (ottetti) ha il corpo. Il client, dopo la riga vuota, legge esattamente n byte, non uno di più e non uno di meno: i byte successivi sono l'inizio della risposta seguente sulla stessa connessione.
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 14
Hello, world!Hello, world! più il \n finale sono 14 byte. Il valore va convertito con strtol e controllato (non negativo, non assurdamente grande); Content-Length conta byte, non caratteri: "è" in UTF-8 vale due byte. La lettura corretta è un ciclo che accumula i byte finché sono n (read può restituirne meno: 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 →). Il codice è in Esercizio - Client HTTP 1.1 con Content-Length e connessione persistente (slide HTTP 1.1), che include anche il caso della HEAD.
Il server deve conoscere la lunghezza prima di iniziare a inviare gli header. Per un file statico è la dimensione (fstat); per una risposta generata (output di un programma, risultato di una query, uno stream) non è nota: o si accumula tutto in memoria per misurarlo, o si usa il chunked.
Chunked transfer encoding
Definizione (chunked). Con
Transfer-Encoding: chunkedil corpo è inviato come una sequenza di chunk: ciascuno preceduto dalla propria lunghezza in esadecimale e terminato da CRLF. L'ultimo chunk ha lunghezza zero. Sintassi (RFC 9112 par. 7.1):
chunked-body = *chunk last-chunk trailer-section CRLF
chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
chunk-size = 1*HEXDIG
last-chunk = 1*("0") [ chunk-ext ] CRLFEsempio. Il corpo Il chunked permette di inviare il corpo a pezzi.\n (49 byte) in tre chunk da 11, 20 e 18 byte. In esadecimale 11 = b, 20 = 14, 18 = 12:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
b\r\n
Il chunked \r\n
14\r\n
permette di inviare \r\n
12\r\n
il corpo a pezzi.\n\r\n
0\r\n
\r\nSul filo (i \r\n sono i veri byte 0x0D 0x0A): b CRLF + 11 byte + CRLF, 14 CRLF + 20 byte + CRLF, 12 CRLF + 18 byte + CRLF, 0 CRLF e una riga vuota finale. In totale 71 byte per 49 di dati: ogni chunk costa il numero esadecimale e due CRLF (il corpo decodificato è 49 byte; le lunghezze dei chunk non fanno parte del contenuto).
Punti chiave:
- I dati di un chunk sono esattamente
chunk-sizebyte e possono contenere qualunque cosa, anche CRLF o byte zero. Il decoder conta i byte, non cerca un terminatore. - Dopo i dati ci sono sempre un CRLF, che si legge e si scarta.
- Il chunk di lunghezza zero chiude il corpo; dopo
0 CRLFpuò esserci un trailer (campiNome: valore, annunciati con l'headerTrailer) e poi la riga vuota finale. Il trailer serve a mandare metadati calcolati durante l'invio (un hash, un conteggio). - Estensioni di chunk:
1a;nome=valore CRLFdopo la dimensione; chi non le conosce le ignora. La dimensione in esadecimale può arrivare con cifre maiuscole o minuscole e può avere zeri iniziali. - Ogni ricevente deve saper decodificare il chunked (obbligatorio in HTTP/1.1) e deve proteggersi dall'overflow: una dimensione esadecimale con molte cifre non va convertita con un
int.
I vantaggi: non occorre conoscere la lunghezza in anticipo; si può fare streaming (il client riceve e mostra i dati mentre il server li genera); la connessione resta persistente. Non si usa con i client HTTP/1.0, che non lo capiscono.
Decodifica
read chunk-size, chunk-ext (se c'e'), CRLF
while (chunk-size > 0) {
read chunk-data (esattamente chunk-size byte) e CRLF
accoda chunk-data al contenuto
read chunk-size, chunk-ext, CRLF
}
read le righe di trailer fino alla riga vuotaIn C (da Esercizio - Client HTTP 1.1 con decodifica del chunked (slide HTTP 1.1)):
for (;;) {
rb_line(r, line, sizeof line); /* "1a;ext" oppure "0" */
unsigned long size = strtoul(line, &end, 16); /* esadecimale: si fermano alla ';' o al fine riga */
if (size == 0) break; /* ultimo chunk */
copia_esatti(r, out, size); /* ESATTAMENTE size byte */
rb_line(r, line, sizeof line); /* il CRLF dopo i dati: riga vuota */
}
while (rb_line(r, line, sizeof line) > 0) { /* trailer */ } /* fino alla riga vuota */Nota sul codice delle slide. Il client chunked del corso legge un byte alla volta e riconosce il confine del chunk cercando la prossima coppia CRLF (alterna "ho letto la dimensione" e "ho letto i dati"), senza usare la dimensione dichiarata per contare i byte. Funziona con una risposta i cui dati non contengono CRLF, ma si rompe se un chunk contiene una a-capo \r\n (comune in una pagina HTML), perché quel CRLF verrebbe scambiato per la fine del chunk e la riga successiva letta come dimensione. Il metodo corretto è contare chunk-size byte, come nel codice qui sopra e nell'esercizio.
Content-Encoding e Transfer-Encoding
Sono cose diverse. Content-Encoding: gzip descrive come è codificato il contenuto (la risorsa è compressa, e rimane tale fino al destinatario finale); Transfer-Encoding: chunked descrive come il messaggio è trasferito su questa connessione, e può essere tolto o rimesso da ogni intermediario. Un corpo può essere insieme Content-Encoding: gzip e Transfer-Encoding: chunked.
Nuovi metodi
Un server che riceve un metodo che non conosce risponde 501 Not Implemented; se lo conosce ma non vale per quella risorsa 405 Method Not Allowed con l'header Allow che elenca quelli permessi. Le proprietà di sicurezza e idempotenza (tabella) sono in Livello applicazione - HTTPIl livello applicazione è il più alto della pila: offre servizi all'utente con una connessione logica tra le due applicazioni e riceve servizi solo dal trasporto (DNS, HTTP, e-mail, FTP). Il Web (WWW, nato al CERN nel 1989) è un servizio client-server distribuito di pagine collegate da ipertesti; ogni pagina ha un URL protocollo://host:porta/percorso. HTTP: il client manda una richiesta, il server una risposta, su TCP (server sulla porta 80, client su una porta temporanea); senza stato. Messaggi di testo (riga di richiesta o di stato, intestazioni, riga vuota, corpo), metodi GET, POST, HEAD, PUT, DELETE, codici di stato 2xx-5xx. Una pagina con N oggetti incorporati richiede 2(N+1) RTT con connessioni non persistenti e (N+2) RTT con connessione persistente (trascurando la trasmissione). I cookie danno memoria al protocollo: Set-Cookie nella risposta, Cookie nelle richieste, file nel browser e base di dati nel sito. Un proxy (web cache) tiene le copie delle risposte recenti: meno carico sul server, meno traffico, meno ritardo.Livello applicazione - HTTP →.
100 Continue
Un client che deve inviare un corpo grande (un caricamento) può chiedere prima al server se lo accetterebbe: manda gli header con Expect: 100-continue e aspetta. Il server risponde HTTP/1.1 100 Continue (poi il client invia il corpo e arriva la risposta finale) oppure subito un errore (401, 413, 417), risparmiando al client di inviare dati inutili. Le risposte 1xx sono provvisorie: dopo di esse arriva sempre una risposta finale.
Richieste di intervalli
Con Range: bytes=1000-1999 il client chiede solo un pezzo della risorsa; il server risponde 206 Partial Content con Content-Range: bytes 1000-1999/5000 (e Content-Length: 1000) e il solo intervallo. Un server dichiara di supportarlo con Accept-Ranges: bytes. Serve a riprendere un download interrotto (con Range: bytes=<già ricevuti>-) o a leggere solo una parte di un file (video).
Chiusura, errori e sicurezza
- Chiusura. Chi vuole chiudere dopo la risposta lo annuncia con
Connection: closee poi chiude. Un server che chiude una connessione inattiva può farlo senza avviso: se il client sta inviando proprio in quel momento riceve un reset. - Risposta troncata. Con
Content-Lengtho con il chunked, una connessione che si chiude prima della fine è una risposta incompleta e non va usata come se fosse valida. Con il delimitatore "fino alla chiusura" non c'è modo di saperlo. - Response splitting. Se un server inserisce in un header un valore proveniente dall'utente senza togliere CR e LF, un attaccante può chiudere gli header e iniettare una seconda risposta. Nei programmi, i valori degli header si costruiscono solo da dati ripuliti dai caratteri di controllo (Esercizio - Server HTTP che risponde in chunked con trailer (sul modello della prova pratica)).
Esercizi collegati
- Esercizio - Client HTTP 1.1 con Content-Length e connessione persistente (slide HTTP 1.1): più richieste sulla stessa connessione, regole di lunghezza,
HEAD. - Esercizio - Client HTTP 1.1 con decodifica del chunked (slide HTTP 1.1): decodifica corretta, trailer, estensioni.
- Esercizio - Server HTTP che risponde in chunked con trailer (sul modello della prova pratica): invio dei chunk.
- Esercizio - Server HTTP concorrente con fork e connessioni persistenti (sul modello della prova pratica): server con keep-alive e timeout.
Errori tipici
- Aspettare un corpo dopo una risposta a
HEAD,204o304: il programma si blocca. - Leggere
Content-Lengthbyte dopo aver già consumato parte del corpo nel buffer degli header (con una lettura a blocchi i byte del corpo possono essere già in memoria). - Leggere "fino alla chiusura" con una connessione persistente: la
readnon ritorna mai0. - Decodificare il chunked cercando CRLF invece di contare i byte dichiarati, o confondere la dimensione esadecimale con decimale (
b= 11,14= 20, non 14). - Includere nel contenuto le righe con le dimensioni dei chunk.
- Dimenticare il CRLF dopo i dati di un chunk o la riga vuota finale dopo
0 CRLF(il client aspetta e il server non invia più nulla). - Un server HTTP/1.1 che manda
Content-Lengthsbagliato: nella connessione persistente la risposta successiva viene letta male. - Dimenticare che
Hostè obbligatorio, o non rispondere400quando manca. - Scrivere
Connection: keep-alivecome se fosse necessario: in HTTP/1.1 è già il comportamento di default.
Domande d'esame
1. Quali sono i vantaggi delle connessioni persistenti e come cambia la lettura della risposta in un client?
Traccia. Un solo handshake TCP e una sola fase di slow start per più richieste: meno RTT e banda (esempio: pagina con 5 oggetti, RTT 50 ms: 600 ms non persistente, 350 ms persistente). Ma la chiusura non delimita più la risposta: il client deve usare Content-Length o il chunked (e rispettare HEAD, 204, 304); altrimenti la read non termina oppure legge la risposta successiva come corpo.
2. Si riceve Transfer-Encoding: chunked con il corpo 5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n. Qual è il contenuto e quanti byte? Come si decodifica?
Traccia. Chunk di 5 byte (hello) e di 6 byte ( world, con lo spazio iniziale), poi chunk zero: contenuto hello world, 11 byte. Si legge la riga con la dimensione, si converte da esadecimale, si leggono esattamente quei byte e il CRLF che segue, si ripete fino al chunk 0, poi si leggono gli eventuali trailer fino alla riga vuota.
3. In quale ordine si stabilisce la lunghezza del corpo di una risposta HTTP/1.1? Cosa succede per HEAD?
Traccia. (1) HEAD, 1xx, 204, 304: nessun corpo. (2) Transfer-Encoding: chunked: si decodificano i chunk. (3) Content-Length: quei byte. (4) altrimenti fino alla chiusura della connessione. Con Transfer-Encoding e Content-Length insieme vince il primo (e il messaggio è sospetto). La risposta a HEAD ha gli stessi header di una GET, compreso Content-Length, ma nessun corpo.
Versione ripasso
- Versioni. RFC 2068/2616, 7230-7235, oggi RFC 9110 (semantica) e RFC 9112 (HTTP/1.1). Testuale, senza stato, su TCP; stessa sintassi di HTTP/1.0 (HTTP 0.9 e 1.0 - struttura dei messaggi, header e parsingHTTP/0.9 (1991) ha una sola riga di richiesta "GET /percorso" e la risposta e' solo il documento HTML, senza codici di stato ne' header, e la connessione si chiude alla fine; HTTP/1.0 (RFC 1945) aggiunge Request-Line con versione, Status-Line con codice, header (generali, di richiesta, di risposta, di entita'), i metodi HEAD e POST, tipi di contenuto diversi dall'HTML; un messaggio e' riga iniziale, header "Nome: valore" a righe CRLF, riga vuota, corpo; in HTTP/1.0 il corpo della risposta finisce quando il server chiude la connessione, mentre per POST serve Content-Length; il parsing legge la prima riga, poi gli header fino alla riga vuota, poi il corpo; i nomi degli header non distinguono maiuscole e minuscole, e un client robusto accetta LF semplice e spazi variabili.HTTP 0.9 e 1.0 - struttura dei messaggi, header e parsing →).
- Novità. Connessioni persistenti di default;
Hostobbligatorio;Transfer-Encoding: chunked;Cache-Control/ETag/If-None-Match; metodiPUT,DELETE,OPTIONS,TRACE,CONNECT;Expect: 100-continue;Range/206. - Persistenza. La connessione resta aperta; si chiude con
Connection: close(richiesta o risposta). Evita handshake e slow start a ogni oggetto. Esempio RTT 50 ms, 5 oggetti:2(N+1)RTT = 600ms,(2+N)RTT = 350ms, pipelining3RTT = 150ms. Il server può chiudere in ogni momento: si ritentano solo le richieste idempotenti. Pipelining: risposte nello stesso ordine, head-of-line blocking, abbandonato dai browser (6 connessioni parallele). - Host. Virtual hosting (stesso IP, molti siti). Obbligatorio in 1.1: manca =
400. Accettare anche l'URI assoluto nella request line (Livello applicazione - HTTPIl livello applicazione è il più alto della pila: offre servizi all'utente con una connessione logica tra le due applicazioni e riceve servizi solo dal trasporto (DNS, HTTP, e-mail, FTP). Il Web (WWW, nato al CERN nel 1989) è un servizio client-server distribuito di pagine collegate da ipertesti; ogni pagina ha un URL protocollo://host:porta/percorso. HTTP: il client manda una richiesta, il server una risposta, su TCP (server sulla porta 80, client su una porta temporanea); senza stato. Messaggi di testo (riga di richiesta o di stato, intestazioni, riga vuota, corpo), metodi GET, POST, HEAD, PUT, DELETE, codici di stato 2xx-5xx. Una pagina con N oggetti incorporati richiede 2(N+1) RTT con connessioni non persistenti e (N+2) RTT con connessione persistente (trascurando la trasmissione). I cookie danno memoria al protocollo: Set-Cookie nella risposta, Cookie nelle richieste, file nel browser e base di dati nel sito. Un proxy (web cache) tiene le copie delle risposte recenti: meno carico sul server, meno traffico, meno ritardo.Livello applicazione - HTTP →). - Lunghezza del corpo (RFC 9112 par. 6.3, in ordine). (1)
HEAD,1xx,204,304: nessun corpo, anche conContent-Length. (2)2xxaCONNECT: tunnel. (3)Transfer-Encoding+Content-Length: vinceTransfer-Encoding(sospetto: request smuggling). (4) chunked: si decodifica. (5)Content-Lengthnon valido: errore. (6)Content-Lengthvalido: esattamente quei byte. (7) richiesta senza nulla: corpo vuoto. (8) risposta senza nulla: fino alla chiusura (non permette di riusare la connessione). - Content-Length. Byte (non caratteri),
strtolcon controlli, lettura in ciclo di esattamentenbyte; conHEADil corpo manca anche se l'header c'è. Un server deve conoscere la lunghezza prima degli header: file (fstat) oppure si accumula la risposta, oppure chunked. - Chunked.
chunk-sizein esadecimale [;ext] CRLF,chunk-sizebyte di dati (anche CRLF o zeri), CRLF; chunk0; trailer facoltativo (Nome: valore); riga vuota finale. Esempio:b CRLF Il chunked CRLF 14 CRLF permette di inviare CRLF 12 CRLF il corpo a pezzi.\n CRLF 0 CRLF CRLF= 71 byte per 49 di dati. Decodifica: contare i byte dichiarati, non cercare CRLF (il codice delle slide si rompe se un chunk contiene\r\n); ignorare le estensioni; attenzione all'overflow della dimensione. - Decodificatore.
for (;;) { rb_line(r, line, sizeof line);
unsigned long size = strtoul(line, &end, 16); /* si ferma a ';' */
if (size == 0) break;
copia_esatti(r, out, size); /* esattamente size byte */
rb_line(r, line, sizeof line); } /* CRLF dopo i dati */
while (rb_line(r, line, sizeof line) > 0) { } /* trailer fino alla riga vuota */- Content-Encoding vs Transfer-Encoding. Il primo descrive il contenuto (gzip, end-to-end), il secondo il trasferimento sulla connessione (chunked, hop-by-hop).
- Metodi.
PUTsostituisce,DELETEelimina (idempotenti),OPTIONSchiedeAllow,TRACErimanda la richiesta,CONNECTapre un tunnel,PATCHmodifica parziale. Sconosciuto:501; non permesso:405+Allow. - 100-continue / Range.
Expect: 100-continue->100 Continueo errore subito.Range: bytes=a-b->206,Content-Range: bytes a-b/totale,Accept-Ranges: bytes: riprendere un download. - Sicurezza. Request smuggling (TE e CL ambigui), response splitting (CR/LF dai dati dell'utente in un header). Risposta troncata riconoscibile solo con lunghezza dichiarata.
- Codice essenziale (le funzioni centrali, senza commenti):
static long read_chunked(struct rbuf *r, FILE *out)
{
char line[MAX_LINE], buf[4096];
long total = 0;
int nchunks = 0;
for (;;) {
if (rb_line(r, line, sizeof line) < 0)
return -1;
char *end;
errno = 0;
unsigned long size = strtoul(line, &end, 16);
if (end == line || errno == ERANGE || size > (unsigned long)MAX_CHUNK ||
!(*end == '\0' || *end == ';' || *end == ' ' || *end == '\t')) {
fprintf(stderr, "chunk-size non valido: '%s'\n", line);
return -1;
}
if (size == 0)
break;
nchunks++;
fprintf(stderr, "chunk %d: %lu byte\n", nchunks, size);
size_t left = size;
while (left > 0) {
ssize_t k = rb_read(r, buf, left < sizeof buf ? left : sizeof buf);
if (k < 0)
return -1;
fwrite(buf, 1, (size_t)k, out);
left -= (size_t)k;
}
total += (long)size;
if (rb_line(r, line, sizeof line) < 0 || line[0] != '\0') {
fprintf(stderr, "manca il CRLF dopo il chunk\n");
return -1;
}
}
for (;;) {
if (rb_line(r, line, sizeof line) < 0)
return -1;
if (line[0] == '\0')
break;
fprintf(stderr, "trailer: %s\n", line);
}
return total;
}- Errori tipici: aspettare un corpo dopo
HEAD/204/304; leggere fino alla chiusura con connessione persistente; chunk decodificato cercando CRLF o con dimensione decimale; lunghezze dei chunk nel contenuto; manca il CRLF dopo i dati o la riga vuota finale;Content-Lengthsbagliato;Hostmancante senza400.
Esercizi su questo argomento
- Esercizio - Client HTTP 1.1 con Content-Length e connessione persistente (slide HTTP 1.1)
- Esercizio - Client HTTP 1.1 con decodifica del chunked (slide HTTP 1.1)
- Esercizio - Client WebSocket con handshake, SHA-1 e Base64 (sul modello della prova pratica)
- Esercizio - Parsing di request line, header e URI (sul modello della prova pratica)
- Esercizio - Proxy HTTP con filtro degli host (sul modello della prova pratica)
- Esercizio - Server con autenticazione Basic, ETag e GET condizionale (sul modello della prova pratica)
- Esercizio - Server HTTP che risponde in chunked con trailer (sul modello della prova pratica)
- Esercizio - Server HTTP con CGI (sul modello della prova pratica)
- Esercizio - Server HTTP concorrente con fork e connessioni persistenti (sul modello della prova pratica)
- Esercizio - Servizio REST con JSON (sul modello della prova pratica)
Teoria collegata
- Caching, autenticazione, tipi MIME e URI in HTTP
- Client e server TCP in C - bind, listen, accept e processi concorrenti
- HTTP 0.9 e 1.0 - struttura dei messaggi, header e parsing
- Modello ISO-OSI, data plane e control plane, architetture client-server e publish-subscribe
- Protocolli per i servizi multimediali
- System call POSIX, file descriptor e API delle socket
- Web service, XML, JSON, SOAP e REST
- WebSocket, QUIC e HTTP-3