Les événements envoyés par le serveur (SSE) envoient des mises à jour automatiques à un client à partir d'un serveur, via une connexion HTTP. Une fois la connexion établie, les serveurs peuvent lancer la transmission de données.
Vous pouvez utiliser des SSE pour envoyer des notifications push depuis votre application Web. Les SSE envoient des informations dans un seul sens. Vous ne recevrez donc pas de mises à jour du client.
Le concept d'instructions SSE vous est peut-être familier. Une application Web "s'abonne" à un flux de mises à jour généré par un serveur. Chaque fois qu'un nouvel événement se produit, une notification est envoyée au client. Toutefois, pour bien comprendre les événements envoyés par le serveur, nous devons comprendre les limites de ses prédécesseurs AJAX. Par exemple :
Interrogation : l'application interroge à plusieurs reprises un serveur pour obtenir des données. Cette technique est utilisée par la majorité des applications AJAX. Avec le protocole HTTP, la récupération de données repose sur un format de requête et de réponse. Le client envoie une requête et attend que le serveur réponde avec des données. Si aucun n'est disponible, une réponse vide est renvoyée. L'augmentation des interrogations augmente la surcharge HTTP.
Interrogation longue (Hanging GET / COMET): si le serveur ne dispose pas de données, il maintient la requête ouverte jusqu'à ce que de nouvelles données soient disponibles. C'est pourquoi cette technique est souvent appelée "GET suspendu". Lorsque les informations sont disponibles, le serveur répond, ferme la connexion et le processus est répété. Ainsi, le serveur répond constamment avec de nouvelles données. Pour ce faire, les développeurs utilisent généralement des hacks tels que l'ajout de balises de script à une iframe "infinie".
Les événements envoyés par le serveur ont été conçus dès le départ pour être efficaces. Lorsqu'il communique avec des SSE, un serveur peut transmettre des données à votre application quand il le souhaite, sans avoir à effectuer de requête initiale. En d'autres termes, les mises à jour peuvent être diffusées d'un serveur à un autre au fur et à mesure qu'elles se produisent. Les SSE ouvrent un seul canal unidirectionnel entre le serveur et le client.
La principale différence entre les événements envoyés par le serveur et les interrogations de longue durée est que les SSE sont gérées directement par le navigateur et que l'utilisateur n'a qu'à écouter les messages.
Événements envoyés par le serveur par rapport aux WebSockets
Pourquoi choisir les événements envoyés par le serveur plutôt que les WebSockets ? Bonne question !
WebSockets dispose d'un protocole riche avec une communication bidirectionnelle en duplex intégral. Un canal bidirectionnel est préférable pour les jeux, les applications de messagerie et tous les cas d'utilisation nécessitant des mises à jour en temps quasi réel dans les deux sens.
Cependant, il arrive parfois que vous n'ayez besoin que d'une communication unidirectionnelle d'un serveur.
(par exemple, lorsqu'un ami met à jour son état, les symboles boursiers, les flux d'actualités ou d'autres mécanismes automatisés d'envoi de données). En d'autres termes, une mise à jour d'une base de données Web SQL côté client ou d'un magasin d'objets IndexedDB.
Si vous devez envoyer des données à un serveur, XMLHttpRequest
est toujours un allié.
Les SSE sont envoyées via HTTP. Aucune implémentation de protocole ou de serveur spécifique n'est requise. Les WebSockets nécessitent des connexions full-duplex et de nouveaux serveurs WebSocket pour gérer le protocole.
De plus, les événements envoyés par le serveur disposent de diverses fonctionnalités que les WebSockets ne possèdent pas par conception, y compris la reconnexion automatique, les ID d'événement et la possibilité d'envoyer des événements arbitraires.
Créer un EventSource avec JavaScript
Pour vous abonner à un flux d'événements, créez un objet EventSource
et transmettez-lui l'URL de votre flux :
const source = new EventSource('stream.php');
Ensuite, configurez un gestionnaire pour l'événement message
. Vous pouvez éventuellement écouter open
et error
:
source.addEventListener('message', (e) => {
console.log(e.data);
});
source.addEventListener('open', (e) => {
// Connection was opened.
});
source.addEventListener('error', (e) => {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
});
Lorsque des mises à jour sont transmises depuis le serveur, le gestionnaire onmessage
se déclenche et de nouvelles données sont disponibles dans sa propriété e.data
. La partie magique est que chaque fois que la connexion est fermée, le navigateur se reconnecte automatiquement à la source au bout d'environ trois secondes. L'implémentation de votre serveur peut même contrôler ce délai avant expiration de la reconnexion.
Et voilà ! Votre client peut désormais traiter les événements provenant de stream.php
.
Format du flux d'événements
Pour envoyer un flux d'événements à partir de la source, il suffit de créer une réponse en texte brut, diffusée avec un type de contenu text/event-stream
, qui suit le format SSE.
Dans sa forme de base, la réponse doit contenir une ligne data:
, suivie de votre message, puis de deux caractères "\n" pour mettre fin au flux :
data: My message\n\n
Données multilignes
Si votre message est plus long, vous pouvez le diviser en utilisant plusieurs lignes data:
.
Deux lignes consécutives ou plus commençant par data:
sont traitées comme une seule donnée, ce qui signifie qu'un seul événement message
est déclenché.
Chaque ligne doit se terminer par un seul "\n" (à l'exception de la dernière, qui doit se terminer par deux). Le résultat transmis à votre gestionnaire message
est une seule chaîne concaténée par des caractères de nouvelle ligne. Exemple :
data: first line\n
data: second line\n\n</pre>
Cela produit "première ligne\ndeuxième ligne" dans e.data
. On pourrait ensuite utiliser e.data.split('\n').join('')
pour reconstruire le message sans les caractères "\n".
Envoyer des données JSON
L'utilisation de plusieurs lignes vous permet d'envoyer du code JSON sans enfreindre la syntaxe :
data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
Code côté client possible pour gérer ce flux :
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.id, data.msg);
});
Associer un ID à un événement
Vous pouvez envoyer un identifiant unique avec un événement de flux en incluant une ligne commençant par id:
:
id: 12345\n
data: GOOG\n
data: 556\n\n
Définir un ID permet au navigateur de suivre le dernier événement déclenché afin que, si la connexion au serveur est interrompue, un en-tête HTTP spécial (Last-Event-ID
) soit défini avec la nouvelle requête. Cela permet au navigateur de déterminer quel événement doit être déclenché.
L'événement message
contient une propriété e.lastEventId
.
Contrôler le délai de reconnexion
Le navigateur tente de se reconnecter à la source environ trois secondes après la fermeture de chaque connexion. Vous pouvez modifier ce délai en incluant une ligne commençant par retry:
, suivie du nombre de millisecondes d'attente avant toute tentative de reconnexion.
L'exemple suivant tente une reconnexion au bout de 10 secondes :
retry: 10000\n
data: hello world\n\n
Spécifier un nom d'événement
Une seule source d'événement peut générer différents types d'événements en incluant un nom d'événement. Si une ligne commençant par event:
est présente, suivie d'un nom unique pour l'événement, l'événement est associé à ce nom.
Sur le client, un écouteur d'événements peut être configuré pour écouter cet événement particulier.
Par exemple, la sortie du serveur suivante envoie trois types d'événements : un événement générique "message", un événement "userlogon" et un événement "update" :
data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
Avec des écouteurs d'événements configurés sur le client :
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.msg);
});
source.addEventListener('userlogon', (e) => {
const data = JSON.parse(e.data);
console.log(`User login: ${data.username}`);
});
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} is now ${data.emotion}`);
};
Exemples de serveurs
Voici une implémentation de base du serveur en PHP:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>
Voici une implémentation similaire sur Node.js à l'aide d'un gestionnaire Express :
app.get('/events', (req, res) => {
// Send the SSE header.
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Sends an event to the client where the data is the current date,
// then schedules the event to happen again after 5 seconds.
const sendEvent = () => {
const data = (new Date()).toLocaleTimeString();
res.write("data: " + data + '\n\n');
setTimeout(sendEvent, 5000);
};
// Send the initial event immediately.
sendEvent();
});
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
const source = new EventSource('/events');
source.onmessage = (e) => {
const content = document.createElement('div');
content.textContent = e.data;
document.body.append(content);
};
</script>
</body>
</html>
Annuler un flux d'événements
Normalement, le navigateur se reconnecte automatiquement à la source d'événements lorsque la connexion est fermée, mais ce comportement peut être annulé à partir du client ou du serveur.
Pour annuler un flux à partir du client, appelez :
source.close();
Pour annuler un flux à partir du serveur, répondez avec un Content-Type
autre que text/event-stream
ou renvoyez un état HTTP autre que 200 OK
(par exemple, 404 Not Found
).
Les deux méthodes empêchent le navigateur de rétablir la connexion.
Quelques mots sur la sécurité
Les requêtes générées par EventSource sont soumises aux règles d'origine identique à celles des autres API réseau telles que la récupération. Si vous souhaitez que le point de terminaison SSE de votre serveur soit accessible depuis différentes origines, découvrez comment l'activer à l'aide du partage des ressources entre origines multiples (CORS).