Plus compliqué maintenant

Je vous propose de réaliser en quelques lignes de code, une petite application qui devra être capable de servir des pages web décrite au format Textile, un des nombreux langages de formatage de texte orientés wiki.

Pour cela, nous allons devoir faire appel à des modules externes, et nous verons comment en configurer l’utilisation.

Avant cela, découvrons la commande npm ou “Node Package Manager”.

$ npm -v
1.2.15

Ici, la commande est disponible dans sa version 1.2.15.

les modules

Si vous faite un tour sur votre moteur de recherche de rpédilection, avec les mots clés nodejs modules, vous allez certainement tomber (et c’est ce que je souhaite 🙂 sur le site officiel des modules:

http://npmjs.org/*

C’est ici que vous pourrez découvrir tout ce que propose l’écosystème NodeJS.

*  Il existe également un « proxy » à npm qui s’appelle http://nodejsmodules.org (merci à Pierrick Paul pour la rectification de mes propos).

Majoritairement, les modules sont installables via la commande npm.

Formatage du texte

En cherchant Textile sur le site en question, vous trouverez textile-js, un module qui est capable de convertir un text (chaine de caractères) décrit en textile vers le langage bien connu HTML.

donc :

$ npm install -g textile-js
npm http GET https://registry.npmjs.org/textile-js
npm http 304 https://registry.npmjs.org/textile-js
C:\Users\[username]\AppData\Roaming\npm\textile-js -> C:\Users\[username]\AppData\Roaming\npm\node_modules\textile-js\bin\textile
textile-js@0.1.2 C:\Users\[username]\AppData\Roaming\npm\node_modules\textile-js

Nous sommes maintenant équipés pour traiter du textile avec le module en version 0.1.2.

Structure des projets demo02 et suivant

Passons à la création de notre mini serveur. Mais avant, convennons d’une structure

samples/demo02/
  wiki/
    css/
      main.css
    index.textile
    page1.textile
  demo02.js

Structure arborescente du serveur Wiki Textile

Donc, notre source demo02.js contiendra le code, le répertoire wiki hebrgera les pages exposées ainsi que leurs dépendances. le fichier main.css sera la feuille de style appliquée à nos pages.

Lecture d’un fichier

Pour pouvoir servir nos page, nous devons traiter les fichiers textile pour les convertir vers le HTML et le servir comme page HTML5 (c’est MONchoix !).

Et donc, il nous faut pouvoir LIRE des fichiers. cela est possible via le module interne fs. Nous nous appuierons sur la fonctionfs.readFile(path, [encoding], function(err, data){});

fs.readFile(path, "utf8", function(err, data){
    if(err){
      onError(path);
    }else{
      onSuccess(path,data);
    }        
});

dans l’exemple de code ci-dessus, en cas de succès, la fonction onSuccess(path,data) est appelée, sinon, ce sera onError(path) qui sera appelée.

onSuccess(path,data)
Field Description
path Chemin du fichier chargé
data données issues du fichier
onError(path)
Field Description
path Chemin du fichier chargé

Et pour faire des choses propres et réutilisables, nous allons ajouter le contrôle d’existance du fichier:

function loadFile(path,onSuccess,onError){
  fs.exists(path,function(exists){  
    if(!exists){
      onError(path);
    } else {
      fs.readFile(path, "utf8", function(err, data){
        if(err){
          onError(path);
        }else{
          onSuccess(path,data);
        }
      });
    }
  });
};

ce qui sera appellable comme ci-dessous:

loadFile(path,onSuccess, onError)
Field Description
path Chemin du fichier chargé
onSuccess(path,data) Fonction à exécuter après chargement du fichier, où path est le chemlin du fichier chargé, et data le contenu du fichier.
onError(path) Fonction à exécuter en cas d’erreur lors de l’accès au fichier ou lorsque le fichier n’existe pas, où path est le chemin du fichier recherché.

Exemple d’utilisation de notre petite fonction :

loadFile('path/to/my/file.textile',
  // onSuccess
  function(path,data){
    console.log('file :['+path+ '],content:['+data+']');
  },
  //onError
  function(path){
    console.log('Unable to load file :['+path+ ']');
  });

Voila un chargement de fichier encapsulé et propre, orienté event.

Nous allons pouvoir nous concentrer sur notre server

TextileServer Core

Créons notre fichier demo02.js avec les bon imports :

var http = require('http'),
    fs = require('fs'),
    textile = require('textile-js');
...
// Chargement d'un fichier 
function loadFile(path,onSuccess,onError){}
...
// Server request handler
function myRequestHandler(request,response){
  // si la page cherché est bien dans le path wiki
  var urlPath = url.parse(request.url).pathname;
  // si le fichier est bien dans /wiki
  if(urlPath.indexOf("/wiki") === 0){
    // on procède à son chargement
    var txtFilePath = path.join(process.cwd(), urlPath);
    loadFile( txtFilePath, 
      function(path,data){
        // Rponse contiendra du HTML
        response.writeHeader(200,{'Content-Type':"text/html"});
        // on vconvertit le textile en html
        var content = textile(data).toString();
        // on envoie la page générée sur la réponse
        response.write(decorateHtml(content),'utf8');
        response.end();
      },
      function(path){
        sendHttpError(404,"url '"+request.url+"' not found");
      });
  }else{
    sendHttpError(404,"url '"+request.url+"' not found");
  }
}
// Déclaration du serveur
var server = http.createServer(myRequestHandler).listen(8000);

Donc si nous reprenons par le menu, nous chargeons les dépendances nécessaires: httpfs et textile-js, puis nous déclarons la fonctionloadFile(). Ensuite, nous créons la fonction de request handler. Enfin, nous déclarons, comme dans l’exemple précédent, le serveur sur le port 8000.

Note 1
La méthode sendHttpError(errCode,errMessage) ne sera pas détaillée dans cet article, je vous laisse la découvrir dans le fichier demo02.js.

Note 2
La fonction decorateHtml(content) est un moyen d’injecter le contenu issu du fichier textile dans un template de page HTML (statique). elle ne sera pas détaillée non plus, aussi, je vous invite a consulter le code sur le repository en ligne, dans le fichier demo02.js.

Lançons le serveur node sur notre fichier demo02.js

$ node demo02.js

Accédons à la page générée via http://localhost:8000/wiki/index.textile

Notre server NodeJS avec ficheir textile – demo02.js

Notre server NodeJS avec ficheir textile – demo02.js

On templatise ?

Utilisation d’un nouveau module

Nous avons vu comment servir des pages HTML depuis des fichiers au format textile. Je vous propose de mettre un peu en forme nos pagesHTML à travers un template de page qui sera dynamique, et non plus statique comme cela est codé dans decorateHtml() de la version dedemo02.js.

T’as une tâche, Mustache !

Mustache est un moteur de templating, utilisant un format de placeholder basé sur les accolades (’{’ et ‘}’), d’où le nom de Mustache (jeu de mot subtile :).

Par exemple, dans le texte Bonjour {{username}} ! le placeholder “{{username}}” sera remplacé par la valeur de la variable username, passé au moteur.

Exemple de code:

var mustache = require('mustache');
var template = "Bonjour {{username}} !"
console.log("interprété par Mustache: '"+mustache.render(template,{'username':"Frédéric"})+"'");

Ce qui donne à l’execution la sortie suivante:

$> node samples/demo02/test-mustache.js
interprété par Mustache: 'Bonjour Frédéric !'

Nous pouvons maintenant facilement interfacer Mustache avec notre petit serveur. Ce qui est proposé dans la variante demo02-1.js. Dans ce premier exemple, il s’agit surtout de réécrire la fonction decorateHtml():

function decorateHtml(content){
  // template
  var template = "<!DOCTYPE html >" 
          + "<html>" 
          + "<head>" 
          + "<meta Charset=\"utf-8\">" 
          + "<title>Demo02</title>" 
          + "<link rel=\"stylesheet\" type=\"text/css\" href=\""+"/wiki/css/main.css\" />"
          + "</head>" 
          + "<body>" 
          ...
          + "<div id=\"content\" class=\"markdown-body\">" 
          + "{{{content}}}" 
          + "</div>" 
          ...
          + "</body>" 
          + "</html>";
    // remplacer les variables
    return mustache.render(template,{'content':content});
}

Pour que la solution soit totalement bien, il faut que nous puissions charger le template html depuis un fichier donné, ce qui est fait dans demo02-2.js.

Nous allons réutiliser la fonction de chargement de fichier créée pour la lecture des pages textile. Mais nous devons quelque peu modifier la structure de la méthode myRequestHandler() afin de transmettre la réponse HTTP à la méthdoe decorateHtml(response, content);

function decorateHtml(response, content){
  loadFile("wiki/template.html",
    function(path,template){
      // la réponse contiendra du HTML
      response.writeHeader(200,{'Content-Type':"text/html"});
      response.write(mustache.render(template,{'content':content}),'utf8');
      response.end();
    },
    function(path){
      // la réponse contiendra du HTML
      response.writeHeader(500,{'Content-Type':"text/html"});
      response.write("<h1>Error 500</h1>"
        +"<p class=\"\">Template ["+path+"] not found !</p>"
        + "<div class=\"container\"><div id=\"markup\"><div id=\"content\" class=\"markdown-body\">"
        + content
        + "</div></div></div>",'utf8');
      response.end();
    }); 
}

Et evidement, il faut ajouter le fichier de template02-2.html avec un contenu plus ou moins identique à celui-ci-dessous:

<!DOCTYPE html > 
<html> 
  <head> 
    <meta Charset="utf-8"> 
    <title>Demo02-2</title> 
    <link rel="stylesheet" type="text/css" href="/wiki/css/main.css" />
  </head> 
  <body> 
    <header> 
      <h1>Demo02-2</h1>
    </header> 
    <div> 
      <div id="markup"> 
        <div id="content"> 
          {{{content}}} 
        </div> 
      </div> 
    </div> 
    <footer> 
      <p>&copy; 2013 - Capgemini - 
        <a href="mailto:frederic.delorme@capgemini.com" title="contact author"/>Frédéric Delorme</a>
      </p>
    </footer> 
  </body> 
</html>

Note
Vous constaterez la présence d’un triple ‘{’ dans l’expression {{{content}}}. En effet, c’est le moyen pour expliquer à notre Mustache qu’il ne faut pas traduire le contenu de cette variable en HTML, ainsi les caractères spéciaux tels que les lettres accentuées ne sont pas échappés.

Lancer le server node avec la commande :

$ node demo02-2.js

On obtient bien la page(http://localhost:8000/wiki/index.textile) web ci-dessous:

Fichier wiki en mode textile affiché en HTML

Fichier wiki en mode textile affiché en HTML

Le problème du fichier css ?

Tous les fichiers qui doivent être servis par notre belle application ne sont pas tous des fichier textile. le template html en est la première preuve: en ligne de ce fichier, nous avons une ligne important une feuille de styles CSS :

 <link rel="stylesheet" type="text/css" href="/wiki/css/main.css" />

Je vous propose de filter les fichiers au moment de la convertion Textile vers HTML dans la fonction myRequestHandler.

Ce que nous mettrons en oeuvre dans le fichier demo02-3.js

Si l’url demandée (et donc le fichier correspondant, dans le cas où celui-ci existe) se termine par “.textile”, il faut faire la transformation Textile vers HTML, dans le cas contraire, on sert simplement le fichier:

...
// onSuccess
function(filepath,data){
  if(txtFilePath.match(".textile$") == ".textile"){
    // on convertit le textile en html
    var content = textile(data).toString();
    // on envoie la page générée sur la réponse
    decorateHtml(response, content);
  }else{
    // pas un fichier textile !
    sendFile(response, filepath, data);
  }
},
...

Mais nos fichiers peuvent aussi bien être des feuilles de styles, des javascript, ainsi que des images, nous devons donc prévoir les deux cas: Si le fichier termine par “.css” ou “.js” alors, on sert le fichier en encodage utf-8, sinon on le sert en binaire:

function sendFile(response, filepath, data) {
  if (filepath.match(".css$") == ".css" ||
      filepath.match(".js$") == ".js" ) {
    response.writeHeader(200, { "Content-Type": "text/css" });
    response.write(data, "utf8");
  }else{
    response.write(data, "binary");
  }
  response.end();
}

démarrons notre exemple :

$ node demo02-3.js

en pointant sur http://localhost:8000/wiki/index.textile, nous devrions obtenir la page suivante :

(demo02-3) Le serveur Wiki en état de marche

(demo02-3) Le serveur Wiki en état de marche

Conclusion: ça marche !

Voila, nous avons un petit serveur de page wiki en ordre de marche.

Allons plus loin et orientons nous vers une solution Entreprise en ajoutant sécurité et authentification.

Publicités