La Server-Sent Events EventSource API è una libreria (HTML5, ndR) che permette di delegare al browser il monitoraggio di una sorgente di dati e di ottenere notifiche "push" dal server ad ogni nuovo evento. Con questa semplicissima API possiamo, ad esempio, tenere sotto osservazione un servizio remoto e gestire ogni messaggio da esso generato all'interno delle nostre applicazioni web.
Grazie all'estrema semplicità di utilizzo risulta un'ottima alternativa ai classici meccanismi di polling sviluppati attorno all'utilizzo di tecniche AJAX. Nonostante il loro supporto non abbracci ancora l'intero insieme dei browser di ultima generazione esistono ottime librerie capaci di emulare in modo soddisfacente questa interessante
feature.
La forma più semplice di implementazione dei Server Sent Events è la seguente: supponiamo di avere un servizio remoto che, ad ogni interrogazione, restituisce l'ora corrente: implementiamo questo semplice meccanismo in PHP creando un file 'tempo.php
' all'interno di una cartella che abbiamo predisposto per questo progetto e che possa essere raggiungibile attraverso un web server:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo 'data: ' . strftime('%H:%M:%S',time()) . PHP_EOL;
echo PHP_EOL;
?>
Per prima cosa notiamo come l'output di questo script non sia HTML, ne JSON e XML ma semplice testo contenente una sola riga prefissata dalla particella 'data:
' e valorizzata con la data corrente, ad esempio:
data: 12:28:00
Per ogni riga 'data:
' seguita da una riga vuota, il browser genererà per noi un evento di tipo message, che potremo facilmente gestire.
Prima di proseguire dobbiamo sincerarci che ogni interrogazione a 'tempo.php
' abbia come MIME type di risposta 'text/event-stream
'; per fare questo forziamo l'header Content-Type utilizzando il comando 'header
' di PHP; utilizziamo lo stesso comando anche per sincerarci che la risposta non venga memorizzata nella cache del browser con l'header Cache-Control: no-cache
.
Costruiamo ora la pagina web, che possiamo chiamare index.html
, per monitorare il semplice
servizio appena creato:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Che ore sono?</title>
<script>
var source = new EventSource('tempo.php');
source.addEventListener('message', function (event) {
document.querySelector('span').textContent = event.data;
});
</script>
</head>
<body>
Sono le: <span></span>
</body>
</html>
Lato front-end sintassi e implementazione di queste API sono semplicissime. È sufficiente creare un nuovo oggetto EventSource che punti alla pagina da monitorare e agganciare a questo oggetto un apposito listener per l'evento 'message'. Nella proprietà 'data' dell'evento potremo trovare il contenuto testuale ricevuto dal server, in questo caso l'ora corrente.
Eventi multipli
Ecco cosa accade dietro le quinte: il browser accede ripetutamente all'indirizzo specificato come parametro nel costruttore dell'oggetto EventSource
e per ogni 'data:
' recuperato genera un evento 'message' che contiene nel suo campo 'data' il contenuto testuale prodotto dal server. Possiamo decidere di cambiare il nome dell'evento utilizzando la sintassi 'event: nomeevento
'; questo ci permette di gestire eventi di tipo diverso all'interno dello stesso flusso di dati.
Proviamo a modificare l'esempio precedente generando due eventi, uno 'timerome
' contenente l'ora corrente con il fuso orario 'Europe/Rome' ed un'altro 'timenewyork
' con la stessa ora ma nel fuso orario della grande mela; ecco il nuovo 'tempo.php
'
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
date_default_timezone_set('Europe/Rome');
echo 'event: timerome' . PHP_EOL;
echo 'data: ' . strftime('%H:%M:%S',time()) . PHP_EOL;
echo PHP_EOL;
date_default_timezone_set('America/New_York');
echo 'event: timenewyork' . PHP_EOL;
echo 'data: ' . strftime('%H:%M:%S',time()) . PHP_EOL;
echo PHP_EOL;
?>
Dobbiamo effettuare alcune modifiche anche sul front-end per gestire i due eventi generati da questa sorgente dati:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Che ore sono?</title>
<script>
var source = new EventSource('tempo.php');
source.addEventListener('timerome', function (event) {
document.querySelector('span[data-tz="rome"]').
textContent = event.data;
});
source.addEventListener('timenewyork', function (event) {
document.querySelector('span[data-tz="new_york"]').
textContent = event.data;
});
</script>
</head>
<body>
Roma: <span data-tz="rome"></span><br/>
New York: <span data-tz="new_york"></span>
</body>
</html>
Nella seconda parte dell'articolo vedremo come generare grafici utilizzando l'API per la ricezione dei messaggi.
Grafici dinamici con i Server-Sent Events
Vediamo come costruire qualcosa di più complesso: supponiamo di dover sviluppare un grafico legato ad alcuni indici che variano molto velocemente e di volere che questa variazione si rifletta il prima possibile all'interno dell'interfaccia utente. Possiamo creare una sorgente remota che risponda ad ogni interrogazione con il valore attuale degli indici ed utilizzare un EventSource
lato client per recuperare queste informazioni.
Per prima cosa costruiamo la sorgente remota, per le finalità dell'esempio andrà benissimo generare il valore degli indici in modo random, ovviamente in un ambiente di produzione dovremmo interrogare il database. Creiamo un nuovo file 'tick.php
' all'interno della cartella di progetto:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo 'data: {"data1": '. rand(1,10) . ',"data2": '. rand(1,10) . ',"data3": '.
rand(1,10) . ',"data4": '. rand(1,10) . '}' . PHP_EOL;
echo PHP_EOL;
?>
Possiamo notare come il messaggio di testo sia stato serializzato in JSON, questo rende facile inviare attraverso queste API anche strutture più complesse del semplice messaggio testuale. Ora passiamo al client: creiamo una pagina: 'grafico.html
' così strutturata:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Un semplice grafico</title>
<style>
section
{
position:relative;
height: 100px;
width: 100%;
background: gray;
}
div
{
position: absolute;
background: red;
width: 50px;
bottom: 0px;
-webkit-transition: height 1s;
-moz-transition: height 1s;
transition: height 1s;
}
div:nth-child(1){ left: 10px; }
div:nth-child(2){ left: 70px; }
div:nth-child(3){ left: 130px; }
div:nth-child(4){ left: 190px; }
</style>
</head>
<body>
<section>
<div></div>
<div></div>
<div></div>
<div></div>
</section>
<script>
var stocks = new EventSource("tick.php");
stocks.addEventListener('message', function (event) {
var graph_data = JSON.parse(event.data),
d1 = document.querySelector('div:nth-child(1)'),
d2 = document.querySelector('div:nth-child(2)'),
d3 = document.querySelector('div:nth-child(3)'),
d4 = document.querySelector('div:nth-child(4)');
d1.style.height = "" + (graph_data.data1 * 10) + "px";
d2.style.height = "" + (graph_data.data2 * 10) + "px";
d3.style.height = "" + (graph_data.data3 * 10) + "px";
d4.style.height = "" + (graph_data.data4 * 10) + "px";
});
</script>
</body>
</html>
In questo caso ad ogni evento 'message
' dobbiamo deserializzare il messaggio ricevuto e associare ad ogni barra del nostro grafico l'altezza corrispettiva all'indice che rappresenta.
Se proviamo ad eseguire il tutto in un browser con il supporto per queste API potremmo sperimentare una semplice ma efficace versione di un grafico animato che si aggiorna automaticamente alla variazione del dato sul server:
![Esempio](img/fig01.png)
Polyfill
Ecco due possibili polyfill per i browser che non supportano nativamente l'API, consigliati da Remy Sharp in un suo post interessantissimo su EventSource: