Vai al contenuto
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

Come funziona HTTP: richieste, risposte, header e codici di stato

Leggi uno scambio HTTP reale riga per riga: metodi, URL, header, classi di codici di stato e cookie, provati con curl su un server locale.

Base11 min di letturaDi NotHoly

Che cos'è HTTP

HTTP è il protocollo con cui browser, app e API si scambiano messaggi. RFC 9110 definisce il significato dei messaggi: metodi, codici di stato, campi header e così via. RFC 9112 definisce come appaiono i messaggi HTTP/1.1 in testo semplice su una connessione TCP. La panoramica di MDN è un'introduzione più breve, orientata al browser, e si legge bene insieme alle RFC.

Lo schema è semplice. Un client invia una richiesta e un server restituisce una risposta. Lo scambio lo inizia sempre il client. L'HTTP in chiaro non cifra niente e non ricorda chi sei da una richiesta all'altra. La cifratura arriva da TLS, trattato nel prossimo articolo. La memoria tra le richieste arriva da cookie e token, di cui parliamo più avanti.

Perché conta

La maggior parte dei comportamenti web che studierai passa per HTTP. Un form invia un valore nel corpo, un'API si fida di un header, un redirect porta l'utente altrove e una cache conserva la copia sbagliata di una pagina. Quando in seguito userai un proxy come OWASP ZAP per ispezionare il traffico, vedrai questi stessi messaggi. Se non sai leggere una richiesta grezza, l'output del proxy è difficile da usare.

La forma di un messaggio

Una richiesta HTTP/1.1 è testo semplice in questa forma:

