Node.js pour les développeurs jQuery

La puissance de jQuery peut aussi servir côté serveur…

Marc Buils

Node.js est souvent associé à la notion de JavaScript Natif (développement JavaScript sans utiliser de framework), ce qui peut faire fuir de nombreux développeurs formés sur jQuery.

Je propose donc à tous ces développeurs, qui s'intéressent à Node.js mais qui ne veulent pas perdre la puissance et l'expérience acquise sur jQuery, cet article qui les guidera pas à pas et de façon ludique dans le développement d'un système de messagerie instantanée.

Commentez

- Titre : Node.js pour les développeurs jQuery

- Auteur : Marc Buils

- Parution : 3 décembre 2012

- Mise à jour : 10 décembre 2012

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2012 Marc Buils. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Présentation

- III. Installation

- IV. Votre premier serveur HTTP

- IV-A. Rappel sur la communication Navigateur/Serveur

- IV-B. Fonctions nécessaires au développement du serveur

- IV-B-1. Inclusion d'un module JavaScript

- IV-B-2. Module http

- IV-B-3. Exécution d'un script Node.js

- IV-C. Exercice

- IV-D. Exemple de solution

- V. Développement d'un serveur HTTP générique

- V-A. Quelques rappels sur jQuery

- V-A-1. Encapsulation jQuery

- V-A-2. Plugin jQuery

- V-A-3. .data

- V-B. Fonctions nécessaires au développement du serveur

- V-B-1. Module url

- V-B-2. Module fs

- V-B-3. Installation de module

- V-C. Exercice

- V-D. Exemple de solution

- VI. Développement d'un serveur WebSocket générique

- VI-A. Quelques rappels sur jQuery

- VI-A-1. .on() et .trigger()

- VI-B. Fonctions nécessaires au développement du serveur

- VI-B-1. Module websocket.server

- VI-C. Exercice

- VI-D. Exemple de solution

- VII. Développement d'un système de messagerie instantanée

- VII-A. Exercice

- VII-B. Exemple de solution

- VIII. Conclusion

- IX. Remerciements

I - Introduction

Node.js pour les développeurs jQuery est un article destiné aux développeurs front end qui souhaitent se lancer dans l'aventure du développement JavaScript côté serveur.

Vous allez découvrir, pour ceux qui en doutaient encore, que la réputation de jQuery ne tient pas seulement à sa faculté de manipuler facilement les éléments du DOM ou d'être compatible avec la majorité des navigateurs. Mais il la tient aussi par ses patterns, son concept d'objet unique, son système de plugins et surtout sa faculté à être simple, accessible et en perpétuelle évolution.

Cet article est articulé autour d'exercices et d'exemples de solutions qui vont vous permettre d'appréhender quelques concepts de Node.js et jQuery, tout en développant des briques logicielles qui pourront vous suivre sur vos futurs projets.

Les exemples de code décrits dans cet article sont téléchargeables ici ou disponibles sur github.

Les descriptions d'API données dans cet article ne sont pas forcément complètes.
Elles se limitent aux fonctionnalités nécessaires pour suivre les exemples de l'article.
N'hésitez pas à visiter la documentation officielle des API pour avoir une description exhaustive.

II - Présentation

Au même titre que les interpréteurs Perl, Python ou encore PHP, Node.js est un interpréteur pour le langage JavaScript. Basé sur le moteur JavaScript V8 de Chrome, il fait partie des moteurs JavaScript les plus performants.

À l'inverse du PHP qui se greffe sur un serveur Web (Apache, IIS…) afin de s'acquitter de la gestion du protocole HTTP, Node.js propose de créer votre propre serveur Web ou WebSocket grâce à son framework qui, heureusement, vous simplifie grandement la tâche.

Cependant, rien ne vous oblige à créer votre serveur Web en JavaScript. Node.js peut aussi servir de complément à un serveur Web plus robuste afin de ne gérer que la partie communication temps réel (sa spécialité...).

III - Installation

L'installation étant assez simple que ce soit sous Windows, Mac OS X ou Linux, je ne m'étendrais pas sur ce paragraphe. Sachez seulement que Node.js fait office de serveur Web. À ce titre, il doit être installé sur la machine qui tiendra le rôle de serveur.

Vous pouvez télécharger Node.js directement sur son site internet :
http://nodejs.org/

IV - Votre premier serveur HTTP

À la fin de ce chapitre, vous comprendrez le mécanisme de Node.js et vous serez capable de créer un serveur HTTP de base.

IV-A - Rappel sur la communication Navigateur/Serveur

Avant d'aller plus loin, il faut bien comprendre le principe de communication entre le navigateur internet de l'utilisateur et le serveur Web.

Voici ce qui se passe lorsqu'un utilisateur entre une URL sur son navigateur internet :

IV-B - Fonctions nécessaires au développement du serveur

IV-B-1 - Inclusion d'un module JavaScript

La fonction require permet d'inclure un fichier JavaScript. À la différence des fichiers JavaScript côté client, avec Node.js on parle de « module ».

