HTTP 0.9 e 1.0 - struttura dei messaggi, header e parsing
In questa pagina 7
HTTP (HyperText Transfer Protocol) è il protocollo del livello applicazione del Web (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 →). Questa nota segue le prime due versioni, che sono le più semplici e permettono di capire come si costruiscono e si leggono i messaggi in un programma C: HTTP/0.9 e HTTP/1.0 (RFC 1945). Sono testuali, senza stato, e viaggiano su TCP (porta 80) come visto in Client e server TCP in C - bind, listen, accept e processi concorrentiUn client TCP in C fa getaddrinfo, socket, connect, poi write e read; un server fa socket, setsockopt(SO_REUSEADDR), bind, listen e un ciclo di accept. Un server iterativo serve un client alla volta e un client lento blocca tutti; un server concorrente con fork crea un processo figlio per ogni connessione: il figlio chiude il socket di ascolto e serve la connessione, il padre chiude il socket di connessione e torna ad accept. I figli terminati diventano zombie finche' il padre non li raccoglie con waitpid, di solito in un gestore di SIGCHLD con WNOHANG in un ciclo; i descrittori non chiusi tengono viva la connessione (nessun FIN) o esauriscono EMFILE; uno stato CLOSE_WAIT che si accumula indica una close mancante. Alternative: thread, pre-fork, I/O multiplexing con poll o epoll.Client e server TCP in C - bind, listen, accept e processi concorrenti →.
HTTP/0.9
Nasce nel 1991 con il primo Web (Tim Berners-Lee): serve solo a ottenere documenti HTML. Il protocollo è minimo.
Definizione (Simple-Request e Simple-Response, RFC 1945 par. 4.1).
Simple-Request = "GET" SP Request-URI CRLF;Simple-Response = [ Entity-Body ]. La richiesta è una sola riga; la risposta è soltanto il documento.
Funzionamento: il client apre la connessione TCP, invia GET /pagina.html seguito da CRLF, il server risponde con il contenuto della pagina e chiude la connessione. Lo si può provare a mano (se il server accetta ancora questo formato):
$ printf 'GET /index.html\r\n' | nc 127.0.0.1 8080
<html><body><h1>Ciao</h1></body></html>I limiti sono evidenti:
- il client non può mandare nulla oltre al percorso: niente dati, niente informazioni sul client;
- nessun codice di stato: non si distingue il documento vuoto da un errore, né un documento inesistente da un errore di generazione;
- nessuna autenticazione;
- solo HTML: senza
Content-Typeil client non sa se riceve testo, immagini o altro (RFC 1945 par. 4.1: il formato semplice «impedisce al server di identificare il tipo di contenuto»); - la fine della risposta si capisce solo dalla chiusura della connessione: se la connessione cade a metà, il client non può accorgersene.
Un client C per HTTP/0.9 è il minimo indispensabile: socket, connect, write("GET /pagina\r\n"), read in ciclo fino a 0. Il codice è in Esercizio - Client HTTP 0.9 e 1.0 che scarica una pagina (slide HTTP 0.9 e 1.0). Un server moderno risponde a questo formato solo se ha il supporto esplicito: molti rispondono con un errore.
HTTP/1.0
HTTP/1.0 (RFC 1945, 1996) è la prima versione formalmente standardizzata (documento informativo). Sul funzionamento di base non cambia: una connessione TCP per ogni richiesta, il server chiude dopo la risposta. Introduce:
- header nelle richieste e nelle risposte, per descrivere meglio ciò che viene scambiato;
- codici di stato: una risposta dice se è andata bene;
- nuovi metodi: oltre a
GET,HEADePOST; - contenuti non HTML: immagini, testo, dati binari, con
Content-Type.
La richiesta diventa una Full-Request e la risposta una Full-Response. Il formato generale dei messaggi (RFC 1945 par. 4.1):
Full-Request = Request-Line
*( General-Header | Request-Header | Entity-Header )
CRLF
[ Entity-Body ]
Full-Response = Status-Line
*( General-Header | Response-Header | Entity-Header )
CRLF
[ Entity-Body ]Un messaggio HTTP è quindi: una riga iniziale, zero o più header (uno per riga), una riga vuota (solo CRLF) che separa gli header dal corpo, e l'eventuale corpo (entity body). Ogni riga termina con CRLF (\r\n, byte 0x0D 0x0A).
La richiesta
GET /index.html HTTP/1.0
User-Agent: client10/1.0
From: studente@example.com
If-Modified-Since: Sun, 11 Oct 2026 20:00:25 GMTRequest-Line: Method SP Request-URI SP HTTP-Version CRLF, tre campi separati da un solo spazio.
| metodo | significato | corpo nella richiesta |
|---|---|---|
GET |
richiede la risorsa | no |
HEAD |
come GET ma la risposta contiene solo gli header, nessun corpo |
no |
POST |
invia dati al server (un modulo, un file) perché li elabori | sì |
HEAD serve a conoscere dimensione, tipo e data di modifica senza scaricare il corpo, o a controllare che un link esista. Il Request-URI è di norma il percorso assoluto (/index.html, con l'eventuale query ?a=1); la sintassi degli URI è in 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 →.
La risposta
HTTP/1.0 200 OK
Date: Sun, 11 Oct 2026 20:05:00 GMT
Server: mini-http/1.0
Last-Modified: Sun, 11 Oct 2026 20:00:25 GMT
Content-Type: text/html
Content-Length: 40
<html><body><h1>Ciao</h1></body></html>Status-Line: HTTP-Version SP Status-Code SP Reason-Phrase CRLF. Il codice (3 cifre) è quello che il programma legge; la frase è solo testo per le persone. Il corpo qui è di 40 byte (39 caratteri più il \n finale), e Content-Length lo dice.
Le categorie dei codici (la prima cifra) sono cinque:
| classe | significato | esempi in HTTP/1.0 |
|---|---|---|
| 1xx | informativo (non usato in HTTP/1.0) | |
| 2xx | successo | 200 OK, 201 Created, 202 Accepted, 204 No Content |
| 3xx | reindirizzamento: servono altre azioni | 301 Moved Permanently, 302 Moved Temporarily, 304 Not Modified |
| 4xx | errore del client | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found |
| 5xx | errore del server | 500 Internal Server Error, 501 Not Implemented, 502 Bad Gateway, 503 Service Unavailable |
Il client deve ragionare sulla classe quando incontra un codice sconosciuto (un 299 si tratta come un 200, un 499 come un 400).
Esempi di risposte non 200:
HTTP/1.0 301 Moved Permanently
Location: http://www.example.com/nuova.htmlHTTP/1.0 304 Not Modified
Date: Sun, 11 Oct 2026 20:06:00 GMTHTTP/1.0 404 Not Found
Content-Type: text/html
Content-Length: 59
<html><body><h1>404 - pagina non trovata</h1></body></html>Il 304 non ha mai un corpo: dice che la copia in possesso del client è ancora valida (vedi sotto).
Gli header
Un header è Nome: valore seguito da CRLF. I nomi non distinguono maiuscole e minuscole (content-length è uguale a Content-Length). In HTTP/1.0 gli header si dividono in quattro categorie:
| categoria | applicati a | header (RFC 1945) |
|---|---|---|
| generali | richieste e risposte | Date (data del messaggio), Pragma (no-cache: non usare copie in cache) |
| di richiesta | richieste | User-Agent (software del client), Referer (URI della pagina di provenienza), From (e-mail di chi richiede), Authorization (credenziali), If-Modified-Since (GET condizionale) |
| di risposta | risposte | Location (dove si trova la nuova risorsa, per i reindirizzamenti), Server (software del server), WWW-Authenticate (sfida di autenticazione) |
| di entità | descrivono il corpo | Allow, Content-Encoding, Content-Length (byte del corpo), Content-Type, Expires, Last-Modified |
La data deve avere un formato fisso e sempre in GMT (RFC 1945 par. 3.3): Sun, 06 Nov 1994 08:49:37 GMT è il formato preferito (derivato dalla RFC 1123); un ricevente deve accettare anche i formati obsoleti Sunday, 06-Nov-94 08:49:37 GMT e Sun Nov 6 08:49:37 1994.
In una richiesta valida di HTTP/1.0 gli header sono facoltativi. L'header Host, che dice a quale sito della macchina ci si rivolge, non fa parte di HTTP/1.0 ma quasi tutti i server di oggi lo pretendono (è obbligatorio in 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 →); un client scritto per un server reale lo manda sempre.
Dove finisce il corpo
Il punto più delicato di HTTP/1.0 è sapere quanti byte compongono il corpo:
- richieste
GETeHEAD: non hanno corpo; la richiesta termina con la riga vuota; - richieste
POST: il corpo c'è e la sua lunghezza deve essere dichiarata conContent-Length(RFC 1945 par. 8.3: «unContent-Lengthvalido è obbligatorio per tutte le POST»; un server che non riesce a determinarla risponde400); - risposte: il server chiude la connessione quando ha finito; il client legge fino a quando
readrestituisce0. IlContent-Lengthè facoltativo ma, se c'è, permette di accorgersi di una risposta troncata (sereadrestituisce0prima di aver ricevutoContent-Lengthbyte, la risposta è incompleta).
Esempio di POST con un modulo:
POST /iscrizione HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 17
nome=Mario&eta=30nome=Mario&eta=30 ha 17 caratteri, e Content-Length: 17 permette al server di sapere quando smettere di leggere senza attendere la chiusura della connessione (che il client non può fare, perché dopo la richiesta attende la risposta).
GET condizionale
Per non riscaricare ciò che il client ha già, il client memorizza la risposta insieme alla data Last-Modified e alla richiesta successiva aggiunge If-Modified-Since con quella data. Se la risorsa non è cambiata il server risponde 304 Not Modified senza corpo; altrimenti 200 con il corpo nuovo.
1a richiesta: GET /index.html
200 OK, Last-Modified: Sun, 11 Oct 2026 20:00:25 GMT, corpo (il client salva data e corpo)
2a richiesta: GET /index.html + If-Modified-Since: Sun, 11 Oct 2026 20:00:25 GMT
304 Not Modified (nessun corpo: si usa la copia salvata)
3a richiesta (file modificato): 200 OK + corpo nuovo + Last-Modified nuovoLa data va rispedita così com'è (come stringa): non serve convertirla. L'esercizio Esercizio - GET condizionale con If-Modified-Since e cache su file (slide HTTP 0.9 e 1.0) implementa proprio questo schema, con la cache in un file.
Provare HTTP/1.0 a mano
Essendo testo, HTTP si prova con nc (netcat) o telnet:
$ printf 'GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n' | nc www.example.com 80
HTTP/1.0 200 OK
...
$ curl -v --http1.0 http://www.example.com/ # mostra richiesta e rispostaPer ottenere l'indirizzo di un server da usare nei programmi: nslookup www.example.com o dig www.example.com (Livello applicazione - DNSIl DNS (Domain Name System) traduce i nomi (www.amazon.com) negli indirizzi IP, perché le persone preferiscono i nomi e i protocolli TCP/IP usano gli indirizzi. È un database distribuito e gerarchico: albero rovesciato con radice, domini di primo livello e sottodomini (al più 128 livelli); le informazioni sono su tanti server (13 server radice) e i nuovi domini si registrano presso un registrar accreditato ICANN. Ogni ISP ha un DNS locale, il cui indirizzo l'host riceve con DHCP: l'host (resolver) gli manda la richiesta, di solito su UDP, e il DNS locale interroga radice, dominio di primo livello e server dell'organizzazione. Ogni server che impara un'associazione la tiene in cache, marcando la risposta non autoritativa, e la scarta dopo il TTL. Record (nome, tipo, valore, classe, TTL). Con il NAT il DNS deve restituire l'indirizzo pubblico. Quattro attacchi: macchina compromessa, risposta falsa all'host, avvelenamento della cache del DNS locale, server DNS malevolo.Livello applicazione - DNS →).
Il parsing dei messaggi
Leggere un messaggio da un socket significa riconoscere le sue parti, un byte dopo l'altro, sapendo che i dati arrivano a pezzi (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 →). L'algoritmo ha tre fasi.
- Riga iniziale. Si legge fino al primo
\n(togliendo il\rche lo precede). In una risposta si estraggono versione, codice, frase consscanf(riga, "HTTP/%d.%d %d", ...); la frase è il resto della riga, dopo il secondo spazio. In una richiesta: metodo, URI, versione. - Header. Una riga alla volta: ogni riga ha la forma
nome: valore. Si cerca il primo:, il nome è quello che precede, il valore è quello che segue senza gli spazi iniziali e finali. Una riga vuota (lunghezza 0 dopo aver tolto il CRLF) segnala la fine degli header. - Corpo. Quello che segue la riga vuota. In HTTP/1.0: fino alla chiusura della connessione per le risposte,
Content-Lengthbyte per le richieste con corpo.
stato 1: leggo la riga iniziale --(CRLF)--> stato 2
stato 2: leggo una riga --(riga vuota)--> stato 3 (corpo)
--(riga "nome: valore")--> si memorizza e resta in stato 2
stato 3: leggo il corpoEsempio di lettura un byte alla volta (come nelle slide del corso): non si rischia mai di leggere oltre la riga vuota, perché ciò che segue è il corpo (o, con le connessioni persistenti, la risposta successiva), ed è per questo che il codice è semplice:
static int read_line(int fd, char *line, size_t max)
{
size_t n = 0;
char c;
while (n + 1 < max) {
if (read(fd, &c, 1) <= 0)
return -1; /* connessione chiusa o errore */
if (c == '\n') {
if (n > 0 && line[n - 1] == '\r') /* si toglie il CR prima del LF */
n--;
line[n] = '\0';
return (int)n;
}
line[n++] = c;
}
return -1; /* riga troppo lunga: si rifiuta */
}Il costo è una system call per byte: va bene per esercizi, nei server veri si usa un buffer di lettura (Esercizio - Server HTTP iterativo con GET, HEAD e codici di errore (sul modello della prova pratica)). Il codice completo di un client che esegue le tre fasi è in Esercizio - Client HTTP 0.9 e 1.0 che scarica una pagina (slide HTTP 0.9 e 1.0); la separazione in funzioni di parsing, con test, è in Esercizio - Parsing di request line, header e URI (sul modello della prova pratica).
Un parser robusto
Le regole che separano un parser che funziona sui propri esempi da uno che funziona con i server veri:
- Terminatori di riga: la specifica dice CRLF, ma un ricevente accetta anche il solo LF (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 → ripete la regola per HTTP/1.1). Il codice sopra lo fa.
- Spazi nei valori: la sintassi di RFC 1945 prevede
:seguito da uno spazio, ma le versioni successive (RFC 9110, par. 5.6.3) ammettono un numero qualsiasi di spazi o tabulazioni prima e dopo il valore, e alcuni server non ne mettono. Si legge "dopo i due punti, saltando gli spazi", non "due caratteri dopo i due punti". - Nomi case-insensitive: si confronta con
strcasecmp. - Righe lunghe e troppi header: si impone un limite (un buffer fisso e un numero massimo di header) e si rifiuta il resto, altrimenti si ha un buffer overflow o un consumo di memoria illimitato.
- Header ripetuti: lo stesso nome può comparire più volte (
Set-Cookie); la struttura dati deve ammetterlo. - Controllo di ogni valore di ritorno: una connessione che si chiude a metà degli header è un errore, non una risposta valida.
- Dati non fidati:
Content-Lengthva convertito constrtolcontrollando che sia un numero non negativo; il valore non si usa mai per dimensionare un array senza un limite.
Nota sul codice delle slide. La lettura degli header che pone il valore a due caratteri dopo il : funziona con la sintassi di RFC 1945 (": ") ma non con un server che omette lo spazio; ed è dimensionata con un buffer fisso da 100000 byte senza controlli. Va bene per capire il meccanismo, non per un programma robusto.
Esercizi collegati
- Esercizio - Client HTTP 0.9 e 1.0 che scarica una pagina (slide HTTP 0.9 e 1.0)
- Esercizio - GET condizionale con If-Modified-Since e cache su file (slide HTTP 0.9 e 1.0)
- Esercizio - Parsing di request line, header e URI (sul modello della prova pratica)
- Esercizio - Server HTTP iterativo con GET, HEAD e codici di errore (sul modello della prova pratica)
Errori tipici
- Dimenticare la riga vuota (
\r\n\r\n) alla fine della richiesta: il server aspetta altri header e non risponde mai. - Usare
\nal posto di\r\nnelle richieste scritte da un client. - Leggere la risposta con una sola
reade supporre di avere tutto: servono un ciclo fino a0e un buffer abbastanza grande (o un salvataggio su file). - Stampare il corpo con
printf("%s")senza il'\0', o constrlen, perdendo i byte dopo uno zero (immagini). - Cercare gli header con
strcmp(maiuscole e minuscole contano) invece distrcasecmp. - Credere che
Reason-Phraseabbia significato per il programma: conta solo il codice. - Trattare qualunque risposta come successo senza controllare il codice (
404e500hanno un corpo che è una pagina di errore, non il documento richiesto). - In una
POST, non mandareContent-Lengtho mandarlo sbagliato (un valore troppo piccolo tronca il corpo, uno troppo grande fa attendere il server).
Domande d'esame
1. Elencare le differenze fra HTTP/0.9 e HTTP/1.0 e scrivere un esempio di richiesta e di risposta HTTP/1.0.
Traccia. 0.9: una sola riga GET /percorso, risposta = solo il documento, niente header né codici di stato, solo HTML, connessione chiusa alla fine. 1.0: Request-Line con versione, Status-Line con codice, header generali, di richiesta, di risposta e di entità, metodi HEAD e POST, contenuti di qualsiasi tipo (Content-Type), Content-Length; ancora una connessione per richiesta. Esempio: GET /index.html HTTP/1.0 + riga vuota; HTTP/1.0 200 OK, Content-Type: text/html, Content-Length: 40, riga vuota, 40 byte di corpo.
2. Come fa un client HTTP/1.0 a capire dove finisce la risposta? E un server dove finisce una richiesta POST?
Traccia. Il client legge il corpo finché il server chiude la connessione (read restituisce 0); se c'è Content-Length può verificare di non aver ricevuto una risposta troncata. Una POST ha un corpo che il client non può delimitare chiudendo (aspetta la risposta): per questo Content-Length è obbligatorio e il server legge esattamente quei byte; GET e HEAD finiscono con la riga vuota.
3. Un client invia GET /a.html HTTP/1.0 con If-Modified-Since: <data>. Quali risposte possibili e cosa fa il client?
Traccia. 304 Not Modified senza corpo: il client mostra la copia salvata. 200 OK con il corpo nuovo e un nuovo Last-Modified: il client sostituisce la copia. Altri codici (404, 500): il client non tocca la copia e segnala l'errore.
Versione ripasso
- HTTP/0.9 (1991).
Simple-Request = "GET" SP Request-URI CRLF;Simple-Response = [Entity-Body]. Risposta = solo il documento, il server chiude. Nessun header, nessun codice, solo HTML, nessuna autenticazione; il client non può mandare dati. - HTTP/1.0 (RFC 1945). Una connessione TCP per richiesta. Novità: header, codici di stato, metodi
HEADePOST, contenuti non HTML (Content-Type). - Messaggio. Riga iniziale, header
Nome: valore, riga vuota, corpo; righe terminate da CRLF.
GET /index.html HTTP/1.0 HTTP/1.0 200 OK
User-Agent: client10/1.0 Content-Type: text/html
If-Modified-Since: <data> Content-Length: 40
<40 byte di corpo>- Request-Line:
Method SP Request-URI SP HTTP-Version.GETrichiede;HEADsolo header;POSTinvia dati con corpo. Status-Line: versione, codice (3 cifre), frase (solo per le persone). Classi: 1xx info (non in 1.0), 2xx successo (200, 201, 204), 3xx reindirizzamento (301, 302, 304), 4xx errore client (400, 401, 403, 404), 5xx errore server (500, 501, 502, 503). Codice sconosciuto: si tratta come la sua classe (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 →). - Header. Nomi case-insensitive. Generali:
Date,Pragma. Richiesta:User-Agent,Referer,From,Authorization,If-Modified-Since. Risposta:Location,Server,WWW-Authenticate. Entità:Allow,Content-Encoding,Content-Length,Content-Type,Expires,Last-Modified. Data:Sun, 06 Nov 1994 08:49:37 GMT(sempre GMT).Hostnon è in 1.0 ma i server lo vogliono. - Fine del corpo.
GET/HEAD: riga vuota.POST:Content-Lengthobbligatorio (altrimenti400), es.nome=Mario&eta=30= 17 byte. Risposta: il server chiude la connessione (read= 0); conContent-Lengthsi rileva il troncamento. - GET condizionale. Si salva
Last-Modifiede il corpo; la volta dopoIf-Modified-Since: <stessa stringa>.304senza corpo = si usa la copia;200= si sostituisce (Esercizio - GET condizionale con If-Modified-Since e cache su file (slide HTTP 0.9 e 1.0)). - Parsing. (1) riga iniziale fino a
\n(toglie\r);sscanf("HTTP/%d.%d %d"). (2) header riga per riga: primo:, nome prima, valore dopo saltando gli spazi; riga vuota = fine. (3) corpo. Lettura un byte alla volta (nessuna lettura oltre) o con buffer. - Parser robusto. Accettare LF semplice, spazi variabili dopo
:,strcasecmpper i nomi, limiti su righe e numero di header, header ripetuti,strtolperContent-Length, controllare ogniread. - Codice essenziale (le funzioni centrali, senza commenti):
static int read_line(int fd, char *line, size_t max)
{
size_t n = 0;
char c;
while (n + 1 < max) {
ssize_t r = read(fd, &c, 1);
if (r <= 0)
return -1;
if (c == '\n') {
if (n > 0 && line[n - 1] == '\r')
n--;
line[n] = '\0';
return (int)n;
}
line[n++] = c;
}
return -1;
}static int parse_header(char *line, struct header *h)
{
char *colon = strchr(line, ':');
if (colon == NULL || colon == line)
return -1;
*colon = '\0';
char *v = colon + 1;
while (*v == ' ' || *v == '\t')
v++;
size_t len = strlen(v);
while (len > 0 && (v[len - 1] == ' ' || v[len - 1] == '\t'))
v[--len] = '\0';
h->name = strdup(line);
h->value = strdup(v);
return 0;
}- Errori tipici: manca
\r\n\r\nfinale;\ninvece di\r\n; una solaread;printf("%s")su dati binari;strcmpsui nomi; credere che la frase conti; ignorare il codice;POSTsenzaContent-Length.
Esercizi su questo argomento
- Esercizio - Client HTTP 0.9 e 1.0 che scarica una pagina (slide HTTP 0.9 e 1.0)
- Esercizio - Client HTTP 1.1 con Content-Length e connessione persistente (slide HTTP 1.1)
- Esercizio - GET condizionale con If-Modified-Since e cache su file (slide HTTP 0.9 e 1.0)
- Esercizio - Parsing di request line, header e URI (sul modello della prova pratica)
- Esercizio - Server con autenticazione Basic, ETag e GET condizionale (sul modello della prova pratica)
- Esercizio - Server HTTP iterativo con GET, HEAD e codici di errore (sul modello della prova pratica)