Javascript server-side : la révolution node.js ?

1 June 2011 par Joffrey F

Il y a encore quelques années, la mention du langage Javascript faisait se hérisser les cheveux de la plupart des programmeurs. Beaucoup le considéraient alors comme un boulet hérité des années Netscape dont on se serait volontiers passé s'il y avait eu une alternative viable. Grâce à l'évolution technologique récente, l'explosion et la démocratisation des RIA (Rich Internet Applications) et la libéralisation du marché des navigateurs (montée en puissance de Firefox et Google Chrome notamment), le Javascript est devenu un citoyen de première classe du paysage web.

Alors Javascript en tant que langage permettant de développer un serveur d'application, pourquoi pas ?

Car malgré ses défauts mille fois pointés du doigt, Javascript est un langage possédant une expressivité impressionnante, orienté objet, doté de principes hérités des langages fonctionnels (Lisp, etc.) comme la closure ou les lambda-functions, et intégrant le paradigme de programmation évènementielle... Bien loin, en fait, du langage jouet que certains décrivent encore !

Cet article a pour vocation de présenter node.js, une technologie basée sur V8, le moteur javascript de Google Chrome, dont elle étend les capacités en ajoutant de nombreuses fonctionnalités d'I/O (Entrées/Sorties). Suite à quelques généralités à son sujet, nous mettrons les mains dans le code et ferons l'ébauche d'un serveur basique capable de répondre à des requêtes HTTP. Nous conclurons par une ouverture sur quelques frameworks et bibliothèques associées à node.

Présentation et historique

Node.js est un projet né au cours de l'année 2009 (les premières traces du projet remontent à février 2009, la première « release » (0.0.1) à mai 2009). C'est un projet open-source, sous licence MIT. Les sources sont disponibles sur github, ainsi qu'une partie de la documentation, notamment concernant l'installation de node sur votre environnement. L'API, quant à elle, se trouve sur nodejs.org.

C'est un projet actif et dynamique qui bénéficie d'une release mineure toutes les deux semaines environ. Fin avril 2011, la dernière release stable est la version 0.4.7. Le créateur et principal mainteneur est Ryan Dahl.

Le principe fondateur de cette technologie est d'offrir une surcouche au moteur V8 permettant de gérer les entrées/sorties de manière évènementielle. Ces fonctionnalités sont fortement orientées vers les applications réseau (support de HTTP, HTTPS, TCP, ...). La gestion évènementielle élimine les appels bloquants de l'application, ce qui permet une meilleure concurrence par rapport aux systèmes traditionnels basés sur l'utilisation de threads, ainsi qu'une empreinte mémoire fortement diminuée, comme le prouvent de nombreux benchmarks. En somme, node.js pourrait devenir, une fois arrivé à maturité, la référence en termes de développement d'applications web à fort trafic.

Réalisation d'un serveur basique en node.js

Nous allons à présent tenter de donner un aperçu de ce qu'est la programmation avec node.js. Notre objectif est de réaliser un petit serveur http très simple capable de servir les fichiers HTML contenus dans un dossier donné. Nous considèrerons que vous avez une connaissance basique du protocole HTTP et du langage Javascript.

