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:

output
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. GET vuol dire “dammi”.
  • /books/42 è il percorso (path): quale cosa vuoi, dentro quel server.
  • ?lang=it è la query: parametri in più, coppie chiave=valore separate 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:

MetodoSignificatoEsempio
GETLeggi, senza cambiare nienteAprire una pagina
POSTCrea qualcosa, o manda dei datiInviare un modulo, pubblicare un commento
PUT / PATCHSostituisci / modifica qualcosa che esisteCambiare il titolo di un libro
DELETECancellaTogliere 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:

output
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:

CodiceFamigliaQuelli che incontrerai
2xxTutto bene200 OK, 201 Created (creato), 204 No Content (fatto, niente da dire)
3xxVai da un’altra parte303 See Other (guarda quest’altro indirizzo)
4xxHai sbagliato tu, client400 Bad Request, 404 Not Found, 405 Method Not Allowed
5xxHo sbagliato io, server500 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:

terminale
gleam add gleam_http

Il modulo gleam/http/request sa costruire una richiesta a partire da un indirizzo, con request.to:

src/read_url.gleam
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)))
}
output
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:

src/build_request.gleam
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")))
}
output
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:

gleam
[
  "http://localhost:8000/",
  "http://localhost:8000/books/42",
  "https://example.com/search?q=gleam&page=2",
  "not a url",
]

Il risultato deve essere:

output
localhost: / (0 params)
localhost: books > 42 (0 params)
example.com: search (2 params)
Invalid URL: not a url

Suggerimento: request.get_query restituisce un Result; se la query non c’è, conta zero parametri.

Mostra una soluzione (prima prova da solo!)
src/postman.gleam
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 (2xx bene, 3xx vai altrove, 4xx errore del client, 5xx errore del server), intestazioni e corpo.
  • localhost è il tuo computer; la porta dice con quale programma parlare.
  • Il pacchetto gleam_http (gleam add gleam_http) definisce Request(body) e Response(body), e il tipo http.Method.
  • request.to(url) legge un indirizzo; request.path_segments e request.get_query lo spezzano in pezzi comodi.
  • set_method, set_path, set_header, set_body costruiscono 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.