Lezione 1 di 7 · 20 min di lettura
Come parla il web
Prima di scrivere un server, bisogna sapere che lingua parla. Richieste e risposte HTTP, metodi, percorsi, intestazioni e codici di stato, e il pacchetto gleam_http che li trasforma in normali valori Gleam.
Una domanda e una risposta
Quando scrivi un indirizzo nel browser e premi Invio, succede una cosa molto semplice: il tuo browser manda un messaggio di testo a un altro programma, il server, e il server risponde con un altro messaggio. Il browser legge la risposta e ti mostra la pagina.
Il messaggio che parte si chiama richiesta (request), quello che torna risposta (response). La lingua in cui sono scritti si chiama HTTP, HyperText Transfer Protocol: un protocollo, cioè un insieme di regole su come devono essere fatti i messaggi, così che due programmi scritti da persone diverse, in linguaggi diversi, si capiscano.
In questo modulo scriveremo il programma che sta dall’altra parte: il server. Ma prima guardiamo i messaggi da vicino, perché sono molto meno misteriosi di quanto sembri.
Com’è fatta una richiesta
Ecco una richiesta vera, esattamente come la spedisce curl (un programma che fa richieste dal terminale, lo useremo dalla prossima lezione) quando gli chiedi l’indirizzo http://localhost:8000/books/42?lang=it:
GET /books/42?lang=it HTTP/1.1
Host: localhost:8000
User-Agent: curl/8.18.0
Accept: */*È testo, una riga dopo l’altra. La prima riga dice le cose più importanti:
GETè il metodo: che cosa vuoi fare.GETvuol dire “dammi”./books/42è il percorso (path): quale cosa vuoi, dentro quel server.?lang=itè la query: parametri in più, coppiechiave=valoreseparate da&.HTTP/1.1è la versione del protocollo.
Le righe dopo sono le intestazioni (headers): coppie Nome: valore con informazioni aggiuntive. Host dice a quale sito è rivolta la richiesta, User-Agent chi la sta facendo, Accept che tipo di risposta si accetta (*/*: qualsiasi). Dopo le intestazioni può esserci un corpo (body): i dati che mandi al server, per esempio il contenuto di un modulo compilato. Una richiesta GET di solito non ce l’ha.
Dettagli nerd Cosa sono localhost e :8000? (indirizzi e porte)
Ogni computer in rete ha un indirizzo IP, un numero che lo identifica, come 93.184.215.14. Uno è speciale: 127.0.0.1 vuol dire sempre “questo computer”, e il nome localhost è un soprannome per lui. Quando provi un server sul tuo portatile, browser e server stanno sulla stessa macchina, e si parlano attraverso localhost senza passare da nessuna rete vera.
Su un computer però girano tanti programmi che ascoltano la rete, e l’indirizzo da solo non basta a dire con quale vuoi parlare. Per questo ogni programma si mette in ascolto su una porta, un numero da 1 a 65535: localhost:8000 vuol dire “il programma che ascolta sulla porta 8000 di questo computer”. I siti web usano di solito la porta 80 (per http) e la 443 (per https), ed è per questo che negli indirizzi normali la porta non si scrive: il browser la aggiunge da solo.
I metodi
Il metodo dice al server che intenzioni hai. I più usati sono pochi:
| Metodo | Significato | Esempio |
|---|---|---|
GET | Leggi, senza cambiare niente | Aprire una pagina |
POST | Crea qualcosa, o manda dei dati | Inviare un modulo, pubblicare un commento |
PUT / PATCH | Sostituisci / modifica qualcosa che esiste | Cambiare il titolo di un libro |
DELETE | Cancella | Togliere un libro dallo scaffale |
È una convenzione, non una legge fisica: niente impedisce a un server di cancellare qualcosa quando riceve un GET. Ma è una pessima idea, perché i browser e altri programmi danno per scontato che GET si possa ripetere senza fare danni (per esempio lo rifanno quando ricarichi la pagina).
Com’è fatta una risposta
Ecco la risposta del server che scriveremo nella prossima lezione:
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 26
date: Mon, 28 Sep 2026 13:49:25 GMT
connection: keep-alive
<h1>Hello from Gleam!</h1>Stessa forma: una prima riga, le intestazioni, una riga vuota, e il corpo. La prima riga contiene il codice di stato, un numero di tre cifre che riassume com’è andata: 200 OK, tutto bene. L’intestazione content-type dice che cosa c’è nel corpo (qui: una pagina HTML, in UTF-8), content-length quanti byte è lungo.
I codici di stato sono divisi in famiglie, dalla prima cifra:
| Codice | Famiglia | Quelli che incontrerai |
|---|---|---|
2xx | Tutto bene | 200 OK, 201 Created (creato), 204 No Content (fatto, niente da dire) |
3xx | Vai da un’altra parte | 303 See Other (guarda quest’altro indirizzo) |
4xx | Hai sbagliato tu, client | 400 Bad Request, 404 Not Found, 405 Method Not Allowed |
5xx | Ho sbagliato io, server | 500 Internal Server Error |
Il famoso “errore 404” è proprio questo: il server ha capito la richiesta, ma a quel percorso non c’è niente.
Quiz
Un server riceve una richiesta per /books/99, ma il libro 99 non esiste. Quale codice di stato è giusto restituire?
Il pacchetto gleam_http
In Gleam, richieste e risposte sono normali valori, con un tipo. Li definisce il pacchetto gleam_http, che useranno tutti i pacchetti web del modulo. Dalla cartella exercises:
gleam add gleam_httpIl modulo gleam/http/request sa costruire una richiesta a partire da un indirizzo, con request.to:
import gleam/http
import gleam/http/request
import gleam/io
import gleam/string
pub fn main() -> Nil {
let assert Ok(req) = request.to("http://localhost:8000/books/42?lang=it")
echo req
io.println(http.method_to_string(req.method))
io.println(req.host)
io.println(req.path)
io.println(string.inspect(request.path_segments(req)))
io.println(string.inspect(request.get_query(req)))
}src/read_url.gleam:8
Request(Get, [], "", Http, "localhost", Some(8000), "/books/42", Some("lang=it"))
GET
localhost
/books/42
["books", "42"]
Ok([#("lang", "it")])request.to restituisce un Result, perché non tutte le stringhe sono indirizzi validi. Il record Request ha tutti i pezzi che abbiamo visto, in ordine: il metodo (Get, una variante del tipo http.Method), le intestazioni (una lista di tuple, qui vuota), il corpo (""), lo schema (Http o Https), l’host, la porta (un Option), il percorso e la query.
Due funzioni sono particolarmente comode. request.path_segments spezza il percorso nei suoi pezzi, ["books", "42"]: una lista, su cui potremo fare pattern matching. request.get_query trasforma la query in una lista di coppie, già decodificata.
Costruire richieste e risposte
Le richieste si possono anche costruire pezzo per pezzo, con una catena di tubi. Le risposte stanno nel modulo gleam/http/response, e funzionano allo stesso modo:
import gleam/http
import gleam/http/request
import gleam/http/response
import gleam/io
import gleam/string
pub fn main() -> Nil {
let req =
request.new()
|> request.set_method(http.Post)
|> request.set_path("/books")
|> request.set_header("Content-Type", "application/json")
|> request.set_body("{\"title\": \"Dune\"}")
echo req
let res =
response.new(201)
|> response.set_header("content-type", "text/plain")
|> response.set_body("Created")
echo res
io.println(string.inspect(response.get_header(res, "content-type")))
}src/build_request.gleam:14
Request(Post, [#("content-type", "application/json")], "{\"title\": \"Dune\"}", Https, "localhost", None, "/books", None)
src/build_request.gleam:20
Response(201, [#("content-type", "text/plain")], "Created")
Ok("text/plain")Nota due dettagli. Il nome dell’intestazione è diventato minuscolo: in HTTP i nomi delle intestazioni non distinguono maiuscole e minuscole, e gleam_http li scrive sempre in minuscolo, così get_header(res, "content-type") li trova senza sorprese. E il tipo del corpo è libero: qui è una String, ma Request e Response sono generici (lezione 4.5), Request(body), perché ogni server sceglie come rappresentare il corpo. Lo vedremo con wisp.
Dettagli nerd Cosa c'è sotto HTTP? (TCP)
HTTP dice come sono fatti i messaggi, ma non come viaggiano. Sotto c’è un altro protocollo, TCP, che si occupa di aprire una connessione tra due programmi e di consegnare i byte tutti, in ordine, senza buchi: se un pezzo si perde per strada, TCP lo rispedisce. HTTP si appoggia a una connessione TCP e ci scrive sopra il suo testo.
Aprire una connessione costa un po’ di tempo, per questo HTTP/1.1 la tiene aperta per più richieste di fila: è il connection: keep-alive che hai visto nella risposta. E https è lo stesso HTTP, dentro una connessione cifrata (TLS), così nessuno lungo la strada può leggere o cambiare i messaggi.
Esercizio · sul tuo computer
Il postino
Crea src/postman.gleam. Scrivi una funzione describe(url: String) -> Nil che legge un indirizzo con request.to e stampa l’host, i pezzi del percorso separati da " > " (o / se il percorso è vuoto), e quanti parametri ha la query. Se l’indirizzo non è valido, stampa Invalid URL: seguito dall’indirizzo.
Chiamala su questi quattro indirizzi:
[
"http://localhost:8000/",
"http://localhost:8000/books/42",
"https://example.com/search?q=gleam&page=2",
"not a url",
]Il risultato deve essere:
localhost: / (0 params)
localhost: books > 42 (0 params)
example.com: search (2 params)
Invalid URL: not a urlSuggerimento: request.get_query restituisce un Result; se la query non c’è, conta zero parametri.
Mostra una soluzione (prima prova da solo!)
import gleam/http/request
import gleam/int
import gleam/io
import gleam/list
import gleam/string
pub fn main() -> Nil {
[
"http://localhost:8000/",
"http://localhost:8000/books/42",
"https://example.com/search?q=gleam&page=2",
"not a url",
]
|> list.each(describe)
}
fn describe(url: String) -> Nil {
case request.to(url) {
Ok(req) -> {
let path = case request.path_segments(req) {
[] -> "/"
segments -> string.join(segments, " > ")
}
let params = case request.get_query(req) {
Ok(pairs) -> list.length(pairs)
Error(_) -> 0
}
io.println(
req.host <> ": " <> path <> " (" <> int.to_string(params) <> " params)",
)
}
Error(_) -> io.println("Invalid URL: " <> url)
}
}Ricapitolando
- Il web funziona a richieste e risposte, scritte nel protocollo HTTP: messaggi di testo tra un client (il browser) e un server.
- Una richiesta ha un metodo (
GET,POST,PUT,PATCH,DELETE), un percorso, una query facoltativa, delle intestazioni e un corpo facoltativo. - Una risposta ha un codice di stato (
2xxbene,3xxvai altrove,4xxerrore del client,5xxerrore del server), intestazioni e corpo. localhostè il tuo computer; la porta dice con quale programma parlare.- Il pacchetto
gleam_http(gleam add gleam_http) definisceRequest(body)eResponse(body), e il tipohttp.Method. request.to(url)legge un indirizzo;request.path_segmentserequest.get_querylo spezzano in pezzi comodi.set_method,set_path,set_header,set_bodycostruiscono richieste e risposte; i nomi delle intestazioni diventano minuscoli.
Nella prossima lezione passiamo dall’altra parte: scriveremo un server vero, e ci parleremo con il browser.