W3docs

JavaScript Server-Sent Events (EventSource)

Impara i Server-Sent Events in JavaScript con l'API EventSource: ricevi aggiornamenti in streaming dal server via HTTP, con riconnessione automatica ed eventi nominati.

I Server-Sent Events (SSE) sono un modo standard con cui un server può inviare in push un flusso continuo e unidirezionale di aggiornamenti testuali al browser attraverso una singola connessione HTTP a lunga durata. Invece che il client esegua ripetuti polling a un endpoint tramite fetch chiedendo "c'è qualcosa di nuovo?", la connessione rimane aperta e il server scrive messaggi ogni volta che ha qualcosa da inviare. Il browser espone questa funzionalità tramite l'interfaccia built-in EventSource, quindi non è necessaria alcuna libreria esterna per consumare uno stream.

Gli SSE eccellono quando gli aggiornamenti fluiscono in una sola direzione — dal server al client. Feed di notizie in diretta, notifiche, barre di avanzamento per build o upload, dashboard di monitoraggio e ticker azionari sono tutti casi d'uso naturali.

Ricevere uno stream con EventSource

Aprire uno stream richiede una sola riga. Si crea un EventSource puntato a un endpoint del server, poi si allegano i gestori per gli eventi che genera.

const es = new EventSource('/events');

es.onmessage = (event) => {
  console.log('New message:', event.data);
};

es.onopen = () => {
  console.log('Connection opened');
};

es.onerror = (error) => {
  console.log('Connection error:', error);
};

Vale la pena notare alcune cose:

  • La connessione si apre non appena si costruisce l'EventSource.
  • onmessage si attiva per ogni messaggio predefinito (senza nome) inviato dal server. Il payload è sempre una string, disponibile tramite event.data.
  • onopen si attiva una volta che la connessione è stabilita (e nuovamente dopo una riconnessione).
  • onerror si attiva quando la connessione cade o fallisce. A differenza di una fetch fallita, questo non significa necessariamente che si debba rinunciare — il browser di solito tenta di riconnettersi autonomamente (maggiori dettagli a seguire).

Ecco un esempio leggermente più completo che analizza payload JSON e aggiorna la pagina:

const es = new EventSource('/notifications');

es.onmessage = (event) => {
  const data = JSON.parse(event.data);
  const li = document.createElement('li');
  li.textContent = data.message;
  document.querySelector('#feed').append(li);
};

Il formato wire

Il server risponde con il tipo di contenuto text/event-stream e scrive testo UTF-8 semplice in un formato basato su righe. Ogni messaggio è un blocco di righe campo: valore, e una riga vuota segna la fine di un messaggio.

data: Hello there

data: First line
data: Second line

event: priceUpdate
data: {"symbol":"ACME","price":42.10}

id: 42
data: This message has an id

retry: 10000
data: Reconnect after 10 seconds next time

I campi riconosciuti sono:

  • data: — il payload del messaggio. Se un messaggio ha più righe data:, vengono unite con un newline (\n) prima di essere consegnate come event.data.
  • event: — un nome evento opzionale. Senza di esso, il messaggio viene consegnato a onmessage.
  • id: — un identificatore opzionale che il browser memorizza per la riconnessione.
  • retry: — il ritardo di riconnessione in millisecondi.

Le righe che iniziano con i due punti (:) sono commenti e vengono ignorati — i server spesso li inviano come ping keep-alive.

Eventi nominati

Per impostazione predefinita ogni messaggio va a onmessage. Un server può invece etichettare un messaggio con un campo event:, e il client ascolta quel nome specifico tramite addEventListener. Questo consente di instradare diversi tipi di aggiornamenti a gestori differenti sulla stessa connessione.

Dato uno stream come questo:

event: priceUpdate
data: {"symbol":"ACME","price":42.10}

event: newsAlert
data: Markets closed early today

data: a plain default message

Il client si configura come segue:

const es = new EventSource('/market');

es.addEventListener('priceUpdate', (event) => {
  const { symbol, price } = JSON.parse(event.data);
  console.log(`${symbol} is now ${price}`);
});

es.addEventListener('newsAlert', (event) => {
  console.log('News:', event.data);
});

// Messages with no `event:` field still land here.
es.onmessage = (event) => {
  console.log('Default message:', event.data);
};

Riconnessione automatica

Questa è la caratteristica che distingue gli SSE dall'implementare manualmente lo streaming con fetch. Se la connessione cade — una rete instabile, un riavvio del server, un timeout del proxy — il browser si riconnette automaticamente dopo un breve ritardo. Non è necessario scrivere alcun ciclo di retry.

Per rendere la ripresa affidabile, il browser tiene traccia dell'ultimo id: ricevuto. Alla riconnessione invia quel valore nell'header di richiesta Last-Event-ID, così il server può riprendere esattamente da dove lo stream si era interrotto anziché riprodurre tutto dall'inizio.

GET /events HTTP/1.1
Last-Event-ID: 42
Accept: text/event-stream

Il server può anche regolare il ritardo prima della prossima riconnessione inviando un campo retry: (in millisecondi):

