← Tutti gli episodi

Stagione 94  /  Episodio 2

Il messaggero

La segnalazione, il pezzo che WebRTC lascia a voi: il formato SDP, il modello offerta e risposta, i transceiver, la rinegoziazione e lo schema della negoziazione perfetta per evitare le collisioni.

Note dell'episodio

Bentornati sulle due rive. Oggi parliamo del primo pezzo che il browser non fa per voi: il messaggero.

Nel primo episodio abbiamo detto che, prima di costruire il ponte, le due rive devono mettersi d'accordo sul progetto. Quali codec usare, con quali chiavi cifrare, da quali indirizzi passare. Ma c'è un paradosso: per mettersi d'accordo devono già potersi parlare. E il ponte non c'è ancora.

La soluzione è un messaggero. Una barca a remi, lenta e piccola, che attraversa il fiume portando le lettere. Non trasporta il carico vero, solo i biglietti con il progetto. Nel mondo WebRTC si chiama segnalazione.

La prima cosa da sapere è che la specifica, di proposito, non dice come costruire il messaggero.

All'inizio sembrava una mancanza. In realtà è una scelta molto saggia. Ogni applicazione ha già un suo modo di far comunicare gli utenti: un sistema di chat, una piattaforma di telefonia, un'app con le notifiche, un gioco con il suo server. Imporre un protocollo di segnalazione avrebbe significato costringere tutti a costruirne uno nuovo, accanto a quello che avevano già.

Così WebRTC dice soltanto: ecco i messaggi che le due rive si devono scambiare, fateli arrivare dall'altra parte come preferite. Nella pratica, la scelta più comune è un WebSocket verso un piccolo server che inoltra i messaggi. Ma funzionano anche le richieste HTTP, gli eventi inviati dal server, un sistema di code. Nei primi esperimenti con WebRTC, molti sviluppatori copiavano il testo a mano da una finestra all'altra. Funziona anche così, ed è il modo migliore per capire cosa succede.

C'è una sola regola: il messaggero deve essere affidabile e mantenere l'ordine delle lettere. Se una lettera si perde, il ponte non si costruisce.

Cosa c'è scritto, allora, nelle lettere?

Il formato si chiama SDP, Session Description Protocol, ed è vecchio: nasce alla fine degli anni Novanta per descrivere le sessioni multimediali, e la sua versione attuale è la RFC 8866. È un testo semplice, fatto di righe brevi. Ogni riga comincia con una lettera, un segno di uguale e un valore. La v per la versione, la o per l'origine, la m per i flussi multimediali, la a per gli attributi.

Una descrizione SDP ha una parte iniziale, che riguarda tutta la sessione, e poi una sezione per ogni flusso. Ogni sezione comincia con una riga m: m uguale audio, m uguale video, oppure m uguale application per i canali dati. Sotto ogni sezione ci sono gli attributi: i codec disponibili, ognuno con un numero; le credenziali per ICE, cioè un nome utente e una password che serviranno a verificare la strada; l'impronta del certificato, il fingerprint, su cui torneremo nell'episodio dedicato alla sicurezza; la direzione del flusso.

Leggere un SDP la prima volta mette un po' paura. Sono decine di righe dense. Ma vale la pena farlo almeno una volta, con calma: nelle note di questa puntata ne trovate uno commentato. Molti problemi di compatibilità si capiscono in un minuto guardando quali codec offre una riva e quali accetta l'altra.

Il modo in cui le lettere si scambiano segue un modello preciso, chiamato offerta e risposta.

Una delle due rive scrive un'offerta: ecco cosa so fare, ecco cosa vorrei mandare e ricevere. L'altra riva legge l'offerta e scrive una risposta: di tutto quello che proponi, ecco cosa accetto. Il risultato è l'intersezione delle due capacità.

Le regole precise di questo scambio, adattate a JavaScript, stanno in un documento chiamato JSEP. La versione aggiornata è la RFC 9429, del 2024, che ha sostituito la prima versione del 2021. Nel codice si traduce in quattro chiamate. Chi offre chiama createOffer e poi setLocalDescription, per dire al proprio browser: questo è il mio progetto. Manda l'offerta con il messaggero. Chi riceve chiama setRemoteDescription con l'offerta, poi createAnswer e setLocalDescription con la risposta. La rimanda indietro. Chi aveva offerto chiama setRemoteDescription con la risposta. Fine.

Nei browser moderni si può anche chiamare setLocalDescription senza argomenti: il browser capisce da solo se deve creare un'offerta o una risposta, a seconda dello stato. È più corto e lascia meno spazio agli errori.

Lo stato del messaggero si legge in una proprietà chiamata signalingState. Stable significa che non ci sono trattative in corso. Have local offer significa che ho mandato una proposta e aspetto. Have remote offer significa che ho ricevuto una proposta e devo rispondere.

Ogni sezione dell'SDP corrisponde a una corsia del ponte, e nel codice si chiama transceiver.

Un transceiver mette insieme un mittente e un ricevente per un singolo flusso. Ha una direzione: invia e ricevi, solo invia, solo ricevi, oppure inattivo. Quando aggiungete una traccia con addTrack, il browser crea o riusa un transceiver. Potete anche crearne uno esplicitamente, per esempio per dire: voglio ricevere video, anche se io non ne mando.

Il modello attuale si chiama Unified Plan: una sezione m per ogni traccia. Se avete codice vecchio, potreste trovare riferimenti a Plan B, il modello precedente di Chrome, dove più tracce condividevano la stessa sezione. È stato rimosso da tempo, ma molti tutorial in rete lo danno ancora per buono.

Il progetto del ponte non è fissato per sempre. Se a metà chiamata accendete la videocamera, o condividete lo schermo, serve una nuova corsia. E quindi una nuova trattativa.

Il browser vi avvisa con un evento: negotiationneeded. Significa: il progetto è cambiato, serve una nuova offerta. La rinegoziazione funziona esattamente come la prima volta, offerta e risposta, ma il ponte resta in piedi durante i lavori.

E qui nasce il problema più classico della segnalazione.

Immaginate che entrambe le rive decidano, nello stesso istante, di proporre una modifica. Le due barche partono insieme, si incrociano a metà fiume, e ognuna arriva dall'altra parte con un'offerta. Ma ognuna delle due rive è nello stato have local offer: ha già proposto qualcosa e aspetta una risposta, non un'altra proposta. In inglese questo si chiama glare, abbagliamento.

La soluzione raccomandata dalla specifica si chiama negoziazione perfetta. Prima di cominciare, si stabilisce che una delle due rive è educata e l'altra è scortese. Quando le lettere si incrociano, la riva scortese ignora l'offerta in arrivo e tiene la propria. La riva educata, invece, ritira la propria proposta, con un'operazione chiamata rollback, e accetta quella dell'altra. Nessuno resta bloccato.

Il bello di questo schema è che il codice è identico sui due lati: cambia solo un valore, educato vero o falso. Di solito è il server di segnalazione a decidere chi è chi, per esempio il primo che entra nella stanza è scortese e il secondo educato. Nelle note trovate l'implementazione completa, in poco più di trenta righe.

Quindi, ricapitolando: la segnalazione è il messaggero che porta il progetto del ponte, e WebRTC vi lascia libertà totale su come costruirlo. Le lettere sono scritte in SDP, si scambiano con il modello offerta e risposta, e la negoziazione perfetta evita che due lettere incrociate blocchino tutto.

Però nella lettera manca ancora un'informazione fondamentale: da dove passerà il ponte. Le rive sono circondate da mura, e trovare un varco è il tema del prossimo episodio: i cancelli e la vedetta. Ci sentiamo lì.