Un « module » peut retourner un résultat qui, dans le framework de Node.js, est en général un objet à instancier.

Pour que votre module retourne une valeur, il suffit de donner cette valeur à la variable globale module.exports.
Nous n'étudierons pas plus cette notion dans cet article, mais il important de la connaître car le framework de Node.js est basé sur ce principe.

La fonction require ne recharge pas les modules à chaque fois, mais elle ne les charge que lors du premier appel.
Par la suite, elle se contente de retourner l'instance stockée.

IV-B-2 - Module http

Le module http est intégré de base avec Node.js. Il retourne un objet JavaScript qui permet de créer un serveur HTTP facilement.

Voici le code permettant d'inclure le module http :

1. var http = require('http') ;

Pour créer l'instance de votre serveur, vous devez utiliser la commande http.createServer( callback( request, response ) ).

Exemple d'utilisation :

1. var callback = function( request, response ){

2. // traitement de la requête

3. };

4. var server = http.createServer( callback ) ;

La fonction callback est appelée à chaque fois qu'un utilisateur appelle une page depuis son navigateur.

La commande précédente permet de créer l'instance du serveur, mais il faut maintenant que ce dernier se mette en écoute sur un port IP. C'est le rôle de la commande listen( port, name ).

Exemple d'utilisation :

1. server.listen( 80, '127.0.0.1' ) ;

Enfin, il y a les commandes response.writeHead et response.end qui permettent respectivement de renvoyer la partie HEADER du protocole HTTP et la partie données.

La partie HEADER du protocole HTTP est invisible pour l'utilisateur mais nécessaire au navigateur, entre autres pour indiquer de quelle manière il doit afficher les données (page HTML, page Ajax, fichier ZIP…).

Exemple d'utilisation de response.writeHead( code, options ) :

1. response.writeHead( 404, {'Content-Type': 'text/plain'} ) ;

Le paramètre code correspond au code HTTP de retour. Les plus utilisés sont le code 200 = statut OK, et le code 404 = fichier non trouvé.
Vous trouverez sur Wikipedia la liste complète des codes HTTP.

'Content-Type' correspond au type de fichier renvoyé par le serveur.
Vous en trouverez ici une liste non exhaustive.

Exemple d'utilisation de response.end( data ) :

1. response.end( 'Fichier non trouvé' ) ;

IV-B-3 - Exécution d'un script Node.js

Pour exécuter un script Node.js, il suffit d'exécuter la commande suivante :

1. node {nom_du_fichier}

L'extension '.js' n'est pas obligatoire.
Pour un fichier 'fichier.js' vous pouvez donc l'exécuter avec la commande node fichier.js ou encore node fichier.

IV-C - Exercice

Vous devez créer dans le fichier hello-world.js un serveur HTTP qui retourne Hello World quelle que soit la page demandée par l'utilisateur.

Le serveur doit écouter le port 1337.

IV-D - Exemple de solution

Pour créer votre serveur, suivez les actions suivantes :

- créez le répertoire de notre projet ;

- dans ce répertoire, créez un fichier hello-world.js ;

- puis insérez le code suivant dans le fichier hello-world.js :

hello-world.js

1. /**

2. * Simple Hello World

3. */

4. ;(function( http /* variable qui reçoit le module http */ ){

5. // Création du serveur HTTP

6. http.createServer( function (req, res) { // fonction de callback appelée pour chaque appel de page

7. // code de statut 200 (OK) et retourne un fichier de type 'text/plain'

8. res.writeHead(200, {'Content-Type': 'text/plain'});

10. // Le fichier retourné contient le texte 'Hello World'

11. res.end('Hello World\n');

12. } )

13. .listen(1337, "127.0.0.1");     // Le serveur écoutera le port 1337

14. })( require('http') /* inclue le module http */ );

- ouvrez un terminal dans le répertoire de votre projet ;

- exécutez la commande suivante : node hello-world.js;

- lancez votre navigateur Web préféré sur l'URL http://127.0.0.1:1337.

Vous devez avoir une page comme celle-ci qui s'affiche :

Vous remarquerez que notre code est encapsulé dans une fonction anonyme. Ce n'est pas obligatoire, mais c'est pratique pour fournir les modules nécessaires à notre code tout en créant un contexte de développement dédié.

Vous remarquerez également que le premier caractère (en dehors des commentaires) est un ';'.
Ça permet de protéger votre code contre les scripts qui se terminent sans ';' dans le cas où vous utiliseriez un outil pour compresser tous vos scripts en un seul et unique fichier.

V - Développement d'un serveur HTTP générique

Maintenant que vous maitrisez le concept de Node.js, nous allons pouvoir intégrer les notions de jQuery qui vont vous permettre de faire évoluer votre serveur HTTP actuel vers un serveur plus générique comme Apache (mais en moins poussé bien sûr).

V-A - Quelques rappels sur jQuery

Avant de commencer nos développements jQuery, nous allons commencer par quelques petits rappels sur certaines spécificités de jQuery.

