Lezione 2 di 7 · 20 min di lettura
Il primo server
Un server web vero in quindici righe. I pacchetti mist e wisp, il gestore delle richieste, parlare con il server dal browser e da curl, fermarlo, e cosa succede se la porta è già occupata.
Due pacchetti, due mestieri
Per scrivere un server web in Gleam servono due pacchetti, che fanno due lavori diversi:
- mist è il server HTTP: apre la porta, accetta le connessioni, legge i byte che arrivano e li trasforma in un valore
Request, e poi rispedisce indietro laResponsecome testo HTTP. È la parte “idraulica”. - wisp è un framework web: non parla con la rete, ma ti dà gli strumenti per scrivere la parte che decide cosa rispondere. Risposte già pronte (
wisp.ok(),wisp.not_found()…), lettura di moduli e JSON, protezioni di sicurezza, log.
Tu scrivi una funzione che riceve una richiesta e restituisce una risposta; wisp ti aiuta a scriverla, mist la collega al mondo. Dalla cartella exercises (il pacchetto gleam_erlang c’è già dal modulo 7):
gleam add wisp mistgleam add scarica anche le loro dipendenze: una ventina di pacchetti in tutto, tra cui glisten (su cui si appoggia mist per le connessioni TCP) e gleam_http, che hai già aggiunto nella lezione precedente.
Quindici righe
Crea src/hello_server.gleam:
import gleam/erlang/process
import mist
import wisp
import wisp/wisp_mist
pub fn main() -> Nil {
let secret_key_base = wisp.random_string(64)
let assert Ok(_) =
wisp_mist.handler(handle_request, secret_key_base)
|> mist.new
|> mist.port(8000)
|> mist.start
process.sleep_forever()
}
fn handle_request(_request: wisp.Request) -> wisp.Response {
wisp.html_response("<h1>Hello from Gleam!</h1>", 200)
}Partiamo dal fondo, perché è la parte più importante. handle_request è il gestore (handler): una normale funzione che riceve una wisp.Request e restituisce una wisp.Response. Per ora ignora la richiesta (per questo il parametro si chiama _request, lezione 2.1) e risponde sempre con la stessa pagina HTML e il codice 200. wisp.html_response imposta anche l’intestazione content-type: text/html; charset=utf-8.
Poi main, riga per riga:
wisp.random_string(64)genera una chiave segreta casuale. Wisp la usa per firmare i dati che affida al browser, come i cookie: per questo esempio non serve, ma la vuole comunque.wisp_mist.handler(handle_request, chiave)è l’adattatore tra i due pacchetti: trasforma il tuo gestore wisp in un gestore che mist sa usare.mist.newprepara un server con quel gestore,mist.port(8000)sceglie la porta,mist.startlo avvia. Avviare può fallire, quindimist.startrestituisce unResult, che controlliamo conlet assert(lezione 5.4): se il server non parte, non ha senso proseguire.process.sleep_forever()addormentamainper sempre. Ricordi la lezione 7.2? Quandomainfinisce, il programma si spegne con tutti i suoi processi, server compreso. Un server deve restare acceso, e cosìmainnon finisce mai.
Dettagli nerd Cosa vuol dire firmare un dato?
Un server a volte affida delle informazioni al browser, per esempio “questo utente ha fatto l’accesso”, dentro un cookie: un piccolo dato che il browser conserva e rimanda a ogni richiesta. Il problema è che il browser è nelle mani dell’utente, che può cambiare il cookie come vuole.
La firma risolve il problema: il server calcola, a partire dal dato e dalla chiave segreta, una specie di impronta digitale, e la attacca al dato. Quando il cookie torna indietro, il server ricalcola l’impronta: se non coincide, qualcuno l’ha modificato. Senza la chiave non si può produrre un’impronta valida, ed è per questo che deve restare segreta. Una chiave casuale a ogni avvio va bene per provare; in un sito vero si usa sempre la stessa, conservata fuori dal codice, altrimenti a ogni riavvio i cookie firmati prima smettono di valere.
Accendere il server
gleam run -m hello_serverListening on http://127.0.0.1:8000E poi… niente. Il terminale non torna al prompt, e va bene così: il server è acceso, e aspetta. 127.0.0.1 è localhost (lezione precedente), e la riga la stampa mist quando la porta è aperta.
Apri il browser all’indirizzo http://localhost:8000. Vedrai il titolo “Hello from Gleam!”: il browser ha mandato una richiesta GET /, mist l’ha passata al tuo gestore, e la risposta è tornata indietro. Il tuo primo sito web.
Parlare con il server da curl
Il browser è comodo, ma nasconde i dettagli. Per vederli c’è curl, un programma che fa richieste HTTP dal terminale e stampa quello che torna: c’è già su Linux, macOS e Windows 10 e successivi. Il server occupa il primo terminale, quindi aprine un secondo:
curl localhost:8000<h1>Hello from Gleam!</h1>Senza opzioni, curl fa una richiesta GET e stampa solo il corpo. Con -i (include) mostra anche la prima riga e le intestazioni della risposta:
curl -i localhost:8000/anythingHTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 26
date: Mon, 28 Sep 2026 13:42:20 GMT
connection: keep-alive
<h1>Hello from Gleam!</h1>Ecco la risposta HTTP della lezione precedente, costruita dal tuo codice. Il content-type l’ha messo wisp.html_response; content-length, date e connection li aggiunge mist. E nota il percorso: /anything riceve la stessa pagina, perché il gestore non guarda la richiesta. Anche curl -X POST localhost:8000/x (con -X scegli il metodo) risponde uguale. Nella prossima lezione il gestore imparerà a distinguere.
Spegnere il server
Torna nel primo terminale e premi Ctrl+C. È il menu di interruzione della BEAM della lezione 2.6:
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
(l)oaded (v)ersion (k)ill (D)b-tables (d)istributionScrivi a e premi Invio. Il server si spegne, e da quel momento curl localhost:8000 risponde con un errore: non c’è più nessuno ad ascoltare sulla porta 8000.
Ogni volta che cambi il codice, devi spegnere il server e riaccenderlo: gleam run compila e avvia il programma, ma non si accorge dei file che cambiano mentre gira.
La porta è occupata
Una porta può avere un solo programma in ascolto. Se provi ad avviare il server una seconda volta, mentre il primo è ancora acceso (in un altro terminale, o perché ti sei dimenticato di spegnerlo), il secondo non riesce ad aprirla. L’uscita è lunga, perché a lamentarsi sono i supervisori interni di mist (lezione 7.6); la riga che conta è questa:
Failed to start socket listener: EaddrinuseEaddrinuse è il nome in codice del sistema operativo per address in use, “indirizzo già in uso”. Il rimedio è spegnere l’altro server, oppure scegliere un’altra porta con mist.port.
Quiz
Perché main finisce con process.sleep_forever()?
Esercizio · sul tuo computer
Un secondo server
Crea src/second_server.gleam: un server sulla porta 8080 che risponde a qualsiasi richiesta con il testo semplice Server number two, at your service (non HTML). Per il testo semplice c’è wisp.ok(), una risposta 200 già pronta, e wisp.string_body(risposta, testo) per cambiarne il corpo.
Avvialo con gleam run -m second_server, e da un secondo terminale chiedi curl localhost:8080. Cosa stampa? Poi prova curl -i localhost:8080, e guarda che content-type ha messo wisp.ok().
Mostra una soluzione (prima prova da solo!)
import gleam/erlang/process
import mist
import wisp
import wisp/wisp_mist
pub fn main() -> Nil {
let secret_key_base = wisp.random_string(64)
let assert Ok(_) =
wisp_mist.handler(handle_request, secret_key_base)
|> mist.new
|> mist.port(8080)
|> mist.start
process.sleep_forever()
}
fn handle_request(_request: wisp.Request) -> wisp.Response {
wisp.ok()
|> wisp.string_body("Server number two, at your service")
}Con curl -i vedrai content-type: text/plain: è quello che mette wisp.ok(), e string_body cambia il corpo senza toccare le intestazioni. Il server della lezione, sulla porta 8000, può restare acceso nello stesso momento: porte diverse, nessun conflitto.
Ricapitolando
- mist è il server HTTP (connessioni, byte, protocollo); wisp è il framework con cui scrivi le risposte.
gleam add wisp mist. - Un gestore è una funzione
fn(wisp.Request) -> wisp.Response. wisp_mist.handler(gestore, chiave_segreta)lo adatta a mist;mist.new,mist.port,mist.startavviano il server.process.sleep_forever()tiene accesomain, e quindi il programma.wisp.html_response(html, 200)risponde con una pagina;wisp.ok() |> wisp.string_body(testo)con testo semplice.curlfa richieste dal terminale:-imostra le intestazioni,-Xsceglie il metodo,-dmanda dati.- Si spegne con Ctrl+C, poi
a. Dopo ogni modifica al codice, va riavviato. Eaddrinuse: la porta è già occupata da un altro programma.
Nella prossima lezione il server smetterà di dare sempre la stessa risposta: guarderemo percorso e metodo, e impareremo a provare il gestore senza accendere il server.