HTML5 & Web socket

HTML5

HTML5 apporte beaucoup au développeur en permettant, notamment, de donner plus de sens à la source.

De nombreux nouveaux tags sont désormais valides header, nav, footer, etc... et permettent une meilleure sémantique des pages. Ensuite, tous les tags HTML peuvent avoir des attributs personnalisés liés à l'application et suffixé de data-. On trouvera donc data-monAttribut plutôt que d'utiliser des classes inutiles et dénuées de sens.

Exemple de code HTML5

<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <title>here comes a title</title> <link rel="stylesheet" media="screen" href="ecran.css"> </head> <body> <header> <h1>Wake up sheeple!</h1> <p> <a href="news.html">News</a> - <a href="blog.html">Blog</a> - <a href="forums.html">Forums</a> </p> <p>Last Modified: <time>2009-04-01</time></p> <nav> <h1>Navigation</h1> <ul> <li><a href="articles.html">Index of all articles</a></li> <li><a href="today.html">Things sheeple need to wake up for today</a></li> <li><a href="successes.html">Sheeple we have managed to wake</a></li> </ul> </nav> </header> <div> <article> <header> <h1>My Day at the Beach</h1> </header> <div> <p>Today I went to the beach and had a lot of fun.</p> ...more content... </div> <footer> <p>Posted <time pubdate="" datetime="2009-10-10T14:36-08:00">Thursday</time>.</p> </footer> </article> ...more blog posts... </div> <footer> ... </footer> </body> </html>

Les APIs

De plus, HTML5 apporte une suite intéressantes d'APIs qui permettent permettraient de rendre les applications web encore plus performantes, indépendantes, etc :

Stockage en local de fichier

Base de données locales et accessible dans une pseudo version de SQL