V-A-1 - Encapsulation jQuery

Lorsqu'on développe côté front end, on utilise généralement la fonction jQuery avec deux types de données :

- les chaines de caractères, avec une expression CSS afin de sélectionner un élément du DOM ou une liste d'éléments du DOM contenus dans notre page HTML (exemple : $('#id_mon_element')) ;

- des fonctions qui sont appelées lorsque le document est initialisé (exemple : $(function(){ /* code appelé une fois la page complètement chargée */ })).

Mais la fonction jQuery peut aussi être appelée avec d'autres types de données, comme des structures (exemple : $({})) ou des tableaux (exemple : $([{}, {}])).
L'intérêt de cette utilisation côté front end est limité à des cas très particuliers. Par contre vous verrez que pour un développement Node.js, cette faculté va nous être particulièrement utile.

V-A-2 - Plugin jQuery

Une autre particularité de jQuery qu'on apprécie beaucoup côté front end, est sa gestion des plugins. Encore une fois, côté client, on se limite plus souvent à utiliser des plugins existants plutôt qu'à en créer de nouveaux.

Sur Node.js ce sera l'inverse, on va plutôt avoir tendance à en créer. Surtout aujourd'hui où le nombre de plugins jQuery pour Node.js est très limité.

Voici un petit rappel sur la façon (plutôt une des façons) de créer un plugin jQuery :

