Diffuser les mises à jour avec des événements envoyés par le serveur

Événements envoyés par le serveur (les SSE) envoient des mises à jour automatiques à un client à partir d'un serveur . Une fois la connexion établie, les serveurs peuvent lancer des données la transmission.

Vous pouvez utiliser des SSE pour envoyer des notifications push à partir de votre application Web. Les SSE envoient des informations dans un sens. Vous ne recevrez donc pas de mises à jour du client.

Le concept de SSE vous est peut-être familier. Une application Web permet de s'abonner à un flux de mises à jour générées par un serveur et, dès qu'un nouvel événement se produit, une notification est envoyée au client. Mais pour bien comprendre les événements envoyés par le serveur, nous devons de comprendre les limites de ses prédécesseurs AJAX. Par exemple :

  • Sondage: l'application interroge de façon répétée un serveur pour obtenir des données. Cette technique est utilisé par la plupart des applications AJAX. Avec le protocole HTTP, la récupération les données s'articulent autour d'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. S'il n'en existe aucun, un champ vide est renvoyée. L'augmentation des interrogations augmente la surcharge HTTP.

  • Sondage long (Hanging GET / COMET): si le serveur ne dispose pas de données disponible, le serveur conserve la demande ouverte jusqu'à ce que de nouvelles données soient disponibles. Cette technique est donc souvent appelée "Hanging GET". Quand ? que des informations deviennent 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 configurer cela, les développeurs utilisent généralement des méthodes comme l'ajout des tags de script sur une valeur "infinite" iFrame.

Les événements envoyés par le serveur ont été conçus dès le départ pour être efficaces. Lorsque vous communiquez avec les SSE, un serveur peut transmettre des données à tout moment, sans avoir à effectuer de requête initiale. Autrement dit, les mises à jour peuvent être diffusées d'un serveur à l'autre au fur et à mesure. SSE ouvrir un canal unidirectionnel unique 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és directement par le navigateur et l'utilisateur n'a qu'à écouter les messages.

Événements envoyés par le serveur et WebSockets

Pourquoi choisir les événements envoyés par le serveur plutôt que WebSockets ? Bonne question !

WebSockets possède un protocole riche, une communication bidirectionnelle full-duplex. Un canal à double sens convient mieux les jeux, les applications de chat et tout cas d'utilisation pour lequel vous avez besoin de 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 statut, les symboles boursiers, les flux d'actualités ou d'autres mécanismes automatisés de transfert de données. Autrement dit, une mise à jour d'une base de données SQL Web ou d'un magasin d'objets IndexedDB côté client. Si vous devez envoyer des données à un serveur, XMLHttpRequest est toujours votre ami.

Les SSE sont envoyées via HTTP. Il n'y a pas de protocole ni de serveur spécial l’implémentation pour commencer à fonctionner. Les WebSockets nécessitent un duplex intégral et de nouveaux serveurs WebSocket pour gérer le protocole.

En outre, les événements envoyés par le serveur présentent de nombreuses fonctionnalités qui ne sont pas disponibles dans WebSockets. par exemple la reconnexion automatique, les identifiants des événements et la possibilité d'envoyer des événements arbitraires.

Créer une EventSource avec JavaScript

Pour vous abonner à un flux d'événements, créez un objet EventSource et transmettez-lui le URL de votre flux:

const source = new EventSource('stream.php');

Configurez ensuite un gestionnaire pour l'événement message. Vous pouvez aussi écoutez 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 envoyées à partir du serveur, le gestionnaire onmessage se déclenche Les nouvelles données sont alors disponibles dans sa propriété e.data. La partie magique est qu'à chaque fois que la connexion est fermée, le navigateur se reconnecte automatiquement au au bout de trois secondes environ. La mise en œuvre 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 de stream.php.

Format du flux d'événements

L'envoi d'un flux d'événements depuis la source consiste à construire en texte brut, diffusée avec un en-tête Content-Type text/event-stream. qui suit le format SSE. Dans sa forme de base, la réponse doit contenir une ligne data:, suivie de votre , suivi de deux « \n » caractères pour terminer le flux:

data: My message\n\n

Données multilignes

Si votre message est plus long, vous pouvez le diviser en plusieurs lignes data:. Au moins deux lignes consécutives 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 du dernier, qui doit se terminer avec deux réponses). Le résultat transmis à votre gestionnaire message est une chaîne unique concaténés par des caractères de retour à la ligne. Exemple :

data: first line\n
data: second line\n\n</pre>

Cela génère "première ligne\ndeuxième ligne" dans e.data. On pourrait alors utiliser e.data.split('\n').join('') pour reconstruire le message sans "\n" caractères.

Envoyer des données JSON

L'utilisation de plusieurs lignes vous permet d'envoyer du code JSON sans rompre la syntaxe:

data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n

Et le 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

La définition d'un identifiant 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) est 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 avant expiration de la reconnexion

Le navigateur tente de se reconnecter à la source après environ trois secondes après la fermeture de chaque connexion. Vous pouvez modifier ce délai en incluant un ligne commençant par retry:, suivie du nombre de millisecondes d'attendre avant de tenter de vous reconnecter.

L'exemple suivant tente de se reconnecter après 10 secondes:

retry: 10000\n
data: hello world\n\n

Spécifier un nom d'événement

Une même source d'événement peut générer différents types d'événements en incluant un élément le nom de l'événement. Si une ligne commençant par event: est présente, suivi du nom unique de l'événement, celui-ci 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 suivante du serveur envoie trois types d'événements, un message générique event, "userlogon" et "update" événement:

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()));
?>

Vous trouverez ci-dessous une implémentation similaire dans Node JS, avec 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 de l'événement lorsque la connexion est fermé, 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 du serveur, répondez avec une réponse autre que text/event-stream Content-Type ou renvoyer un état HTTP autre que 200 OK (par exemple, 404 Not Found).

Ces deux méthodes empêchent le navigateur de rétablir la connexion.

Un mot sur la sécurité

Les requêtes générées par EventSource sont soumises aux règles d'origine identique à à d'autres API réseau, comme la récupération. Si le point de terminaison SSE de votre serveur doit accessibles depuis différentes origines, découvrez comment les activer Partage des ressources entre origines multiples (CORS) :