Stockage de données indexée (sous la forme d'arbre)

Manipulation de fichier

Capture de données sur le device (vidéo / photos)

Push events

Les websockets

Géolocalisation

Web Workers (exécution de tâches de fond)

Mode déconnecté

De nombreux exemples concernant l'ensembles des APIs sont consultables sur http://slides.html5rocks.com/

L'interface WebSocket

L'interface proposée par le consortium W3C concernant les websockets est disponible à l'adresse: http://dev.w3.org/html5/websockets/

Elle est encore à l'état de draft (état au 24 mai 2011) mais certains navigateurs l'implémentent d'ores déjà; à savoir Chrome et Safari (y.c. la version mobile). La découverte d'une faille de sécurité relativement importante lors de la phase de handshake, certains browsers ont décidé de désactivé l'API pour le moment; c'est notamment le cas de firefox 4.

L'interface proposée par le W3C se présente de la manière suivante:

interface WebSocket { readonly attribute DOMString url; // ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSING = 2; const unsigned short CLOSED = 3; readonly attribute unsigned short readyState; readonly attribute unsigned long bufferedAmount; // networking attribute Function onopen; attribute Function onmessage; attribute Function onerror; attribute Function onclose; readonly attribute DOMString protocol; void send(in DOMString data); void close(); };

Côté serveur

Dans le cadre des quelques démonstrations qui vont suivre, nous allons utiliser une librarie php disponible sur github. Toutefois, il existe déjà d'autres implémentations de serveur websockets en Java, en Ruby,...

Côté client

Afin de pouvoir satisfaire tout le monde (c-à-d ceux qui n'utilisent pas des browser modernes Safari ou Chrome :) ), il est possible d'établir une connexion persistante entre le serveur et ces clients via un fichier swf. Par contre, l'utilisation de cette méthode nécessite l'ajout de quelques classes et fichiers pour le client.

Exemples

1 - Date like in ajax

Lien sur l'application

Description

Le fonctionnement de cette application permet de faire la transition entre les requêtes ajax et les websockets.

Pour ce faire, on va établir une connexion persistante entre le client et le server mais les données ne transiterons qu'à la demande du client. Lorsque le client appellera le serveur, celui-ci lui renverra l'heure actuelle et elle sera ensuite affichée chez le client.

Coté Client

Dans cet exemple, le code javascript est très simple. On va créer un nouvel objet WebSocket fourni par l'API du browser et ne fonctionnera que sur Chrome, Safari et Safari mobile.

var myWebsocket = new WebSocket(wsUri);

Le reste du code va simplement permettre de définir les méthodes onopen, onclose, onmessage et onerror de l'objet websocket. On peut noter que la grande partie du code est utilisé pour l'esthétique de l'interface.

myWebsocket.onopen = function (evt) { console.log('Websocket open.'); $('#status').removeClass().addClass('online').html('online'); writeToScreen('connected to '+ wsUri) $('#send').attr('disabled', false); } myWebsocket.onclose = function(evt) { console.log('Websocket closed.'); $('#status').removeClass().addClass('offline').html('offline'); $('#send').attr('disabled', true); } myWebsocket.onmessage = function(evt) { console.log('Message received.') writeToScreen('Received: ' + evt.data); output.animate({scrollTop: output.attr('scrollHeight')}) } myWebsocket.onerror = function(evt) { console.error('Error') $('#status').removeClass().addClass('error').html('error'); }

Côté serveur

Du point de vue du serveur, tout est très simple. Nous avons enregistré une nouvelle application et avons définis quelques méthodes au sein de celle-ci. Cette application va gérer la (dé)connexion des clients et leur répondre la date uniquement à leur demande.

class DateApplication extends Application { private $clients = array(); private $lastTime = 0; public function onConnect($client) { $this->clients[] = $client; } public function onDisconnect($client) { $key = array_search($client, $this->clients); if ($key) { unset($this->clients[$key]); } } public function onData($data, $client) { $client->send(date(DATE_RFC822)); } }

2 - Date from server

Lien sur l'application

Description

Le fonctionnement de cette application diffère de l'exemple précédent. Cette fois-ci, c'est le serveur qui va interagir avec le(s) client(s) connecté(s). Lors de l'ouverture de l'exemple, l'application va ouvrir une connexion sur le serveur. Celui-ci va enregistrer le client dans une liste puis, toutes les 3 secondes, le serveur va transmettre la date à tous les clients

Coté Serveur

public function onTick() { if (time() >= $this->lastTime + 3) { $this->lastTime = time(); foreach ($this->clients as $sendto) { $sendto->send(date(DATE_RFC822)); } } }

3 - Simple IRC

Lien sur l'application

Description

Cet exemple a pour but de créer un mini chat. Il faut alors que le serveur soit en mesure :

D'enregistrer les nouveaux clients.

Broadcaster l'arrivée de quelqu'un.

Broadcaster les messages des utilisateurs

De leur côtés, les clients doivent être en mesure de :

Effectuer une connexion au serveur

Transmettre des messages à tout le monde

Récupérer les messages

Côté Client

var wsUri = "ws://monserveur:monport/echo"; var output; $(document).ready(function() { output = $("div#log"); initWebSocket(); $('#send').click(function(){ console.log('Message sent: ' + $('#message').val()); writeToScreen("Websocket called"); websocket.send($('#message').val()); }); }); function initWebSocket() { websocket = new WebSocket(wsUri); websocket.onopen = function (evt) { console.log('Websocket open.'); $('#status').removeClass().addClass('online').html('online'); writeToScreen('connected to '+ wsUri) $('#send, #message').attr('disabled', false); } websocket.onclose = function(evt) { console.log('Websocket closed.'); $('#status').removeClass().addClass('offline').html('offline'); $('#send, #message').attr('disabled', true); $('#connection').text('Connect'); setTimeout(initWebSocket, 1000); } websocket.onmessage = function(evt) { console.log('Message received.') var data = JSON.parse(evt.data); writeToScreen('['+data.who+']: ' + data.msg); output.animate({scrollTop: output.attr('scrollHeight')}) } websocket.onerror = function(evt) { console.error('Error') $('#status').removeClass().addClass('error').html('error'); } } function writeToScreen(message) { var pre = document.createElement("pre"); pre.style.wordWrap = "break-word"; pre.innerHTML = message; output.append(pre); }

Côté Serveur

public function onData($data, $client) { foreach ($this->clients as $sendto) { $obj = (object) null; $obj->who = $client->toString(); $obj->msg = $data; $sendto->send(json_encode($obj)); } }

4 - IRC (all browser compliant)

Lien sur l'application

Vouloir utiliser les web sockets pourrait, à plus ou moins long terme, devenir très intéressant. En attendant que tous les navigateurs acceptent et s'accordent sur l'API (et plus généralement sur toutes les APIs d'HTML5), il est nécessaire trouver des solutions afin de faire fonctionner les sockets d'une manière plus universelle.

Tout d'abord, on profitera de l'occasion, pour également intégrer la classe JSON qui n'est pas native sur certain browser.

<!-- Needed for old browser --> <script src="js/json2.js"></script>

En ce qui concerne les websockets, la solution qui convient le mieux pour les websockets est l'utilisation d'un composant flash. Hiroshi Ichikawa avec son projet https://github.com/gimite/web-socket-js propose une librarie toute faite qui ne demande qu'une modification mineur du code pour rendre fonctionnel l'objet websocket. On notera par contre, un léger délai sur la réception des données pour les browsers utilisant le composant flash.

<!-- Needed for browser who do not implement WebSocket --> <script src="js/swfobject.js"></script> <script src="js/FABridge.js"></script> <script type="text/javascript"> WEB_SOCKET_SWF_LOCATION = "js/WebSocketMain.swf"; </script>

Les exercices

Hash me!

En se basant sur l'exemple 1 et en modifiant ex-hash-template.zip, faire en sorte que le client envoie une chaine de caractères au serveur. Celui-ci lui renverra le hash md5 de la valeur au client qui l'affichera.

IRC++

En partant de ex-irc-template.zip et de l'exemple 3, faire en sorte que :

Les clients ne reçoivent pas leurs propres message. Il sera toutefois affiché sur l'interface par le javascript.

Les clients peuvent mettre à jour leur nom d'utilisateur. Celui-ci sera transmis dans la liste des utilisateurs

Les clients reçoivent et mettent à jour la liste des personnes connectées uniquement lorsque cela est nécessaire