Premiers pas avec WebSocket et Node.js (et Socket.io)

Démonstration de l'envoi de notification à une page Web par WebSocket, avec Node.js à la source, sur un serveur, ou localement.

Nous allons voir qu'avec WebSocket, il est facile de créer une interface HTML 5 pour une application locale. D'autres applications sont aussi possibles comme par exemple la commande à distance d'un robot.

on et emit

L'API de socket.io contient deux méthodes pour échanger des données qui fonctionnent coté client ou coté serveur, emit et on. D'autres méthodes servent à démarrer la connexion.

emit
Envoi de données. Elle a pour paramètre un code comme par exemple "notification", et l'autre partie réagit à cette commande à travers une méthode on qui a le même code.
on
Répond à l'envoi de données par l'autre partie et appelle une fonction, fournie en paramètre, pour traiter ces données.
connect
Démarre une connexion avec le serveur, à la demande du client. Elle a pour paramètre le nom du serveur, par défaut on n'en met aucun.
listen
Crée un listener (écouteur), une fonction qui intercepte les interventions de la part du client.

Le script d'interface crée d'abord un objet serveur, puis un objet socket.io, puis un listener. Il envoie la page HTML au client.

La page web une fois affichée coté client crée un objet socket et établit la connexion:

var socket = io.connect();

socket.on('notification', function(x) { notification(x); });

La fonction notification est un handler qui attend la réponse du serveur, et traite les données que le serveur envoie. Dans notre exemple le traitement consiste à placer les données reçue dans un calque dont d'id est "storage".

Le serveur intercepte le requête du client avec la fonction on:

listener.sockets.on('connection', function (socket) { start(socket);} ); 

Il répond avec une commande emit:

socket.emit('notification', 'Server online via socket!'); 

Tout ceci s'opère au chargement de la page. Maintenant nous allons voir comment obtenir une réelle interaction entre l'utilisateur et le serveur.

Interaction avec le serveur

Si l'on veut une réelle interaction, il faut que la page envoie une requête au serveur à la demande de l'utilisateur, et qu'elle affiche les données transmises par le serveur en réponse.

Pour en faire la démonstration, on ajoute dans la page un bouton. Quand l'utilisateur presse ce bouton, la fonction JavaScript callserver est exécutée.
La page HTML alors démarre l'échange avec le script coté serveur avec une commande emit.

socket.emit('called', 'Nothing'); 

Le serveur intercepte la requête et en réponse à la commande 'called', envoie un nouveau message. Ce message est un ensemble de données au format JSON que nous avons figuré ici avec une chaîne de caractères':

socket.emit('notification', 'Yes still here! Want some data?');

Du coté client, c'est toujours le même handler qui reçoit les données et affiche le contenu.

socket.on('notification', function(x) { notification(x); });	

Dans la pratique, le contenu sera un ensemble clé-valeurs, les clés correspondant par exemple à des champs de texte dans la page HTML et la valeur étant le contenu à y afficher.

Voici le code source complet des deux fichiers...

Le script coté serveur, socket-mini.js

var uri="/socket-mini.html";
var fs = require('fs'),
    http = require('http'),
    socket = require("socket.io");

var page = fs.readFileSync(__dirname + uri);

function handler(request, response)
{
  response.write(page);
  response.end();
}
var app = http.createServer(function(r, s){ handler(r,s); });
app.listen(1000);
var listener = socket.listen(app, { log: false });

function start(socket)
{
  socket.emit('notification', 'Server online via socket!');
  socket.on('called', function(){
    console.log("Request received.");
    listener.sockets.emit('notification', 'Yes still here! Want some data?');
});
}
listener.sockets.on('connection', function (socket) { start(socket);} );

Pour démarrer le serveur, taper en ligne de commande:

node socket-mini.js

Le serveur ne répond qu'à deux types de requêtes, 'connection' et 'called', la première étant prédéfinie, la seconde ayant été crée par notre démonstration.
On peut créer autant de types de requêtes que l'on veut. Si on veut éviter de définir une fonction pour chacune, on peut placer dans les données envoyées avec 'called' un tableau de type clé-valeur, chaque clé correspond à une commande et chaque valeur fournissant les paramètres pour celle-ci.

Le script coté client, socket-mini.html

<!doctype html>
<html>
<head>
<meta charset=utf-8>
<title></title>
</head>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect();

function notification(content)
{
  var x= document.getElementById("storage");
  x.innerHTML = content;
  console.log(content);
}

socket.on('notification', function(x) { notification(x); });

function callserver()
{
  socket.emit('called', 'Nothing');
}
</script>

<body>
<h1>Notification from server by socket.io</h1>
<fieldset><legend>Messages received</legend>
<div id="storage"></div>
</fieldset>

<form name="form1" method="post" action="">
<input type="button"  value="Call server" onClick="callserver()">
</form>
</body>
</html>

Cette page est envoyée par le serveur et affichée par le navigateur quand on tape dans la barre d'URL du navigateur:

localhost:1000

Cette démonstration est limitée à une seule page HTML donnée en paramètre au serveur et assignée à la variable uri (ce qui suffit pour créer une application avec interface HTML). En se basant sur les précédents tutoriels, notamment Comment réaliser un serveur de pages avec Node.js, on peut le rendre plus générique et utiliser des pages HTML différentes avec le même script coté serveur. Une démonstration complète en est fournie par Advanced Explorer, dans la section Scripts.

Seuls nous intéressent pour cet article les fichiers socket-mini.html et socket-mini.js.