Les serveurs web sont construits pour envoyer des données aux clients. Parfois, les navigateurs doivent modifier le contenu de la page avec des informations envoyées du serveur sans recharger la page. par exemple, dans un chat, quand un nouveau message arrive, il est affiché sans recharger la page. Un autre exemple est quand le server réalise une longue tâche et qu'une barre de progression est affichée dans le navigateur. Le serveur envoi son état (sa progression) au client.
Une option intuitive serait de faire du pooling, généralement en s'appuyant sur des requêtes Ajax. Avec le pooling, le client demande régulièrement l'état du serveur, par exemple chaque seconde. L'inconvénient de cette solution est qu'elle peut surcharger le serveur et le réseau. Cela devient un problème lorsque le nombre de client augmente.
Une meilleur option est d'utiliser les server-sent event (SSE). SSE est un mécanisme avec lequel le serveur envoie des événements aux client. En réalité, un événement est composé de deux choses :
La troisième option est d'utiliser les Websockets. Les Websockets sont appropriés lorsuqe la communicaiton entre le client et le serveur doit être bidirectionnelle. Toutefois, le PHP n'est pas nativement conçu pour supporter les Websockets. Leur implémentation peut être un peu compliquée. La combinaison des SSE et d'Ajax peuvent être une bonne alternative aux Websockets.
La communication SSE est toujours initiée par le client. C'est la première étape dans el diagramme suivant. Le client envoie une requête au serveur afin qu'il ouvre le canal (event stream) :
Lorsque le canal est ouvert, le server envoie autant d'événement que nécessaire au client. Les événements sont illustrés par les étapes 2 à 7 sur le diagramme ci-dessus. Quand la communication est terminée, le serveur ferme le canal.
Pour comprendre comment fonctionne les SSE, je me suis efforcé d'écrire l'exemple le plus simple possible.
Dans cet exemple, le script côté client est composé d'un titre HTML, d'une liste vide (<ol>
) et
d'un code JavaScript. Ce code ouvre le canal avec le serveur. Toutes les 2 secondes, le serveur envoie un
événement contenant l'heure courante. Le message est alors ajouté dans la liste HTML.
Du côté client, le coeur du HTML est composé d'une liste vide :
<ol id="list">
</ol>
Le JavaScript ouvre le canal avec le serveur, ici le script sse.php
:
// Ouvre le canal avec le serveur, ici sse.php
var eventSource = new EventSource("sse.php");
Chaque fois que le serveur envoie un événement, l'événement onmessage
est déclenché la
donnée (event.data
) est ajoutée à la liste :
// Événement lorsqu'un message est reçu du serveur
eventSource.onmessage = function(event) {
// Ajoute le message dans la liste
document.getElementById("list").innerHTML += '<li>'+event.data + "</li>";
};
Côté serveur, le script PHP créer un flux d'événements (event stream). L'option no-cache
empèche
les navigateur de placer ce script en cache.
<?php
// Définit le type mime : event-stream
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
Dans ce simple exemple, the coeur du script est une boucle infinie :
// Boucle infinie jusqu'à ce que le client ferme l'onglet
while (true) {
Pour envoyer un événement, il suffit d'afficher une chaîne de caractères commençant
avec ""data:"
". Ici, nous envoyons l'heure et la date courante. Comme en PHP,
la sortie est bufferisée avant d'être envoyée vers el client, nous devons forcer la transmission
avec flush
.
// Echo time
$time = date('r');
echo "data: The server time is: {$time}\n\n";
// Flush buffer (force sending data to client)
flush();
Notons que dans cet exemple, le flux n'est jamais fermé puisque nous avons écrit une boucle inifine. Le flux se termine lorsque le client ferme l'onglet dans son navigateur. Parfois, les scripts PHP continuent de s'exécuter même si l'onglet est fermé. La ligne suivante permet d'éviter ce problème :
if(connection_aborted()) exit();
Notons que selon la configuration de PHP, le serveur peut bufferiser les données avant de
les transmettre au client. Il faut veiller à ce que les paramètres output_buffering
et zlib.output_compression
soient désactivés dans le fichier php.ini:
output_buffering = Off
zlib.output_compression = Off
Le code ci-dessous est le script client index.html
.
<h1>SSE demo with PHP</h1>
<ol id="list">
</ol>
<script>
// Create new event, the server script is sse.php
var eventSource = new EventSource("sse.php");
// Event when receiving a message from the server
eventSource.onmessage = function(event) {
// Append the message to the ordered list
document.getElementById("list").innerHTML += '<li>'+event.data + "</li>";
};
</script>
Le code ci-dessous est le script sse.php
qui génére les événements:
<?php
// Set file mime type event-stream
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// Loop until the client close the stream
while (true) {
// Echo time
$time = date('r');
echo "data: The server time is: {$time}\n\n";
// Flush buffer (force sending data to client)
flush();
// Wait 2 seconds for the next message / event
sleep(2);
}
?>
L'archive suivante contient le code source décrit sur cette page.