En guise d'avant-propos, il faut signaler que node.js fonctionne particulièrement bien sur des systèmes UNIX (Linux, MacOS, BSD...), mais que l'utilisation sous Windows est (à l'heure actuelle) complexe et requiert des outils intermédiaires comme Cygwin ou MinGW. Ici, nous considèrerons que vous avez accès à un système UNIX avec une version récente de Python installée (Python est nécessaire pour l'installation de Node mais n'est pas utilisé au runtime).

Téléchargez la dernière version de node sur nodejs.org, décompressez l'archive, allez à la racine du dossier extrait et exécutez les commandes suivantes :

mkdir ~/local
./configure --prefix=$HOME/local/node
make
make install
export PATH=$HOME/local/node/bin:$PATH

Une fois cela fait, si tout s'est bien passé, vous devriez pouvoir exécuter la commande node -version et obtenir un affichage du type :

$ node --version
v0.4.7
$

Maintenant que node est installé, il est temps de commencer à coder.  On peut commencer par écrire un serveur HTTP en quelques lignes qui répondra « Hello World » dès qu'on le requêtera. Pour cela on crée un fichier (par exemple « node-example.js ») contenant le code suivant :

// On utilise le module http grâce à l’instruction require, qui récupère
// un objet javascript doté de diverses fonctions utiles que nous stockons
// dans notre variable http
var http = require('http');
var server = http.createServer( // on crée l’objet server.
  // on définit une fonction lambda qui se chargera de répondre lorsqu'un
  // client émettra une requête sur notre serveur.
  function(request, response) {
    // la réponse http sera formée d’un header indiquant le statut 200
    // (requête traitée avec succès)
    response.writeHead(200);
    // On inscrit ‘Hello World!’ suivi d’un retour à la ligne dans le corps
    // de la réponse.
    response.write('Hello World !\n');
    response.end(); // On indique la fin de la réponse.
  });
server.listen(8000); // On dit au serveur d’écouter sur le port 8000.

Sauvegardez le fichier, puis lancez la commande node node-example.js. À partir de là, vous devriez pouvoir accéder au serveur par exemple depuis votre navigateur via l'adresse http://localhost:8000/. Jusqu'ici, notre code est d'une extraordinaire simplicité. Maintenant, créons un fichier index.html avec quelques balises, que notre serveur répondra.

var http = require('http');
// le module "filesystem" qui va nous permettre de lire un fichier
var fs = require('fs'); 
 
function readFileData(callback) { // on déclare une fonction readFileData
  fs.readFile('index.html', // on demande la lecture du fichier index.html
      // on exécute la fonction de callback une fois que le fichier a été lu.
      // C'est dans cette fonction qu'on manipulera les données récupérées.
      callback
    );
}
 
var server = http.createServer(
  function(request, response) {
    // On définit la fonction de callback de la lecture du fichier.
    // Le premier paramètre contiendra l'éventuelle erreur lors de
    // la lecture, le second les données lues.
    function respond(err, data) {
      // Grâce à la closure, on a accès à response à l'intérieur de
      // cette fonction "nestée".
      if (err) { // si on a rencontré une erreur
        response.writeHead(500); // "Internal server error"
        response.end();
      } else { // sinon, on peut répondre avec les données.
        response.writeHead(200,
          // un objet js contenant des headers http.
          // Ici, on définit le content-type.
          { 'Content-Type' : 'text/html' }
        );
        response.write(data);
        response.end();
      }
    }
    // on appelle readFileData en lui passant notre fonction callback.
    readFileData(respond);
  });
 
server.listen(8000);

Redémarrez le serveur (node node-example.js après avoir interrompu le processus précédent), vous pourrez alors visualiser votre fichier index.html depuis votre navigateur. Si le fichier n'existe pas, le serveur vous renverra une erreur HTTP 500. On notera que pendant la lecture du fichier, le serveur en lui-même n'est pas bloqué et peut traiter d'autres requêtes !

A présent, nous voulons pouvoir servir différents fichiers HTML en fonction de l'URL. Reprenons donc notre exemple et améliorons-le.

var http = require('http');
var fs = require('fs');
// On utilise le module url pour parser l'URL de la requête.
var url = require('url');
 
// On ajoute un paramètre 'file' qui indique à quel fichier on veut accéder.
function readFileData(file, callback) {
  fs.readFile(file, callback);
}
 
var server = http.createServer(
  function(request, response) {
    var parsedUrl = url.parse(request.url); // on parse l'URL de requête
    // le pathname contient le chemin du fichier demandé.
    var requestedFile = './' + parsedUrl.pathname;
 
    function respond(err, data) {
      if (err) {
        response.writeHead(500);
      } else {
        response.writeHead(200, { 'Content-Type' : 'text/html' });
        response.write(data);
      }
      response.end();
    }
 
    // On indique quel fichier on doit lire.
    readFileData(requestedFile, respond);
  });
 
server.listen(8000);

Nous avons très peu de choses à ajouter pour que cela fonctionne. Attention néanmoins, ce code n'empêche pas l'accès à des fichiers contenus dans des dossiers parents. On pourrait aussi faire en sorte de retourner une erreur 404 lorsque le fichier n'existe pas au lieu de l'erreur 500 que nous choisissons par défaut pour n'importe quelle erreur de lecture. Idéalement, il faudrait aussi ajouter un cache capable de stocker le contenu des fichiers plutôt que de le relire à chaque fois, et afficher le fichier index.html par défaut si aucun fichier n'est indiqué dans le pathname.

var http = require('http');
var fs = require('fs');
var url = require('url');
 
var buffer = {};
 
function readFileData(file, callback) {
  if (buffer[file]) {
    // si le buffer connaît le fichier, on appelle directement le
    // callback avec le contenu du buffer.
    callback(null, buffer[file]);
  } else {
    fs.readFile(file, function(err, data) {
      if (!err) {
        // si l'on a pas rencontré d'erreur, on crée une entrée
        // dans le buffer avec les données lues.
        buffer[file] = data;
      }
      callback(err, data);
    });
  }
}
 
function checkPathName(path) {
  var regex = /(\.\.$)|(\.\.\/)/;
  return !regex.test(path);
}
 
var server = http.createServer(
  function(request, response) {
    var parsedUrl = url.parse(request.url);
    var requestedFile = './' + parsedUrl.pathname;
    if (!checkPathName(requestedFile)) {
      // Si le pathaname contient '..', on refuse de servir le fichier
      // avec une erreur 403.
      response.writeHead(403);
      response.end();
      return;
    } else if (requestedFile == './' || requestedFile == './/') {
      requestedFile = './index.html';
    }
 
    function respond(err, data) {
      if (err) {
        if (err.code == 'ENOENT') { // Le fichier n'existe pas.
          response.writeHead(404);
          response.write('Sorry, this file is not available.');
        } else {
          response.writeHead(500);
        }
      } else {
        response.writeHead(200, { 'Content-Type' : 'text/html' });
        response.write(data);
      }
      response.end();
    }
 
    readFileData(requestedFile, respond);
  });
 
server.listen(8000);

Nous avons à présent un serveur HTTP basique capable de servir des fichiers HTML, en seulement quelques lignes de code. En partant de là on peut apporter un grand nombre d'autres fonctionnalités.

Le microcosme node.js

Node.js a pour vocation de rester relativement bas-niveau, offrant ainsi les briques de base pour construire une application réseau hautement performante. Mais la communauté qui s'est formée autour de node apporte énormément d'outils pour rendre le développement d'applications simple, intuitif et viable pour des projets d'entreprise.

Npm

NPM est l'abréviation de Node Package Manager. C'est en quelque sorte l'équivalent de ce que pourrait être aptitude ou apt-get (les gestionnaires de paquets sous Debian et Ubuntu), et l'interface est très similaire. Pour télécharger la dernière version d'express, par exemple, il suffit d'exécuter la commande npm install express et de laisser l'utilitaire faire son travail. Si quelques mois plus tard, on désire obtenir la dernière version d'express, on exécutera simplement npm update express.

Cet utilitaire, ainsi que des informations quant à son installation et son utilisation sont disponibles sur github, comme la grande majorité de l'écosystème node.

Express

Plus haut, nous avions commencé à écrire notre serveur HTTP capable de servir des fichiers statiques. Bien que l'exemple soit utile à la compréhension de la mécanique de node, ce n'est pas une solution viable pour le développement d'une « vraie » application web. Fort heureusement, Express existe. Il s'agit d'un framework web basé sur node.js - sans doute le plus abouti d'entre eux - et Ryan Dahl lui-même le suggère à de nombreuses reprises sur le site de node ou lors des conférences qu'il donne. Le framework se base sur le design-pattern MVC (Model View Controller) et inclut entre autres le support du routage, intègre plusieurs langages de template pour les vues (notamment Jade), supporte les vues partielles, etc.

En d'autres termes, Express est sans doute, à l'heure actuelle, le point de départ de toute application web basée sur node.

Wordsquared.com

Ce dernier point s'adressera particulièrement aux sceptiques, pour lesquels node n'est encore qu'un gadget. Le site wordsquared.com est l'un des quelques sites basés sur node aujourd'hui. Wordsquared est particulier, car il s'agit d'une démonstration technologique et technique d'une rare qualité : il s'agit en fait d'un jeu de scrabble géant, multijoueur, en temps réel. La réalisation de l'application est impressionnante, tout comme sa réactivité. C'est sans aucun doute la meilleure preuve de maturité du combo node.js/express.

... Et les autres !

Bien entendu, ce ne sont là que quelques-uns des éléments qui composent le microcosme node.js, et l'on trouve de nombreux modules (allant du parser XML au client MySQL) ainsi que de nombreuses applications construites sur node.

Cette technologie éveille les curiosités et fait buzzer le web, et non sans raison : une fois que le pas de la maturité sera franchi, node pourrait être une alternative sérieuse à des solutions comme Ruby On Rails ou django. Avec son modèle évènementiel et la possibilité d'écrire toute la pile client-serveur en un seul langage (javascript !), il ne fait aucun doute que node fera partie du web de demain.

Tags: , , , , , , , , , ,

9 commentaires pour “Javascript server-side : la révolution node.js ?”

  1. Laurentj dit :

    à propos de Javascript

    >orienté objet

    non, orienté prototype. La nuance est de taille.

    >intégrant le paradigme de programmation évènementielle.

    Pas du tout. Ou alors faut me dire où est-ce définit dans la spec JS.. Faut pas confondre les évènements DOM et Javascript ou les fonctions proposées par Node et ce qui est fourni par JS.

  2. Joffrey F dit :

    Bonjour,

    la programmation orientée prototype est une forme de POO. Croyez bien que je saisis la nuance, même si cet article n’a absolument pas pour but de l’expliciter, d’où le “raccourci” employé.

    Quant à votre seconde remarque, vous avez raison. Néanmoins, si vous connaissez une implémentation du langage qui n’intègre pas d’évènements, je serais curieux de la connaître. =)

  3. Oshimin dit :

    Bonjour,

    Effectivement le js n’est pas événementiel mais les VMs oui.
    Regarde la vm rihno.js de mozilla qui elle n’est pas événementiel, ou même jaxer d’aptana (js coté serveur).

    Le js n’est pas objet, mais un language de prototype,
    On peut reproduire certains comportements des langages objet. Il ya plein d’initiatives dessus Klass.js de prototype en est un exemple.

    Belle présentation de node.js, je crois qu’avec des outils comme node.js, adobe air, titanium appecelerator, phonegap et les differentes implémentation de webkit dans nos mobiles. je pense que js a de beau jours devant lui… =)

  4. Joffrey F dit :

    Bonjour,

    Je me permets d’insister au sujet du débat au sujet de l’aspect orienté objet de Javascript. Que ce soit wikipedia (http://en.wikipedia.org/wiki/Prototype-based_programming), Douglas Crockford (http://javascript.crockford.com/javascript.html) ou le Mozilla Developer Network (https://developer.mozilla.org/en/javascript), tous s’accordent à dire que Javascript est _effectivement_ un langage orienté objet.

  5. bob dit :

    Tout à fait.
    Même si on a plus souvent l’habitude de voir de la POO par classe, la POO par prototype reste tout de même de la programmation objet.

  6. Vincent RABAH dit :

    Bonjour,

    Je suis un passionné par Node.js, et de mon point de vue, la vraie révolution se place au niveau de l’efficience dans le développement : ON UTILISE PLUS QU’UN SEUL ET UNIQUE LANGAGE DU SERVEUR AU CLIENT !

    La véritable innovation est là, dans l’unicité !

    Très cordialement,
    Vincent RABAH

  7. Marc Buils dit :

    Bonjour,

    Je suis entrain d’écrire un article sur node.js et WorkESB pour le site developpez.com, et je souhaite intégrer tel quel votre paragraphe sur l’hitorique de node.js (avec un lien vers cette page évidement).
    Ce serait possible ?

    Merci,

    Marc

  8. Joffrey F dit :

    Bonjour Marc,

    Personnellement cela ne me derange pas que vous utilisiez ce paragraphe si vous en fournissez la source ! Neanmoins ayez a l’esprit que de nombreuses choses ont change depuis que cet article a ete ecrit. La derniere version de node est maintenant 0.8.12 et Isaac Schlueter a succede a Ryan Dahl au role de mainteneur du projet.

    HTH

  9. Marc Buils dit :

    Bonjour Joffrey,

    Je modifierais en effet la version de Node.js ainsi que le mainteneur actuel du projet (je ne savais d’ailleurs pas qu’il avait changé…).

    Merci ton autorisation de diffuser le paragraphe,

    Marc

Laisser un commentaire


eight − = 3