retry: 5000
data: The browser will wait 5 seconds before reconnecting if dropped
Nota

La riconnessione automatica avviene solo quando la connessione è considerata recuperabile. Se il server risponde a una riconnessione con un errore HTTP come 204 No Content o uno stato 4xx, il browser considera lo stream terminato e smette di tentare.

Stato della connessione e chiusura

Un EventSource espone il suo stato corrente tramite la proprietà readyState, che corrisponde a tre costanti:

  • EventSource.CONNECTING (0) — in connessione, o in attesa di riconnettersi.
  • EventSource.OPEN (1) — connesso e in ricezione.
  • EventSource.CLOSED (2) — chiuso e non si riconnetterà.

Quando lo stream non è più necessario, chiamare es.close(). Questo chiude la connessione immediatamente e — cosa importante — il browser non si riconnetterà in seguito.

const es = new EventSource('/events');

// Stop listening after 30 seconds.
setTimeout(() => {
  es.close();
  console.log('readyState is now', es.readyState); // 2 (CLOSED)
}, 30000);

Richieste cross-origin e credenziali

Per impostazione predefinita un EventSource segue la same-origin policy. Per fare streaming da un'origine diversa, il server deve inviare gli opportuni header CORS (come Access-Control-Allow-Origin).

I cookie non vengono inviati nelle richieste cross-origin a meno che non si faccia opt-in. Passare l'opzione withCredentials, e assicurarsi che il server consenta le credenziali nella sua configurazione CORS:

const es = new EventSource('https://api.example.com/events', {
  withCredentials: true,
});

Una nota sul lato server

Gli SSE sono principalmente una funzionalità del browser, ma il server deve cooperare. Qualunque linguaggio si utilizzi, la ricetta è la stessa: rispondere con Content-Type: text/event-stream, mantenere la connessione aperta invece di terminare la risposta, e scrivere chunk in formato evento man mano che i dati diventano disponibili.

Ecco un esempio minimo con Node.js per mostrarne la struttura:

import http from 'node:http';

http.createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  let id = 0;
  const timer = setInterval(() => {
    id += 1;
    res.write(`id: ${id}\n`);
    res.write(`data: The time is ${new Date().toISOString()}\n\n`);
  }, 1000);

  // Stop the timer when the client disconnects.
  req.on('close', () => clearInterval(timer));
}).listen(3000);

Ogni chunk termina con una riga vuota (\n\n) per completare il messaggio. Il client connesso con new EventSource('http://localhost:3000') riceverebbe un aggiornamento al secondo.

Limitazioni

Attenzione

Gli SSE trasportano solo testo (UTF-8) — non esiste un tipo di frame binario, quindi non è possibile trasmettere in streaming byte grezzi come immagini o audio senza prima codificarli. Sono anche strettamente unidirezionali: per inviare dati al server è comunque necessaria una normale richiesta tramite fetch o XMLHttpRequest, oppure una connessione WebSocket full-duplex.

Un ulteriore limite pratico: su HTTP/1.1, i browser limitano il numero di connessioni simultanee verso una singola origine a circa sei. Poiché ogni EventSource mantiene aperta una connessione, aprire molte schede sullo stesso sito può esaurire quel budget. Questo è molto meno preoccupante su HTTP/2, dove molti stream vengono multiplexati su una singola connessione.

SSE vs. WebSockets

Gli SSE e i WebSockets forniscono entrambi dati in tempo reale, ma risolvono problemi diversi.

Server-Sent EventsWebSockets
DirezioneUnidirezionale (server → client)Bidirezionale (full duplex)
ProtocolloHTTP sempliceUpgrade ws:// / wss://
DatiSolo testo (UTF-8)Testo e binario
RiconnessioneAutomatica, integrataDa implementare manualmente
Ideale perFeed, notifiche, avanzamentoChat, giochi, editing collaborativo

Scegliere SSE quando il browser deve solo ascoltare — notifiche, feed in diretta, dashboard, aggiornamenti di avanzamento. La riconnessione automatica, il trasporto HTTP semplice e la piccola API client ne fanno lo strumento più semplice per il lavoro. Scegliere WebSockets quando il client deve anche inviare frequentemente, quando si ha bisogno di dati binari, o quando la latenza per messaggio è importante, come nelle app di chat e nei giochi multiplayer.

Informazione

Se i propri dati sono già gestiti tramite un flusso asincrono basato su promise, abbinare i gestori SSE con async/await per eventuali richieste successive (ad esempio, recuperare i dettagli completi quando arriva una notifica leggera) mantiene il codice pulito e leggibile.

Metti alla prova le tue conoscenze

Pratica
I Server-Sent Events forniscono comunicazione in quale direzione?
I Server-Sent Events forniscono comunicazione in quale direzione?
Pratica
Qual è un vantaggio chiave di EventSource rispetto allo streaming manuale con fetch?
Qual è un vantaggio chiave di EventSource rispetto allo streaming manuale con fetch?
Pratica
Quali affermazioni sul formato wire degli SSE sono vere?
Quali affermazioni sul formato wire degli SSE sono vere?
Was this page helpful?