1. ;(function($){

2. $.fn.notrePlugin = function(/* liste des paramètres si nécessaire */){

3. // 'this' correspond ici à l'objet jQuery auquel s'applique le plugin (celui qui l'a appelé)

4. this.each(function(){

5. // ici, 'this' correspond à la valeur en cours de l'itération

6. }) ;

8. return this ; // on retourne toujours notre objet jQuery pour ne pas casser la chaine

9. } ;

10. )(jQuery) ;

Donc si on utilise jQuery avec des structures, ça pourrait donner :

1. // on encapsule notre code

2. ;(function($){

3. // on crée notre plugin

4. $.fn.notrePlugin = function(){

5. return this.each(function(){

6. console.log( 'mon_texte : %s', this.mon_texte ) ; // on affiche le paramètre mon_texte de la structure contenue dans 'this'

7. }) ;

8. } ;

10. // on appelle notre plugin sur une structure

11. $({

12. mon_texte : 'ma première structure'

13. }).notrePlugin() ; // Affiche dans la console 'mon_texte :  ma première structure'

15. // on appelle notre plugin 2 fois sur une structure

16. $({

17. mon_texte : 'je suis appelé 2 fois'

18. }) .notrePlugin()

19. .notrePlugin() ; // Affiche 2 fois la ligne 'mon_texte :  je suis appelé 2 fois'

21. // on appelle notre plugin sur un tableau de structures

22. $([

23. {

24. mon_texte : 'Je suis la première structure'

25. },

26. {

27. mon_texte : 'Je suis la seconde structure'

28. }

29. ]).notrePlugin() ; // Affiche dans la console 'mon_texte :  Je suis la première structure' puis 'mon_texte :  Je suis la seconde structure'

31. })(jQuery) ;

V-A-3 - .data

Une autre fonctionnalité de jQuery qu'on va utiliser dans cet article est la fonction .data. Cette fonction nous permet d'enregistrer des informations sur un objet encapsulé par jQuery.

Voici un exemple d'utilisation :

1. var mon_objet = {} ;

3. $(mon_objet).data('donnee', 'Donnée enregistrée') ;

4. console.log( $(mon_objet).data('donnee') ) ; // Affiche dans la console 'Donnée enregistrée'

V-B - Fonctions nécessaires au développement du serveur

V-B-1 - Module url

Le module url permet de simplifier la lecture d'une URL. Pour votre serveur HTTP, je vous propose d'utiliser la fonction url.parse afin de récupérer le pathname de l'URL.

Exemple d'utilisation de la fonction url.parse( url ) :

1. var url = require('url') ;

2. var request_url = 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash';

4. console.log( url.parse( request_url ).pathname ) ; // Affiche '/p/a/t/h'

Vous pouvez récupérer l'URL de la page demandée, à l'intérieur de la callback de la fonction http.createServer( callback( request, response ) ) grâce au champ request.url.

V-B-2 - Module fs

Le module fs (File System) permet d'accéder au système de fichier du serveur. Il peut, entre autres, nous permettre de lire un fichier grâce à la fonction fs.readFile.

Exemple d'utilisation de la fonction fs.readFile( filename, callback( error, data ) ) :

1. fs.readFile( './www/index.html', function( error, data ){

2. if (error) {

3. console.error('Problème de lecture ou fichier non existant') ;

4. } else {

5. console.log( data ) ; // Affiche le contenu du fichier ./www/index.html

6. }

7. }) ;

V-B-3 - Installation de module

Lors de l'installation de Node.js, vous avez aussi installé NPM (Node Packaged Modules). Cet outil permet d'installer des modules complémentaires pour votre projet.

Utilisation de NPM pour installer un module :

1. npm install {nom_du_module}

Vous devez ouvrir un terminal dans le répertoire de votre projet avant d'exécuter cette commande.

Vous trouverez la liste complète des modules NPM sur https://npmjs.org/
Vous pourriez, entre autres, avoir besoin des modules jquery ou websocket…

La plupart des frameworks existants pour une utilisation front end ne sont pas compatibles directement avec Node.js à cause de la notion de DOM qui n'existe pas côté serveur.
C'est par exemple le cas de jQuery. Heureusement, ces bibliothèques sont généralement disponibles via NPM !

V-C - Exercice

L'objectif de cet exercice est de faire un serveur HTTP qui retourne les pages demandées par l'utilisateur si elles existent dans le répertoire www. Dans le cas contraire, il retourne la fameuse erreur 404 indiquant que le fichier n'a pas été trouvé.

Ce serveur devra se présenter sous la forme d'un plugin jQuery, à appliquer sur une structure contenant un champ port.
Ce champ port devra correspondre au port écouté par votre serveur.

Pour vous aider, voici un schéma qui décrit les étapes que doit suivre votre serveur :

Et voici le script starthttp.js qui sera à exécuter avec Node .js :

starthttp.js

1. ;(function( $ ){

2. $({

3. port:     8090

4. })

5. .httpserver();

6. })( require('jquery'),             // charge jQuery

7. require('./src/jquery.httpserver') );    // charge votre serveur HTTP

Et une petite page HTML pour tester votre serveur :

www/hello-world.html

1. <!DOCTYPE html>

2. <html>

3. <head>

4. <meta charset="utf-8" />

5. <title>Exemple 2: Votre premier serveur HTTP générique</title>

6. </head>

7. <body>

8. <h1>Hello World</h1>

9. </body>

10. </html>

V-D - Exemple de solution

Voici les actions à suivre :

- dans le répertoire de votre projet, créez un fichier starthttp.js ;

- insérez le code suivant dans ce fichier starthttp.js :

starthttp.js

1. ;(function( $ ){

2. $({

3. port:     8090

4. }).httpserver();

5. })(     require("jquery"),             // charge jQuery

6. require('./src/jquery.httpserver') ) ;     // charge votre serveur HTTP

- créez un répertoire src ;

- puis créez un fichier src/jquery.httpserver.js contenant le code suivant :

src/jquery.httpserver.js

1. ;(function(http, url, fs, $){

2. $.fn.httpserver = function( ){

3. return this.each(function(){

4. var _options = $.extend( {}, {

5. port: 80,                // port par défaut

6. path: './www',            // répertoire par défaut

7. types: {                // liste des types de fichier acceptes

8. "html":    'text/html'

9. }

10. }, this ); // merge les options de la structure 'this' avec les options par défaut

12. // Création du serveur HTTP

13. var _serveur = http.createServer( function (req, res) { // fonction de callback appelée pour chaque appel de page

14. var _pathname = url.parse(req.url).pathname;

15. var _ext = _pathname.split('/');

16. _ext = _ext[_ext.length-1].split('.');

17. _ext = ( _ext.length < 2 ? '' : _ext[_ext.length-1] );

19. fs.readFile( _options.path + _pathname, function(err, data){

20. if ( err ){

21. // code de statut 404 (FILE_NOT_FOUND) et retourne un fichier de type 'text/plain'

22. res.writeHead(404, {'Content-Type': 'text/plain'});

23. // Affiche une erreur

24. res.end('Ereur 404: Fichier non trouve\n');

25. } else {

26. // code de statut 200 (OK) et retourne un fichier de type 'text/plain'

27. res.writeHead(200, {'Content-Type': typeof(_options.types[ _ext ]) != "undefined" ? _options.types[ _ext ] : 'text/plain' });

28. // retourne le fichier trouvé

29. res.end( data );

30. }

31. });

32. } )

33. .listen( _options.port, '127.0.0.1' );    // Le serveur écoutera le port 8090

35. // Partage le serveur

36. $(this).data( 'httpserver', _serveur );

37. });

38. };

39. })(    require('http'),     // charge le module http

40. require('url'),         // charge le module url

41. require('fs'),         // charge le module file system

42. require('jquery') );    // charge le module jQuery

- créez ensuite un répertoire www ;

- puis un fichier www/hello-world.html avec le code suivant :

www/hello-world.html

1. <!DOCTYPE html>

2. <html>

3. <head>

4. <meta charset="utf-8" />

5. <title>Exemple 2: Votre premier serveur HTTP générique</title>

6. </head>

7. <body>

8. <h1>Hello World</h1>

9. </body>

10. </html>

- ouvrez un terminal dans le répertoire de votre projet ;

- exécutez la commande : npm install jquery;

- exécutez la commande : node starthttp.js;

- enfin, lancez votre navigateur Internet préféré sur l'URL http://127.0.0.1:8090/hello-world.html.

Vous devez avoir une page comme celle-ci qui s'affiche :

Vous remarquerez que pour utiliser jQuery, on ne donne pas en paramètre l'objet jQuery comme on l'aurait fait pour du JavaScript client, mais on utilise la commande require.
En Node.js, jQuery est un module qui retourne l'objet jQuery.

On charge un module ./src/httpserver mais on ne récupère pas la valeur de retour dans une variable comme on peut le faire pour le module jQuery. httpServeur est en fait un plugin jQuery qu'on a développé pour créer notre serveur Web. Et comme en jQuery, tous les plugins sont attachés à l'objet jQuery, notre module ne retourne aucune valeur.

Vous remarquerez la syntaxe var _options = $.extend( {}, { /* options par défaut */ }, this ). Pour le même résultat, on aurait pu écrire var _options = $.extend( { /* options par défaut */ }, this ).
Cependant la première syntaxe met en avant le fait que le premier paramètre est en lecture/écriture (il reçoit la même valeur que la variable _options).

Vous pouvez vous passer de la variable _options en mergeant directement les options sur notre structure this.
Mais attention, ça modifie votre structure d'origine ce qui pourrait impacter le fonctionnement des autres plugins jQuery utilisés sur cette même structure.

Vous remarquerez aussi dans le code source qu'on enregistre notre instance de serveur sur l'objet this via la fonction .data de jQuery. Cette instance est nécessaire à la création d'un serveur WebSocket que nous allons créer dans le prochain exemple.

VI - Développement d'un serveur WebSocket générique

VI-A - Quelques rappels sur jQuery

VI-A-1 - .on() et .trigger()

Dans cet exercice, nous allons intégrer la notion d'évènement grâce aux fonctions on et trigger dans leur utilisation la plus basique.

Donc voici des petits exemples d'utilisation histoire de faire une petite piqure de rappel :

1. // Exemple d'utilisation de .on et .trigger

2. ;(function($){

3. var ma_structure = {} ;

5. // Enregistrement d'un évènement sans paramètre

6. $(ma_structure).on('mon_evenement', function(){

7. console.log('Évènement mon_evenement est appelé') ;

8. }) ;

10. // Appel de l'évènement sans paramètre

11. $(ma_structure).trigger('mon_evenement') ; // affiche dans la console : 'Évènement mon_evenement est appelé'

14. // Enregistrement d'un évènement avec paramètres

15. $(ma_structure).on('mon_evenement_avec_params', function(event, param1, param2){

16. console.log('Évènement mon_evenement_avec_params est appelé (param1 : %s, param2 : %s)', param1, param2) ;

17. }) ;

19. // Appel de l'évènement avec paramètres

20. $(ma_structure).trigger('mon_evenement_avec_params', ['paramètre 1','paramètre 2 ']) ; // affiche dans la console : 'Évènement mon_evenement_avec_params est appelé (param1 : paramètre 1, param2 : paramètre 2)'

21. })(jQuery) ;

VI-B - Fonctions nécessaires au développement du serveur

VI-B-1 - Module websocket.server

Le module websocket retourne deux objets à instancier : websocket.server et websocket.client.

Pour notre exemple, seul websocket.server va nous intéresser puisqu'il permet de créer un serveur WebSocket.

Voici un exemple d'utilisation de websocket.server :

1. var wsserver = new WebSocketServer({

2. httpServer: $_this.data('httpserver') // instance du serveur HTTP

3. });

5. // Surveille les évènements

6. wsserver

7. .on('request', function(request) {

8. // accepte la nouvelle connexion

9. var connection = request.accept(null, request.origin);

11. // écoute les événements de la connexion

12. connection

13. .on('message', function(message) {

14. // réception d'un message

15. if (message.type === 'utf8') {

16. // affiche le message reçu

17. console.log('Message reçu : %s',  message.utf8Data) ;

18. }

19. })

20. .on('close', function(connection) {

21. // fermeture de connection

22. console.log('Une connexion a été coupée') ;

23. });

24. });

Vous avez peut-être remarqué que pour créer un serveur WebSocket, il faut fournir l'instance d'un serveur HTTP.
Cela vient du fait qu'avant d'ouvrir une connexion WebSocket, la demande d'ouverture est faite via le protocole HTTP. C'est seulement dans un second temps que la socket HTTP ouverte est transformée en socket WebSocket.

VI-C - Exercice

Pour cet exercice, vous devez créer un plugin jQuery wsserveur qui ouvre une connexion WebSocket. Pour ce faire, vous allez pouvoir réutiliser le serveur HTTP que vous avez développé pour l'exercice précédent.

Votre plugin doit faire remonter l'évènement request sur la structure qui contient la configuration du serveur, avec en paramètre la connexion ouverte.
Il doit également faire remonter sur cette connexion les évènements message et close lorsque la connexion reçoit un nouveau message depuis l'IHM ainsi que lorsqu'elle est coupée.

Voici le script d'initialisation startws.js à exécuter via Node.js :

startws.js

1. ;(function( $ ){

2. $({

3. port:     8091,

4. types: {

5. "js":    'text/js',

6. "html":    'text/html'

7. }

8. })

9. .httpserver()

10. .wsserver()

11. .on('request', function( event, _connection ){

12. console.log('Nouvelle connexion');

14. $( _connection )

15. .on('message', function(event, message){

16. console.log('Nouveau message: %s', message);

17. })

18. .on('close', function(){

19. console.log('Une connexion s\'est fermee');

20. });

21. });

22. })( require('jquery'),                 // charge jQuery

23. require('./src/jquery.httpserver'),     // charge votre serveur HTTP

24. require('./src/jquery.wsserver') );    // charge votre serveur WebSocket

Et voici une page HTML ws.html de test :

www/ws.html

1. <!doctype html>

2. <html>

3. <head>

4. <meta charset="utf-8" />

5. <title>Demo Node.js avec jQuery</title>

6. <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>

7. <script src="ws.js"></script>

8. </head>

9. <body>

10. <h1>Node.js pour les developpeurs jQuery</h1>

11. <input type="text" id="texte" /><input type="button" id="envoyer" value="Envoyer" />

12. </body>

13. </html>

Ainsi que le code JavaScript ws.js lié à la page HTML :

www/ws.js

1. ;(function($){

2. $(function(){

3. window.WebSocket = window.WebSocket || window.MozWebSocket;

5. var _connection = new WebSocket('ws://' + document.URL.substr(7).split('/')[0]);

7. $('#envoyer').click(function(){

8. _connection.send( $('#texte').val() );

9. });

10. });

11. })(jQuery);

Vous pouvez remplacer la double closure :
;(function($){
$(function(){
// code exécuté une fois la page chargée
}) ;
})(jQuery) ;
Par :
;jQuery(function($){
// code exécuté une fois la page chargée
}) ;
Personnellement, je trouve que la première écriture permet de bien séparer le code jQuery d'une part puis le code à exécuter une fois la page chargée d'autre part.

VI-D - Exemple de solution

Suivez les actions suivantes :

- dans le répertoire de votre projet, créez un fichier startws.js ;

- insérez le code suivant dans ce fichier startws.js :

startws.js

1. ;(function( $ ){

2. $({

3. port:     8091,

4. types: {

5. "js":    'text/js',

6. "html":    'text/html'

7. }

8. })

9. .httpserver()

10. .wsserver()

11. .on('request', function( event, _connection ){

12. console.log('Nouvelle connexion');

14. $( _connection )

15. .on('message', function(event, message){

16. console.log('Nouveau message: %s', message);

17. })

18. .on('close', function(){

19. console.log('Une connexion s\'est fermee');

20. });

21. });

22. })( require('jquery'),                 // charge jQuery

23. require('./src/jquery.httpserver'),     // charge votre serveur HTTP

24. require('./src/jquery.wsserver') );    // charge votre serveur WebSocket

- créez un fichier src/jquery.wsserver.js contenant le code suivant :

src/jquery.wsserver.js

1. ;(function( WebSocketServer, $ ){

2. $.fn.wsserver = function(){

3. return this.each(function(){

4. var $_this = $(this);

5. var _wsserver = null;

6. var _connections = {

7. list: []

8. };

10. try {

11. // vérifie si le serveur Web a bien été initialisé

12. if ( typeof( $_this.data('httpserver') ) == "undefined" ){

13. throw "Serveur HTTP non initialisé";

14. }

16. // Partage la liste des connexions

17. $_this.data('connections', _connections);

19. // Crée le serveur de WebSocket

20. _wsserver = new WebSocketServer({

21. httpServer: $_this.data('httpserver')

22. });

24. // Surveille les évènements

25. _wsserver

26. .on('request', function(request) {

27. var _connection = request.accept(null, request.origin);

29. // ajoute la nouvelle connexion à la liste des connexions

30. _connections.list.push( _connection );

32. // diffuse l'information d'une nouvelle connexion

33. $_this.trigger('request', [_connection])

35. // écoute les événements de la connexion

36. _connection

37. .on('message', function(message) {

38. if (message.type === 'utf8') {

39. // diffuse le message reçu

40. $(_connection).trigger('message', [message.utf8Data]);

41. }

42. })

43. .on('close', function(connection) {

44. // suppression de la connection de la liste

45. _connections.list.splice(_connections.list.indexOf(_connection), 1);

47. // diffuse la fermeture de connection

48. $(_connection).trigger('close');

49. });

50. });

52. } catch( e ) {

53. console.error( "Erreur du plugin wsserver: %s", e )

54. }

55. });

56. }

57. })(    require('websocket').server,

58. require('jquery') );

- créez une page www/ws.html avec le code suivant :

www/ws.html

1. <!doctype html>

2. <html>

3. <head>

4. <meta charset="utf-8" />

5. <title>Demo Node.js avec jQuery</title>

6. <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>

7. <script src="ws.js"></script>

8. </head>

9. <body>

10. <h1>Node.js pour les developpeurs jQuery</h1>

11. <input type="text" id="texte" /><input type="button" id="envoyer" value="Envoyer" />

12. </body>

13. </html>

- puis un fichier www/ws.js :

www/ws.js

1. ;(function($){

2. $(function(){

3. // if user is running mozilla then use it's built-in WebSocket

4. window.WebSocket = window.WebSocket || window.MozWebSocket;

6. var _connection = new WebSocket('ws://' + document.URL.substr(7).split('/')[0]);

8. $('#envoyer').click(function(){

9. _connection.send( $('#texte').val() );

10. });

11. });

12. })(jQuery);

- ouvrez un terminal dans le répertoire de votre projet ;

- exécuter la commande : npm install websocket;

- exécutez la commande : node startws.js;

- enfin, lancez votre navigateur Internet préféré sur l'URL http://127.0.0.1:8091/ws.html et observez la console de votre serveur lorsque : vous ouvrez une nouvelle page, envoyez des messages ou quand vous fermez la page.

Vous devriez avoir une page et un terminal comme ceux-ci :

VII - Développement d'un système de messagerie instantanée

VII-A - Exercice

Maintenant que le plus dur est derrière vous, vous avez pour mission d'écrire le serveur d'une messagerie instantanée multiutilisateur.

Voici les messages reçus par le serveur :

- lorsqu'un utilisateur se connecte, l'IHM envoie le message JSON suivant : {type : 'open', login : login} ;

- lorsqu'un utilisateur envoie un message, l'IHM envoie le message suivant : {type : 'message', message}.

Voici les messages envoyés par le serveur à toutes les IHM :

- lorsqu'un nouvel utilisateur se connecte : {type : 'chatopen', login : login} ;

- lorsqu'un utilisateur envoie un message: {type : 'chatmessage', login: login, message : message} ;

- lorsqu'un utilisateur ferme sa fenêtre : {type : 'close', login : login}.

Voici le script d'initialisation startchat.js à exécuter via Node.js :

startchat.js

1. ;(function( $ ){

2. $({

3. port:     8092,

4. path: './www',

5. types: {

6. "js":    'text/js',

7. "html":    'text/html'

8. }

9. })

10. .httpserver()

11. .wsserver()

12. .chatserver();

13. })( require("jquery"),                 // charge jQuery

14. require('./src/jquery.httpserver'),     // charge votre serveur HTTP

15. require('./src/jquery.wsserver'),        // charge votre serveur WebSocket

16. require('./src/jquery.chatserver') );    // charge votre serveur de messagerie

La page HTML chat.html de notre messagerie instantanée :

www/chat.html

1. <!doctype html>

2. <html>

3. <head>

4. <meta charset="utf-8" />

5. <title>Demo Node.js avec jQuery</title>

6. <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>

7. <script src="chat.js"></script>

8. </head>

9. <body>

10. <h1>Node.js pour les developpeurs jQuery</h1>

11. <input type="text" id="texte" /><input type="button" id="envoyer" value="Envoyer" />

12. <div id="salon"></div>

13. </body>

14. </html>

Et le JavaScript chat.js associé à la page :

www/chat.js

1. ;(function($){

2. $(function(){

3. var _login = prompt("Entrez votre login");

5. window.WebSocket = window.WebSocket || window.MozWebSocket;

7. var _connection = new WebSocket('ws://' + document.URL.substr(7).split('/')[0]);

9. _connection.onopen = function () {

10. _connection.send( JSON.stringify({

11. type: 'open',

12. login: _login

13. }) );

14. };

16. _connection.onmessage = function (_message) {

17. var _data = JSON.parse(_message.data);

19. if ( _data.type == 'chatopen' ){

20. $('#salon').prepend( $('<li></li>').text( ">>> "+_data.login+" vient d'arriver") );

21. }else if ( _data.type == 'chatmessage' ){

22. $('#salon').prepend( $('<li></li>').text( _data.login+": "+_data.message) );

23. }else if ( _data.type == 'chatclose' ){

24. $('#salon').prepend( $('<li></li>').text( _data.login+" vient de partir") );

25. }

26. };

28. $('#envoyer').click(function(){

29. _connection.send( JSON.stringify({

30. type: 'message',

31. message: $('#texte').val()

32. }) );

33. });

34. });

35. })(jQuery);

VII-B - Exemple de solution

Suivez les actions suivantes :

- dans le répertoire de votre projet, créez le fichier startchat.js ;

- insérer le code suivant dans le fichier startchat.js :

startchat.js

1. ;(function( $ ){

2. $({

3. port:     8092,

4. path: './www',

5. types: {

6. "js":    'text/js',

7. "html":    'text/html'

8. }

9. })

10. .httpserver()

11. .wsserver()

12. .chatserver();

13. })( require("jquery"),                 // charge jQuery

14. require('./src/jquery.httpserver'),     // charge votre serveur HTTP

15. require('./src/jquery.wsserver'),        // charge votre serveur WebSocket

16. require('./src/jquery.chatserver') );    // charge votre serveur de messagerie

- créez un page www/chat.html avec le code suivant :

www/chat.html

1. <!doctype html>

2. <html>

3. <head>

4. <meta charset="utf-8" />

5. <title>Demo Node.js avec jQuery</title>

6. <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>

7. <script src="chat.js"></script>

8. </head>

9. <body>

10. <h1>Node.js pour les developpeurs jQuery</h1>

11. <input type="text" id="texte" /><input type="button" id="envoyer" value="Envoyer" />

12. <div id="salon"></div>

13. </body>

14. </html>

- créez une page www/chat.js avec le code suivant :

www/chat.js

1. ;(function($){

2. $(function(){

3. var _login = prompt("Entrez votre login");

5. // if user is running mozilla then use it's built-in WebSocket

6. window.WebSocket = window.WebSocket || window.MozWebSocket;

8. var _connection = new WebSocket('ws://' + document.URL.substr(7).split('/')[0]);

10. _connection.onopen = function () {

11. _connection.send( JSON.stringify({

12. type: 'open',

13. login: _login

14. }) );

15. };

17. _connection.onmessage = function (_message) {

18. var _data = JSON.parse(_message.data);

20. if ( _data.type == 'chatopen' ){

21. $('#salon').prepend( $('<li></li>').text( ">>> "+_data.login+" vient d'arriver") );

22. }else if ( _data.type == 'chatmessage' ){

23. $('#salon').prepend( $('<li></li>').text( _data.login+": "+_data.message) );

24. }else if ( _data.type == 'chatclose' ){

25. $('#salon').prepend( $('<li></li>').text( _data.login+" vient de partir") );

26. }

27. };

29. $('#envoyer').click(function(){

30. _connection.send( JSON.stringify({

31. type: 'message',

32. message: $('#texte').val()

33. }) );

34. });

35. });

36. })(jQuery);

- créez le fichier src/jquery.chatserver.js

src/jquery.chatserver.js

1. ;(function($){

2. $.fn.chatserver = function(){

3. return this.each(function(){

4. var $_this = $(this);

6. // s'abonne aux nouveaux clients

7. $_this.on('request', function(_event, _connection){

8. $( _connection )

9. // on vient de se connecter

10. .on('open', function(__event){

11. // rien à faire

12. })

13. // on reçoit un message de l'IHM

14. .on('message', function(__event, _message){

15. // on parse la réponse avant de la traiter

16. var _data = JSON.parse(_message);

18. // l'utilisateur vient de se connecter sur l'IHM

19. if ( _data.type == 'open' ){

20. $( _connection ).data( 'login', _data.login );            // on enregistre nom login

22. $( $_this.data('connections').list )

23. .trigger( 'chatopen', [_data.login] );

24. }else if ( _data.type == 'message' ){

25. $( $_this.data('connections').list )

26. .trigger( 'chatmessage', [$(_connection).data('login'), _data.message] );

27. }

28. })

29. .on('close', function(__event){

30. var _connections = $_this.data('connections') ;

32. $( _connections.list )

33. .trigger( 'chatclose', [$(_connection).data('login')] );

34. })

35. // un nouvel utilisateur vient d'arriver

36. .on('chatopen', function(__event, _login){

37. // on envoie l'information à l'IHM

38. _connection.sendUTF( JSON.stringify({

39. type: 'chatopen',

40. login: _login

41. }) );

42. })

43. // on recoit un message d'un autre utilisateur du chat

44. .on('chatmessage', function(__event, _login, _message){

45. // on envoie l'information à l'IHM

46. _connection.sendUTF( JSON.stringify({

47. type: 'chatmessage',

48. login: _login,

49. message: _message

50. }) );

51. })

52. // un utilisateur vient de partir

53. .on('chatclose', function(__event, _login){

54. // on envoie l'information à l'IHM

55. _connection.sendUTF( JSON.stringify({

56. type: 'chatclose',

57. login: _login

58. }) );

59. });

60. });

61. });

62. };

63. })( require('jquery') );

- exécutez la commande : node startchat;

- entrez l'URL suivante sur votre navigateur HTML5 préféré : http://localhost:8092/chat.html.

Vous devez avoir une page similaire à celle-ci :

VIII - Conclusion

Grâce à l'utilisation de jQuery, vous avez réussi à développer un serveur de messagerie instantanée en temps réel, grâce à des plugins réutilisables, complètement autonomes, mais qui communiquent entre eux.
Le tout en ayant plus de 60 % du code qui peut être réutilisé sur vos prochains projets.

Imaginez ce que pourront donner nos développements jQuery pour Node.js si nous arrivons à avoir la même banque de plugins qu'en front end !

Alors, vous pensez toujours que jQuery est seulement fait pour gérer les éléments du DOM ?

IX - Remerciements

Merci à Bovino et ClaudeLELOUP pour leurs relectures et leurs corrections de cet article.