Server-Sent Events (SSE): aggiornamenti in tempo reale dal server al client

  



Quando sviluppiamo applicazioni web moderne, spesso abbiamo la necessità di inviare aggiornamenti in tempo reale dal server al browser dell’utente. Che si tratti di notifiche push, feed di notizie, aggiornamenti su ordini o eventi di sistema, vogliamo evitare soluzioni pesanti come il polling continuo.

Server-Sent Events (SSE) offre un modo semplice, efficiente e nativo per stabilire una connessione unidirezionale persistente dal server verso il client. 

🔗 Ti piace Techelopment? Dai un’occhiata al sito per tutti i dettagli!


Cos'è SSE?

Server-Sent Events (SSE) è uno standard introdotto da HTML5 che consente a un server di inviare aggiornamenti automatici al browser di un client tramite una connessione HTTP unidirezionale. A differenza del tradizionale polling o delle WebSocket, SSE è pensato per scenari in cui il flusso dei dati va solo dal server verso il client.


A cosa serve?

SSE è ideale per applicazioni in cui il client ha bisogno di ricevere notifiche o aggiornamenti in tempo reale, ma non deve necessariamente inviare dati in modo continuo al server. Alcuni esempi includono:

  • Notifiche push

  • Feed di notizie in tempo reale

  • Aggiornamenti su ordini o transazioni

  • Monitoraggio di sistemi (dashboard in tempo reale)

  • Invio messaggi (unidirezionali)


Come funziona?

Il funzionamento di SSE è relativamente semplice:

  1. Il client apre una connessione HTTP utilizzando l'oggetto EventSource in JavaScript.

  2. Il server mantiene la connessione aperta e invia dati nel formato SSE ogni volta che ha nuove informazioni.

  3. I dati sono inviati come testo semplice, con un formato specifico che include campi come data:, id:, e event:.


Esempio di implementazione

Client (JavaScript):


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

source.onmessage = function(event) {
  console.log("Message from the server:", event.data);
};

source.onerror = function(error) {
  console.error("SSE error:", error);
};


Server (Node.js esempio):


app.get('/events', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
 
    res.write('data: Welcome!\n\n');
 
    setInterval(() => {
      const timestamp = new Date().toISOString();
      res.write(`data: ${timestamp}\n\n`);
    }, 5000);
});


Vantaggi di SSE

  • Semplicità: facilmente integrabile nei browser moderni con poche righe di codice.

  • Supporto nativo nei browser: non necessita di librerie esterne.

  • Gestione automatica della riconnessione: il client tenta di riconnettersi automaticamente in caso di disconnessione.

  • Efficienza: meno overhead rispetto al polling o alle WebSocket nei casi di sola trasmissione server→client.


Errori comuni da evitare

🔴 Errore 1: Non impostare l’header corretto

❌ Codice sbagliato (Node.js con Express):


app.get('/events', (req, res) => {
    // Missing correct headers
    res.send('data: message\n\n');
});

✅ Corretto:


app.get('/events', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
 
    res.write('data: message\n\n');
});


🔴 Errore 2: Chiudere la connessione dopo ogni messaggio

❌ Codice sbagliato:


app.get('/events', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.write('data: This is a message\n\n');
    res.end(); // ❌ Connection closed
});

✅ Corretto:


app.get('/events', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
 
    setInterval(() => {
      res.write(`data: ${new Date().toISOString()}\n\n`);
    }, 5000);
});


🔴 Errore 3: Non gestire le riconnessioni e l'ID degli eventi

❌ Codice sbagliato:


// Server does not contain Last-Event-ID
app.get('/events', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.write(`data: new data\n\n`);
});

✅ Corretto:


app.get('/events', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
 
    const lastEventId = req.header('Last-Event-ID');
    if (lastEventId) {
      console.log('Resume from ID:', lastEventId);
    }
 
    const id = Date.now();
    res.write(`id: ${id}\n`);
    res.write(`data: new data with ID ${id}\n\n`);
});


🔴 Errore 4: Inviare dati JSON senza serializzarli

❌ Codice sbagliato:


const data = { user: 'Luca', status: 'online' };
res.write(`data: ${data}\n\n`); // ❌ [object Object]

✅ Corretto:


const data = { user: 'Luca', status: 'online' };
res.write(`data: ${JSON.stringify(data)}\n\n`);


🔴 Errore 5: Usare SSE per comunicazione bidirezionale

❌ Codice sbagliato:


// Client tries to send messages via EventSource (not possible)
const source = new EventSource('/events');
source.send("Hello!"); // ❌ This method does not exist

✅ Corretto:

Se vuoi una comunicazione client→server, devi usare una richiesta HTTP separata (ma in tal caso non parliamo più di Server-Sent Events 😆) :


// Client JS
fetch('/send-message', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ message: "Hello!" })
});


// Server
app.post('/send-message', (req, res) => {
    const msg = req.body.message;
    console.log("Received message:", msg);
    res.sendStatus(200);
});


🔍 Conseguenze del mantenere la connessione aperta con SSE

⚠️ 1. Consumo di risorse sul server

  • Ogni connessione aperta occupa una thread, socket o processo, a seconda della tecnologia e dell'architettura usata.

  • Se hai migliaia (o milioni) di utenti connessi contemporaneamente, il carico sul server può crescere rapidamente.

Soluzione: usare server non-blocking (come Node.js, Nginx con proxy pass, oppure Go) o tecnologie come Event Loop per gestire molte connessioni in modo efficiente.

⚠️ 2. Timeout dei proxy o load balancer

  • Molti proxy o load balancer (es. Nginx, Apache, AWS ELB) chiudono automaticamente connessioni HTTP aperte da troppo tempo.

  • Questo può causare disconnessioni silenziose tra client e server.

Soluzione: inviare un messaggio SSE di "keep-alive" ogni X secondi (anche solo un commento vuoto)

⚠️ 3. Limite massimo di connessioni per browser

  • I browser impongono un limite al numero di connessioni simultanee per dominio (tipicamente 6).

  • Una connessione SSE attiva ne occupa una, il che può limitare il caricamento di altre risorse o richieste.

Soluzione: attenzione al design dell'app, evitare di aprire più EventSource in parallelo dallo stesso dominio.

⚠️ 4. Compatibilità limitata con ambienti serverless

  • I servizi serverless come AWS Lambda o Vercel non sono ideali per connessioni persistenti, perché sono pensati per richieste brevi.

Soluzione: in questi casi, valutare alternative come WebSocket tramite servizi dedicati (es. AWS API Gateway WebSocket, Pusher, Ably, ecc.)

⚠️ 5. Gestione di riconnessioni e duplicati

  • Quando la connessione cade, EventSource tenta di riconnettersi automaticamente. Ma se il server non gestisce bene l’id, il client può perdere o ricevere due volte gli stessi eventi.

Soluzione: utilizzare il campo id: nei messaggi e leggere Last-Event-ID all’arrivo per garantire continuità.


SSE è semplice e molto utile, ma va usato con attenzione in contesti a larga scala o in ambienti distribuiti. La chiave è conoscere i suoi limiti e progettare il sistema in modo robusto.



Follow me #techelopment

Official site: www.techelopment.it
facebook: Techelopment
instagram: @techelopment
X: techelopment
Bluesky: @techelopment
telegram: @techelopment_channel
whatsapp: Techelopment
youtube: @techelopment