Esercizio - Client HTTP 0.9 e 1.0 che scarica una pagina (slide HTTP 0.9 e 1.0)
In questa pagina 6
Testo (slide HTTP 0.9 e 1.0 del corso di Reti di Calcolatori, Ing. Informatica UniPD; l'estensione con il salvataggio su file è l'«Esercizio 1» delle slide).
- Scrivere un client HTTP/0.9 in C che si collega a un server sulla porta data, invia
GET /percorsoe stampa quello che riceve fino alla chiusura della connessione. - Scrivere un client HTTP/1.0 che invia una richiesta completa (
Request-Line,Host,User-Agent), legge e analizza la risposta separando status-line, header e corpo, mostra stato e header, e salva il corpo in un file. - Il client 1.0 deve accorgersi se la risposta è stata troncata (confrontando
Content-Lengthcon i byte ricevuti).
Teoria: 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 →, 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 →, 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 →, 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 →.
Idea
Il client HTTP è un client TCP (Esercizio - Echo TCP e UDP, client e server (sul modello della prova pratica)) che parla un protocollo testuale: apre la connessione, scrive la richiesta, legge la risposta.
HTTP/0.9. La richiesta è GET /percorso\r\n; la risposta è solo il documento. Non c'è niente da analizzare: si legge fino a read == 0 (il server chiude).
HTTP/1.0. Richiesta e risposta hanno la struttura riga iniziale, header, riga vuota, corpo (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 →). Il client:
- costruisce la richiesta con
snprintf(ogni riga termina con\r\n, e una riga vuota chiude gli header); - legge la status-line con
read_line(un byte alla volta, fino a\n) ed estrae versione e codice consscanf; - legge gli header riga per riga, finché la riga è vuota, spezzando ogni riga al primo
:e togliendo gli spazi; - legge il corpo fino alla chiusura della connessione e lo scrive su file o su
stdout; - confronta, se c'è,
Content-Lengthcon i byte ricevuti.
La lettura un byte alla volta è lenta ma sicura: non si legge mai oltre la riga vuota, quindi tutto ciò che resta nel socket è il corpo. (Un server o una connessione persistente richiedono un buffer di lettura: Esercizio - Server HTTP iterativo con GET, HEAD e codici di errore (sul modello della prova pratica).)
Codice
Client HTTP/0.9
/* client09.c - client HTTP/0.9: una sola riga di richiesta, risposta = solo il documento.
* Compilare: gcc -Wall -Wextra -o client09 client09.c
* Usare: ./client09 host porta percorso es. ./client09 127.0.0.1 8000 /index.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/* Risolve host:porta con getaddrinfo (IPv4 o IPv6) e si connette al primo indirizzo che risponde. */
static int connect_to(const char *host, const char *port)
{
struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /* IPv4 o IPv6, come capita */
hints.ai_socktype = SOCK_STREAM; /* stream = TCP */
int rc = getaddrinfo(host, port, &hints, &res);
if (rc != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
return -1;
}
int fd = -1;
for (p = res; p != NULL; p = p->ai_next) {
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (fd < 0)
continue;
if (connect(fd, p->ai_addr, p->ai_addrlen) == 0)
break; /* connesso */
close(fd);
fd = -1;
}
freeaddrinfo(res);
if (fd < 0)
fprintf(stderr, "connect a %s:%s fallita\n", host, port);
return fd;
}
/* write puo' scrivere meno byte del richiesto: si ripete finche' non e' tutto inviato. */
static int write_all(int fd, const char *buf, size_t n)
{
while (n > 0) {
ssize_t w = write(fd, buf, n);
if (w < 0) {
if (errno == EINTR)
continue;
return -1;
}
buf += w;
n -= (size_t)w;
}
return 0;
}
int main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "uso: %s host porta percorso\n", argv[0]);
return 1;
}
int fd = connect_to(argv[1], argv[2]);
if (fd < 0)
return 1;
/* La richiesta HTTP/0.9 e' SOLO "GET percorso" + CRLF: niente versione, niente header. */
char req[1024];
int n = snprintf(req, sizeof req, "GET %s\r\n", argv[3]);
if (n < 0 || (size_t)n >= sizeof req) {
fprintf(stderr, "percorso troppo lungo\n");
return 1;
}
if (write_all(fd, req, (size_t)n) < 0) {
perror("write");
return 1;
}
/* La risposta e' il documento e basta; finisce quando il server chiude (read restituisce 0). */
char buf[4096];
ssize_t r;
size_t tot = 0;
while ((r = read(fd, buf, sizeof buf)) > 0) {
fwrite(buf, 1, (size_t)r, stdout);
tot += (size_t)r;
}
if (r < 0)
perror("read");
fprintf(stderr, "\n[%zu byte ricevuti]\n", tot);
close(fd);
return 0;
}Client HTTP/1.0
/* client10.c - client HTTP/1.0: GET con status-line, header e corpo; salva il corpo su file.
* Compilare: gcc -Wall -Wextra -o client10 client10.c
* Usare: ./client10 host porta percorso [file_uscita]
* es. ./client10 www.example.com 80 / example.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#define MAX_HEADERS 64
#define MAX_LINE 4096
struct header {
char *name; /* "Content-Length" */
char *value; /* "1256" */
};
static int connect_to(const char *host, const char *port)
{
struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int rc = getaddrinfo(host, port, &hints, &res);
if (rc != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
return -1;
}
int fd = -1;
for (p = res; p != NULL; p = p->ai_next) {
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (fd < 0)
continue;
if (connect(fd, p->ai_addr, p->ai_addrlen) == 0)
break;
close(fd);
fd = -1;
}
freeaddrinfo(res);
return fd;
}
static int write_all(int fd, const char *buf, size_t n)
{
while (n > 0) {
ssize_t w = write(fd, buf, n);
if (w < 0) {
if (errno == EINTR)
continue;
return -1;
}
buf += w;
n -= (size_t)w;
}
return 0;
}
/* Legge UNA riga un byte alla volta (cosi' non si legge mai oltre la riga vuota finale:
* quello che segue e' il corpo). Toglie CRLF o LF. Torna la lunghezza, -1 se EOF o riga troppo lunga. */
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;
}
/* Spezza "Nome: valore " in nome e valore (senza spazi ai bordi). Torna 0 se la riga e' valida. */
static int parse_header(char *line, struct header *h)
{
char *colon = strchr(line, ':');
if (colon == NULL || colon == line)
return -1; /* niente ':' oppure nome vuoto */
*colon = '\0';
char *v = colon + 1;
while (*v == ' ' || *v == '\t')
v++; /* salta gli spazi dopo ':' */
size_t len = strlen(v);
while (len > 0 && (v[len - 1] == ' ' || v[len - 1] == '\t'))
v[--len] = '\0'; /* toglie gli spazi in coda */
h->name = strdup(line);
h->value = strdup(v);
return 0;
}
/* I nomi degli header NON distinguono maiuscole e minuscole. */
static const char *header_get(const struct header *hs, int n, const char *name)
{
for (int i = 0; i < n; i++)
if (strcasecmp(hs[i].name, name) == 0)
return hs[i].value;
return NULL;
}
int main(int argc, char **argv)
{
if (argc < 4 || argc > 5) {
fprintf(stderr, "uso: %s host porta percorso [file_uscita]\n", argv[0]);
return 1;
}
const char *host = argv[1], *port = argv[2], *path = argv[3];
int fd = connect_to(host, port);
if (fd < 0) {
fprintf(stderr, "connessione fallita\n");
return 1;
}
/* Full-Request: Request-Line, header, riga vuota. Host non e' obbligatorio in 1.0, ma quasi tutti i server lo vogliono. */
char req[2048];
int n = snprintf(req, sizeof req,
"GET %s HTTP/1.0\r\n"
"Host: %s\r\n"
"User-Agent: client10/1.0\r\n"
"\r\n",
path, host);
if (n < 0 || (size_t)n >= sizeof req) {
fprintf(stderr, "richiesta troppo lunga\n");
return 1;
}
if (write_all(fd, req, (size_t)n) < 0) {
perror("write");
return 1;
}
/* 1) Status-Line: "HTTP/1.0 200 OK" */
char line[MAX_LINE];
if (read_line(fd, line, sizeof line) < 0) {
fprintf(stderr, "nessuna status-line\n");
return 1;
}
int major, minor, status;
if (sscanf(line, "HTTP/%d.%d %d", &major, &minor, &status) != 3) {
fprintf(stderr, "non e' una risposta HTTP/1.x: '%s'\n", line);
return 1;
}
const char *reason = strchr(line, ' ');
reason = reason ? strchr(reason + 1, ' ') : NULL;
printf("status: %d (%s) versione %d.%d\n", status, reason ? reason + 1 : "", major, minor);
/* 2) Header, fino alla riga vuota */
struct header hs[MAX_HEADERS];
int nh = 0;
for (;;) {
int len = read_line(fd, line, sizeof line);
if (len < 0) {
fprintf(stderr, "risposta interrotta negli header\n");
return 1;
}
if (len == 0)
break; /* riga vuota: finiscono gli header */
if (nh < MAX_HEADERS && parse_header(line, &hs[nh]) == 0) {
printf("header: %s = %s\n", hs[nh].name, hs[nh].value);
nh++;
}
}
/* 3) Corpo: in HTTP/1.0 finisce quando il server chiude la connessione (read restituisce 0). */
FILE *out = (argc == 5) ? fopen(argv[4], "wb") : stdout;
if (out == NULL) {
perror("fopen");
return 1;
}
char buf[8192];
ssize_t r;
long total = 0;
while ((r = read(fd, buf, sizeof buf)) > 0) {
fwrite(buf, 1, (size_t)r, out);
total += r;
}
if (out != stdout)
fclose(out);
close(fd);
/* 4) Se c'e' Content-Length si puo' controllare che il corpo non sia stato troncato. */
const char *cl = header_get(hs, nh, "Content-Length");
if (cl != NULL && atol(cl) != total)
fprintf(stderr, "ATTENZIONE: Content-Length=%s ma ricevuti %ld byte (risposta troncata?)\n", cl, total);
fprintf(stderr, "[corpo: %ld byte]\n", total);
for (int i = 0; i < nh; i++) {
free(hs[i].name);
free(hs[i].value);
}
return 0;
}Compilare e provare
Per avere un server a cui collegarsi basta quello di Python (risponde a HTTP/1.0 per default):
$ mkdir www && echo '<html><body><h1>Ciao</h1></body></html>' > www/index.html
$ (cd www && python3 -m http.server 8000 --bind 127.0.0.1) &
$ gcc -Wall -Wextra -o client10 client10.c
$ ./client10 127.0.0.1 8000 /index.html pagina.html
status: 200 (OK) versione 1.0
header: Server = SimpleHTTP/0.6 Python/3.12.10
header: Date = Sun, 11 Oct 2026 20:00:36 GMT
header: Content-type = text/html
header: Content-Length = 40
header: Last-Modified = Sun, 11 Oct 2026 20:00:25 GMT
[corpo: 40 byte]
$ cat pagina.html
<html><body><h1>Ciao</h1></body></html>
$ ./client10 127.0.0.1 8000 /nonesiste
status: 404 (File not found) versione 1.0
...Con un server vero: ./client10 www.example.com 80 / example.html (l'indirizzo si può ottenere con nslookup www.example.com; getaddrinfo lo fa da solo).
Il client 0.9: gcc -Wall -Wextra -o client09 client09.c && ./client09 127.0.0.1 8080 /index.html. Attenzione: molti server non rispondono alla richiesta a una riga. Il server di Python, per esempio, dopo GET /index.html aspetta ancora le righe degli header e il client resta in attesa; funziona invece con il server dell'esercizio Esercizio - Server HTTP iterativo con GET, HEAD e codici di errore (sul modello della prova pratica), che accetta il formato HTTP/0.9. Con nc si vede lo stesso scambio a mano:
$ printf 'GET /index.html\r\n' | nc 127.0.0.1 8080
<html><body><h1>Ciao</h1></body></html>Spiegazione dei punti chiave
connect_to (stessa funzione dei client successivi).
memset(&hints, 0, sizeof hints)ehints.ai_family = AF_UNSPEC,ai_socktype = SOCK_STREAM: IPv4 o IPv6, solo TCP.getaddrinforestituisce una lista; il ciclo prova gli indirizzi uno dopo l'altro finchéconnectriesce;freeaddrinfolibera la lista. Gli errori digetaddrinfosi leggono congai_strerror, non conerrno.- Le slide usano
AF_INETeinet_addr: funziona ma è legato a IPv4 e a un indirizzo numerico. Congetaddrinfoil programma accetta anche il nome del server.
write_all. write può scrivere meno byte del richiesto: il ciclo avanza il puntatore (buf += w) e diminuisce n finché è tutto inviato.
La richiesta HTTP/1.0 (snprintf).
GET /index.html HTTP/1.0\r\n
Host: 127.0.0.1\r\n
User-Agent: client10/1.0\r\n
\r\nsnprintf non scrive mai più di sizeof req byte e restituisce la lunghezza che avrebbe voluto: se è maggiore o uguale a sizeof req la richiesta sarebbe stata troncata. Host non esiste in HTTP/1.0 ma i server veri lo richiedono (ospitano più siti).
read_line.
- Legge un byte per volta in
c; sereadrestituisce<= 0(connessione chiusa o errore) ritorna-1. - Al
\ntoglie un eventuale\rche lo precede (così funziona sia con CRLF sia con LF), termina la stringa e restituisce la lunghezza:0per una riga vuota, che è il segnale "fine degli header". - Se la riga riempie il buffer ritorna
-1: si rifiuta una riga più lunga del limite invece di scrivere oltre il buffer.
La status-line. sscanf(line, "HTTP/%d.%d %d", &major, &minor, &status) deve restituire 3; altrimenti la risposta non è HTTP/1.x (potrebbe essere una risposta 0.9, senza status-line, e il client lo segnala). La reason phrase è il testo dopo il secondo spazio e serve solo per stampare.
parse_header.
strchr(line, ':'): il nome è prima del primo:, il valore dopo. Se non c'è il:o il nome è vuoto la riga non è valida.- Si saltano gli spazi e le tabulazioni dopo il
:e si tolgono quelli in coda: la sintassi di HTTP/1.0 ha un solo spazio dopo i due punti, ma i server veri non sempre lo rispettano, e così il client funziona con tutti. strdupcopia nome e valore perchélineviene riusato per l'header successivo; alla fine si fafree.
header_get confronta i nomi con strcasecmp: i nomi degli header non distinguono maiuscole e minuscole (Content-Length, content-length, Content-length sono lo stesso header; il server di Python manda Content-type).
Il corpo. Il ciclo while ((r = read(fd, buf, sizeof buf)) > 0) copia tutto con fwrite(buf, 1, r, out) (non fputs o printf("%s"): il corpo può contenere byte zero, come un'immagine) e conta i byte. Con fopen(..., "wb") il file è binario. In HTTP/1.0 il corpo finisce quando il server chiude: read restituisce 0.
Il controllo di integrità. Se è presente Content-Length e il totale ricevuto è diverso, la risposta è stata troncata (la connessione è caduta). Senza questo controllo un download interrotto sembra un successo.
Errori tipici
- Dimenticare la riga vuota finale della richiesta (
\r\ndopo l'ultimo header): il server aspetta altri header e non risponde. - Scrivere
\ninvece di\r\nnella richiesta (molti server tollerano, altri no). - Leggere la risposta con una sola
read: i dati arrivano a pezzi. - Usare
printf("%s")ostrlensul corpo: si perde tutto dopo il primo byte zero (immagini, PDF); usarefwritecon la lunghezza. - Cercare l'header con
strcmp: manca il confronto senza distinguere maiuscole e minuscole. - Assumere due caratteri esatti dopo il
:(": "): un server che omette lo spazio fa leggere un valore spostato. - Non controllare il codice di stato: un
404ha comunque un corpo (una pagina di errore) che non è il documento richiesto. - Non controllare le lunghezze: righe lunghe senza limite o troppi header.
- Dimenticare
close(fd)efree.
Varianti per esercitarsi
- Usare il nome del file sulla riga di comando e accettare un URL intero (
http://host:porta/percorso) separando host, porta e percorso: la funzioneparse_urlè in Esercizio - Parsing di request line, header e URI (sul modello della prova pratica). - Seguire un reindirizzamento: con stato
301o302e headerLocation, rifare la richiesta all'indirizzo indicato (al massimo 5 volte). - Mandare un
HEADal posto delGETe stampare solo gli header. - Rendere il client "condizionale": Esercizio - GET condizionale con If-Modified-Since e cache su file (slide HTTP 0.9 e 1.0).
Versione ripasso
- Testo. Client HTTP/0.9 (
GET /percorso\r\n, si legge fino aread == 0); client HTTP/1.0 che inviaRequest-Line+Host+User-Agent, separa status-line, header e corpo, salva il corpo su file, segnala una risposta troncata (Content-Lengthdiverso dai byte ricevuti). - Struttura.
getaddrinfo(IPv4/IPv6) con ciclosocket+connect;write_all(ciclo:writepuò scrivere meno); richiesta consnprintf(controllaren >= sizeof req); lettura della risposta;free/close. - Richiesta HTTP/1.0.
GET /index.html HTTP/1.0\r\nHost: <host>\r\nUser-Agent: client10/1.0\r\n\r\n. La riga vuota chiude gli header.Hostnon è in 1.0 ma i server lo vogliono. read_line(un byte alla volta: nessuna lettura oltre la riga vuota):
while (n + 1 < max) {
if (read(fd, &c, 1) <= 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; /* riga troppo lunga */ Ritorna 0 per la riga vuota = fine degli header.
- Status-line.
sscanf(line, "HTTP/%d.%d %d", &major, &minor, &status) == 3, altrimenti non è HTTP/1.x (forse 0.9). La frase dopo il secondo spazio serve solo da testo. - Header. Primo
:-> nome / valore; saltare gli spazi dopo:e togliere quelli in coda (RFC 1945 prevede un solo spazio, i server veri no);strdupdi nome e valore;header_getconstrcasecmp. - Corpo.
while ((r = read(fd, buf, sizeof buf)) > 0) fwrite(buf, 1, r, out):fwritecon la lunghezza (il corpo può avere byte zero), file aperto"wb"; fine = chiusura del server. Controllo:Content-Lengthpresente e diverso dal totale = risposta troncata. - Prove.
python3 -m http.server 8000come server;./client10 127.0.0.1 8000 /index.html pagina.html->status: 200 (OK) versione 1.0, header,[corpo: 40 byte]. Il server di Python con una richiesta 0.9 aspetta altri header: usare il server dell'esercizio onc. - Codice essenziale (le funzioni centrali, senza commenti):
static int connect_to(const char *host, const char *port)
{
struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int rc = getaddrinfo(host, port, &hints, &res);
if (rc != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
return -1;
}
int fd = -1;
for (p = res; p != NULL; p = p->ai_next) {
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (fd < 0)
continue;
if (connect(fd, p->ai_addr, p->ai_addrlen) == 0)
break;
close(fd);
fd = -1;
}
freeaddrinfo(res);
return fd;
}
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;
}
static const char *header_get(const struct header *hs, int n, const char *name)
{
for (int i = 0; i < n; i++)
if (strcasecmp(hs[i].name, name) == 0)
return hs[i].value;
return NULL;
}- Errori tipici: manca
\r\n\r\nfinale;\nal posto di\r\n; una solaread;printf("%s")/strlensul corpo;strcmpsui nomi degli header;": "supposto fisso; codice di stato ignorato; righe e header senza limiti; mancanocloseefree.