http
GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: curl/8.12.1
Accept: */*

La prima riga è la riga di richiesta: il metodo, il target della richiesta e la versione del protocollo. Ogni riga successiva è un campo header, scritto come name: value. Una riga vuota chiude la sezione degli header. Se il messaggio ha un corpo, viene dopo quella riga vuota. In rete ogni riga termina con CRLF (un ritorno carrello e un avanzamento riga), che gli editor ti nascondono.

Una risposta ha la stessa struttura, con una riga di stato in cima: versione, codice di stato e reason phrase.

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 15

<h1>hello</h1>

Metodi

Il metodo dichiara cosa vuole il client. RFC 9110 definisce GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS e TRACE. PATCH viene da RFC 5789.

Quando leggi il traffico contano due proprietà:

  • I metodi sicuri (GET, HEAD, OPTIONS, TRACE) dovrebbero essere di sola lettura dal punto di vista del client. RFC 9110, sezione 9.2.1.
  • I metodi idempotenti (GET, HEAD, PUT, DELETE, OPTIONS, TRACE) si possono ripetere con lo stesso effetto voluto di una sola richiesta. RFC 9110, sezione 9.2.2. POST non è né sicuro né idempotente.

Queste proprietà sono promesse sul significato. Quello che succede davvero lo decide comunque il server. Un handler GET che cancella un record viola il contratto, e chi fa una revisione di sicurezza cerca proprio quell'errore.

URL e target della richiesta

Prendi l'URL http://127.0.0.1:8000/missing?lang=en#top e dividilo in parti:

  • Schema: http, che dice al client quale protocollo usare.
  • Authority: 127.0.0.1:8000, l'host e la porta opzionale.
  • Path: /missing.
  • Query: lang=en, dopo il ?.
  • Frammento: top, dopo il #.

Il client non invia al server né lo schema né il frammento. La riga di richiesta contiene il path e la query. L'authority va nell'header Host. HTTP/1.1 richiede Host perché molti siti possono condividere un indirizzo IP, e il server usa quell'header per decidere quale sito deve rispondere.

I nomi degli header non distinguono tra maiuscole e minuscole, quindi Content-Type e content-type sono lo stesso campo (RFC 9110, sezione 5.1). Per i valori il discorso cambia: molti distinguono tra maiuscole e minuscole, quindi non dare per scontato il contrario.

Gli header di richiesta che vedrai più spesso:

  • Host: obbligatorio in ogni richiesta HTTP/1.1. RFC 9112, sezione 3.2, dice che un server deve rispondere 400 quando manca.
  • User-Agent: il software client.
  • Accept: i media type che il client sa gestire.
  • Cookie: i cookie che il browser rimanda al server.
  • Authorization: le credenziali, spesso un bearer token.
  • Content-Type e Content-Length: descrivono il corpo, quando c'è.

Gli header di risposta che vedrai più spesso:

  • Content-Type: il media type del corpo, per esempio text/html.
  • Content-Length: la dimensione del corpo in byte.
  • Date: l'orologio del server, in GMT.
  • Server: il nome del software. Molti server lo mostrano. Gli ambienti di produzione spesso lo nascondono.
  • Last-Modified: l'ultima modifica della risorsa da parte del server.
  • Set-Cookie: chiede al browser di salvare un cookie.

Gli header sono dati controllati da chi li invia. Un browser invia quello che invia il suo software, e uno script o un proxy può inviare quello che vuole. X-Forwarded-For è l'esempio classico: lo aggiunge un proxy, ma può inviarlo anche un client. Non fidarti mai di un header solo perché sembra ufficiale.

Codici di stato

Un codice di stato ha tre cifre. La prima indica la classe, come definito in RFC 9110, sezione 15:

Classe Significato Esempi
1xx Informativo. La richiesta è stata ricevuta e l'elaborazione continua 100 Continue
2xx Successo 200 OK, 201 Created, 204 No Content
3xx Reindirizzamento. Il client deve fare un altro passo 301 Moved Permanently, 304 Not Modified
4xx Errore del client. La richiesta ha un problema 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed
5xx Errore del server. Il server non è riuscito a gestire una richiesta valida 500, 501 Not Implemented, 502, 503

Anche quando un client non riconosce un codice specifico, può regolarsi sulla classe. Un 4xx significa che la richiesta va cambiata. Un 5xx significa che la stessa richiesta potrebbe funzionare più tardi.

Due codici si confondono spesso. Un 401 significa che alla richiesta mancano credenziali valide. Un 403 significa che il server ha capito la richiesta e la rifiuta, anche con le credenziali.

La reason phrase, per esempio Not Found, è testo scelto dal server. RFC 9112, sezione 4, dice che i client dovrebbero ignorarla. Basa la logica sul numero, mai sulle parole.

I cookie non fanno parte di RFC 9110. Sono definiti in RFC 6265. Il ciclo funziona così:

  1. Il server invia Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax.
  2. Il browser salva il valore insieme ai suoi attributi.
  3. Nelle richieste successive a un sito corrispondente, il browser invia Cookie: session=abc123.

Un cookie di sessione è un identificatore assegnato dal server. Non è una password e non è riservato all'utente. Chi possiede il valore di solito può agire come quella sessione finché non scade o non viene revocata. HttpOnly tiene il valore lontano dagli script della pagina. Secure lo permette solo su HTTPS. SameSite limita quando il browser lo invia nelle richieste cross-site. Gli articoli sulla sicurezza web tornano su questi attributi.

Laboratorio pratico

Ti servono curl e Python 3. Windows 11 con Git Bash include curl, e la maggior parte dei sistemi Linux e macOS lo ha già. Il server è http.server, integrato in Python. Eseguilo solo sulla tua macchina, in ascolto su 127.0.0.1, e non esporlo mai su una rete.

L'output seguente viene dall'esecuzione di questi comandi su Windows 11 con Git Bash, curl 8.12.1 e Python 3.12.10. Su questa macchina python3 non era nel path di Git Bash, quindi il server è stato avviato con il percorso completo dell'eseguibile di Python 3.12. Usa python3, py -3 o python a seconda del tuo sistema. Date, orari e stringhe di versione saranno diversi. L'indicatore di avanzamento di curl è stato tolto dall'output qui sotto, e i corpi HTML lunghi sono accorciati.

Passo 1: crea il sito e avvia il server

Nel primo terminale:

bash
mkdir httplab && cd httplab && echo '<h1>hello</h1>' > index.html
python3 -m http.server 8000 --bind 127.0.0.1

Lascia aperto questo terminale. Il server scrive una riga di log per richiesta, quindi in questo terminale compare ogni richiesta che invii nei passi successivi.

Passo 2: leggi uno scambio completo

Nel secondo terminale:

bash
curl -v http://127.0.0.1:8000/

Output:

text
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> 
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:16:48 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 
<h1>hello</h1>
* shutting down connection #0

Leggilo in questo ordine:

  • Le righe che iniziano con * sono le note di curl sulla connessione.
  • Le righe che iniziano con > sono quello che curl ha inviato. Curl ha aggiunto da solo Host, User-Agent e Accept.
  • Le righe che iniziano con < sono quello che il server ha restituito.
  • La riga > vuota chiude gli header di richiesta. La riga < vuota chiude gli header di risposta.
  • <h1>hello</h1> è il corpo. Content-Length: 15 corrisponde al file, perché echo ha aggiunto un a capo dopo il tag.
  • HTTP 1.0, assume close after body mostra una discrepanza. Curl ha chiesto HTTP/1.1, ma il server ha risposto con HTTP/1.0. Il server semplice di Python parla HTTP/1.0, quindi curl chiude la connessione dopo aver letto il corpo.

Passo 3: richiedi un file che non esiste

bash
curl -i http://127.0.0.1:8000/missing

Il flag -i stampa gli header di risposta insieme al corpo. Output, con il corpo HTML accorciato:

text
HTTP/1.0 404 File not found
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 335

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 404</p>
        <p>Message: File not found.</p>
        <p>Error code explanation: 404 - Nothing matches the given URI.</p>
    </body>
</html>

Guarda il primo terminale. Ha registrato:

text
127.0.0.1 - - [08/Nov/2026 05:16:49] code 404, message File not found
127.0.0.1 - - [08/Nov/2026 05:16:49] "GET /missing HTTP/1.1" 404 -

Il codice di stato è 404, della classe di errori del client: il path non corrisponde a nessuna risorsa. La reason phrase File not found è una formulazione del server. Il corpo è una pagina HTML generata dal server per l'errore. Qualsiasi stato può avere qualsiasi corpo, quindi leggi entrambi.

La riga di log indica 05:16 e l'header Date indica 08:16. Il log usa l'ora locale e l'header usa GMT, quindi i due differiscono per il fuso orario locale.

Passo 4: invia un metodo che il server non implementa

bash
curl -i -X POST http://127.0.0.1:8000/

Output, con il corpo HTML accorciato:

text
HTTP/1.0 501 Unsupported method ('POST')
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 357

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('POST').</p>
        <p>Error code explanation: 501 - Server does not support this operation.</p>
    </body>
</html>

La riga di log dice code 501, message Unsupported method ('POST'). Il flag -X cambia solo il metodo. Curl ha inviato gli stessi header di prima.

Il server semplice non ha alcun handler POST, quindi 501 Not Implemented è la risposta onesta. RFC 9110, sezione 15.6.2, definisce 501 per un server che non gestisce la funzionalità richiesta. Un'applicazione reale che conosce la risorsa ma rifiuta POST su quella risorsa risponderebbe invece, di norma, 405 Method Not Allowed. Una risposta 405 deve includere un header Allow che elenca i metodi accettati dalla risorsa.

Passo 5: chiedi solo gli header con HEAD

bash
curl -I http://127.0.0.1:8000/index.html

Output:

text
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:50 GMT
Content-type: text/html
Content-Length: 15
Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT

Il flag -I invia una richiesta HEAD. Lo stato e gli header coincidono con quelli restituiti da GET, ma non segue alcun corpo. Content-Length indica ancora 15, perché descrive la rappresentazione che GET restituirebbe. RFC 9110, sezione 9.3.2, definisce questo comportamento. Il log mostra "HEAD /index.html HTTP/1.1" 200 -.

HEAD è utile quando vuoi controllare se un file esiste, quanto è grande o quando è cambiato l'ultima volta, senza scaricarlo.

bash
curl -v -H 'Cookie: session=demo123' http://127.0.0.1:8000/

Le righe rilevanti dell'output:

text
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> Cookie: session=demo123
> 
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:18:28 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 

Qui la riga del corpo è omessa. Il server ha risposto con lo stesso 200 di prima, perché non ha alcuna logica sui cookie e ignora l'header. È il senso dell'esercizio. Il browser invia i cookie, ma cosa significano lo decide il server. Un server che non imposta mai un cookie non farà niente con quello che gli invii.

Passo 7: ferma il server

Premi Ctrl+C nel primo terminale quando hai finito.

Errori comuni

  • Leggere 200 come successo. Un 200 può contenere una pagina di errore. Controlla il corpo e quello che ha fatto l'applicazione.
  • Considerare GET innocuo. "Sicuro" descrive l'intenzione del client. Se un GET cambia i dati, il server è difettoso, e gli attaccanti ci proveranno.
  • Basare la logica sulla reason phrase. I client dovrebbero usare il numero. I server sono liberi di cambiare le parole.
  • Fidarsi degli header del client. Host, User-Agent, Cookie e X-Forwarded-For arrivano tutti dal lato client. Validali sul server.
  • Dare per scontato che HTTP sia privato. L'HTTP in chiaro è leggibile da chiunque si trovi sul percorso tra te e il server. Per questo i siti reali usano HTTPS. Il laboratorio va bene solo perché il traffico non lascia mai 127.0.0.1.
  • Aspettarsi che -X cambi gli header. Cambia solo il metodo. Gli header si aggiungono con -H.

Per andare oltre

  • Leggi RFC 9110, sezione 9 (metodi) e sezione 15 (codici di stato). È lunga, quindi scorri le sezioni che ti servono.
  • Leggi RFC 9112 per il framing dei messaggi: come client e server concordano dove finisce un corpo, compresi Content-Length e Transfer-Encoding.
  • Leggi la panoramica HTTP di MDN per il punto di vista del browser sugli stessi concetti.
  • Poi, che cosa sono HTTPS e TLS mostra lo stesso scambio con la cifratura. Se non l'hai ancora letto, che cos'è una porta spiega la parte :8000 dell'URL.

Un proxy come ZAP mostra questi stessi messaggi, quindi questo articolo fa da base a quelli successivi sulle vulnerabilità web. Anche l'API di NotHoly su api.notholy.com/v1 è un'API HTTP con corpi JSON, servita su TLS, quindi gli header e i codici di stato visti sopra valgono quando la chiami.

Fonti

  1. RFC 9110: HTTP Semantics
  2. RFC 9112: HTTP/1.1
  3. MDN Web Docs: An overview of HTTP
  4. RFC 6265: HTTP State Management Mechanism (cookies)

Altro su Reti e web

Reti e web

Che cos'è TLS? Come HTTPS protegge il traffico

TLS 1.3 dà a HTTPS riservatezza, integrità e autenticazione del server. Impara l'handshake, le catene di certificati e ciò che il lucchetto non dimostra.

Base12 min di lettura