1. Qu'est-ce qu'on parle de quoi là ?
En fait, c'est plutôt simple. Lorsqu'on programme en Perl : on écrit un fichier texte plein de lignes de code, on l'enregistre, et on lance perl monfichier.pl. Lorsqu'on programme en PHP : on écrit un fichier texte plein de lignes de code, on l'enregistre, et on lance php monfichier.php. Eh bien maintenant, on peut écrire un programme en Javascript, l'enregistrer et lancer : node monfichier.js.
Le créateur de node.js, Ryan Dahl, a pris le moteur Javascript v8, celui qui est extrêmement rapide et qui propulse le navigateur web Google Chrome. Il a ajouté les indispensables bibliothèques permettant d'accéder au système (processus, fichiers) et au réseau. node.js était né !
La philosophie de node.js est de créer une boucle d'événements au lieu de se dupliquer (créer des threads), pour gérer plusieurs tâches en parallèle. De cette manière, paraît-il, un serveur web node.js peut gérer des millions de connexions concourantes. De cette manière surtout, nul besoin de gérer des accès simultanés à des structures de données : il n'y a pas d'accès simultané.
2. Installation de node.js
Je déteste cette partie des articles : rien de plus ennuyeux qu'une procédure d'installation. Node.js étant relativement récent, la procédure la plus souvent employée consiste à l'installer dans le répertoire home d'un utilisateur dédié au lieu de la mélanger au reste du système. En avant toute.
2.1 Création d'un utilisateur
La création d'un utilisateur sous Linux se fait par le biais de la commande adduser. Nous allons créer un utilisateur nodejs.
# adduser --home /home/nodejs nodejs
En fonction de votre distribution, vous aurez peut-être à répondre à quelques questions (par exemple, donner le mot de passe de nodejs).
2.2 Installation du programme node.js
Une fois l'utilisateur créé, on utilise su – pour se logguer en tant que nodejs :
# su – nodejs
nodejs@somewhere:~$
Vient ensuite le rituel maintenant célèbre de l'installation d'un logiciel à partir du code source. Notez qu'il vous faudra au préalable installer le compilateur g++ sur votre poste. Celui-ci peut être facilement mis en place en utilisant apt-get install g++ ( pour la famille Debian ) et yum install gcc-c++ (pour la famille Fedora). On utilise ici wget pour récupérer le fichier source depuis Internet, mais n'importe quel client web fait bien entendu l'affaire.
Seule particularité : on installe node.js dans un répertoire local qui se trouve dans le home directory de l'utilisateur nodejs.
$ wget http://nodejs.org/dist/node-v0.3.3.tar.gz
$ tar xvzf node-v0.3.3.tar.gz
$ cd node-v0.3.3
$ ./configure --prefix=~/local
$ make
$ make install
Nous allons ensuite ajouter le chemin ~/local/bin dans notre path par défaut, de manière à ce que nous puissions utiliser l'exécutable node sans indiquer son emplacement complet. Pour cela, on ajoute la ligne suivante à la fin du fichier/home/nodejs/.bashrc :
export PATH="~/local/bin:$PATH"
On se déloggue, puis on se reloggue ensuite afin que ces changements soient pris en compte.
On peut maintenant entrer dans le vif du sujet : découvrons cette étrange bestiole.
3. Caractéristique principale de node.js.
3.1 Une seule chose à la fois
Javascript n'est pas un langage multithread, node.js non plus. À n'importe quel instant dans le déroulement du programme, on est certain que node ne sera pas en train d'exécuter, en parallèle, d'autres parties du même programme. C'est sans doute étrange de préciser cela, mais lisez la suite, vous verrez qu'on a vite fait de s'embrouiller.
Alors, à quoi ressemble un programme du type « Hello World » en node.js ? Eh bien, il tient en une seule ligne de code (que nous enregistrons dans le fichier hello1.js) :
console.log("Hello World !") ;
Nous exécutons ensuite ce programme :
$ node hello1.js
Hello World !
$
Les habitués du développement en Javascript sur les navigateurs, et plus particulièrement sous Firefox avec l'outil firebug, auront sans doute reconnu la sympathique fonction console.log, qui permet d'afficher sur une sortie de débogage toutes sortes de messages.
3.2 Plusieurs choses à la fois
Autant la partie « programme », c'est-à-dire le code que nous allons écrire, sera exécuté en série, autant l'ensemble des appels externes que node.js effectuera seront réalisés en parallèle, en tâche de fond. C'est la principale particularité de node : Ryan a écrit un ensemble de bibliothèques asynchrones permettant d'interagir avec le monde extérieur, sans bloquer l'exécution du programme principal Javascript (notre programme).
3.3 Asyncoua ?
Asynchrone. En gros, cela signifie qu'on pose une question et qu'on n’attend pas la réponse. Ça surprend au début, n'est-ce pas ? Pour schématiser, dans un langage « classique », on programme de cette façon :
reponse = donneLaReponseA ( maQuestion ) ;
effectueCeciAvecLaReponse(reponse) ;
Sous node.js, on code comme ceci :
donneLaReponseA ( maQuestion, etEffectueCeciLorsqueTuAsLaReponse ) ;
Un exemple valant souvent mille explications, voyons comment lire le contenu d'un fichier local avec node. Nous parlerons de la directive require dans le chapitre suivant.
var fs = require("fs") ;
fs.readFile('/etc/passwd', function (err, data) {
if (err) console.log("erreur : je n'ai pas réussi à lire le fichier");
else console.log("Le fichier contient "+data.length+" caractères");
});
Dans cet exemple, nous disons à node : lis-moi s'il te plait le fichier /etc/passwd. Lorsque tu auras lu ce fichier (ou à moins que tu rencontres une erreur), exécute la fonction que je te passe en argument.
Cet exemple montre deux notions essentielles :
1) On passe comme argument une fonction.
Ceci est une caractéristique de Javascript et vous est sans doute familier si vous codez déjà du Javascript côté browser. Ce type de fonctions (appelées au choix anonymous functions ou lambda expressions) est tellement pratique que tous les langages l'implémentent petit à petit (oui bon, sauf Java, où ils n'ont pas réussi à se mettre d'accord pour l'inclure dans la version 7).
2) On passe un contrat avec node.js.
Lorsque node.js, ou plus précisément ses développeurs, créent un appel asynchrone, ils nous font une promesse : la fonction que l'on passe en argument sera appelée. Elle sera appelée avec un premier argument null si tout se passe bien, avec un premier argument non null s’il y a un souci, mais elle sera appelée. C'est tout de même une bonne nouvelle.
Alors, quelle différence entre les appels asynchrones et nos habitudes de programmation classiques ? Nous avons la main pour continuer l'exécution de notre code avant la fin de l'appel asynchrone. Comparons la même instruction écrite sous node.js et, par exemple, en PHP.
En utilisant PHP :
$data = file_get_contents("/etc/passwd") ;
echo "J'ai fini de lire le fichier" ;
echo "instruction suivante" ;
Le code suivant nous donnera bien entendu :
J'ai fini de lire le fichier
instruction suivante
Maintenant, en utilisant node.js :
var fs = require("fs") ;
fs.readFile('/etc/passwd', function (err, data) {
console.log("J'ai fini de lire le fichier") ;
});
console.log("instruction suivante") ;
nous obtenons :
instruction suivante
J'ai fini de lire le fichier
3.4 Pardonnez-moi si j'insiste
Il est absolument fondamental de bien comprendre l'exemple précédent. Afin de le rendre plus parlant, nous ajoutons une variable i que l'on va incrémenter dans la fonction passée en argument à fs.readFile :
var fs = require("fs") ;
var i = 0 ;
fs.readFile('/etc/passwd', function (err, data) {
i = i + 1;
});
console.log("i = ", i) ;
L'exécution de ce code donnera la sortie :
i = 0
Systématiquement. Toujours. Comme nous le disions au début de ce chapitre, nous avons la garantie que la ligne console.log... sera exécutée juste après la ligne fs.readFile.... Quoi qu'il arrive.
Le contre-exemple serait :
var fs = require("fs") ;
var i = 0 ;
fs.readFile('/etc/passwd', function (err, data) {
i = i + 1;
});
fs.readFile('/etc/hosts', function (err, data) {
i = i + 1;
console.log("i = ", i) ;
});
Quelle sera la sortie de ce script lancé avec node.js ? On ne peut le savoir. Nous n'avons aucune garantie que le callback associé à la lecture de /etc/passwd sera exécute avant celui de /etc/hosts.
Au fait : une fonction que l'on passe en argument d'un appel asynchrone pour être exécutée au retour de celui-ci est appelée un callback. C'est très parlant le terme callback : « tu exécutes ce que je te demande et tu me rappelles quand c'est fait ». Rappel. Callback. Combien de fois par jour votre chef adoré vous pose des callbacks ?
4. Les modules
Par défaut, l'environnement mis à disposition par node.js dans un script est très restreint. Nous disposons de ces quelques objets :
- global : l'espace de nom global (qui, par défaut, est vide) ;
- process : l'objet process, qui représente le processus UNIX dans lequel nous nous trouvons et qui permet d'avoir accès, entre autres, à notre pid, notre current working directory (cwd), ... ;
- des variable magiques __filename et __dirname, qui contiennent respectivement le nom du fichier en cours d'exécution et son répertoire ;
- et enfin, require : la directive nous permettant d'importer des modules.
Les modules fournis par défaut dans node.js sont très bien documentés et le but de cet article n'est pas d'en faire une liste exhaustive. En revanche, nous allons expliquer ici les bases de l'utilisation et de la création des modules.
4.1 Utiliser un module node.js
La directive require permet d'avoir accès à un module node.js en l'affectant à une variable. Par exemple, pour avoir accès à l'ensemble des fonctions du module File System (fs), on utilise :
var fsmodule = require("fs") ;
Nous avons ensuite accès à la fonction readFile via fsmodule.readFile(...), à la fonction writeFile via fsmodule.writeFile(...), etc.
Si nous souhaitons avoir uniquement accès à une fonction spécifique, il suffit de restreindre l'assignation de notre variable à cette fonction. Par exemple :
var write = require("fs").writeFile ;
Dans ce cas, nous pouvons directement utiliser notre variable write comme un alias de la fonction writeFile :
var write = require("fs").writeFile ;
write("message.txt","Hello World !",function() {console.log("c'est écrit") ;});
4.2 Créer un module node.js
La création d'un module node.js est très simple, et c'est une bonne chose : cela encourage l'écriture de composants réutilisables au sein d'un ou plusieurs projets. Un module doit obéir à une règle : nous devons ajouter les fonctions que l'on souhaite rendre disponibles dans l'objet exports. À titre d'exemple, créons un module node.js qui nous permet de loguer des lignes dans un fichier. Nous appelons ce fichier log.js :
var writer = require("fs").createWriteStream;
var logfile = "application.log" ;
exports.log = function ( line ) {
var stream = writer(logfile, { flags : "a+", encoding : "utf8" }) ;
var written = stream.write(line+"\n") ;
if (written) {
stream.destroy() ;
} else {
stream.on("drain",function() { stream.destroy() ; }) ;
}
};
Outre l'aspect complexe de l'écriture d'un fichier en mode append, sur lequel nous reviendrons dans le chapitre suivant, nous avons créé une fonction log, qui prend comme argument une chaîne de caractères et qui l'écrit à la fin d'un fichier application.log.
Utilisons maintenant notre module en créant le fichier modtest1.js :
var logger = require("./log").log ;
logger("ceci est ma première ligne de log") ;
logger("ceci est ma seconde ligne de log") ;
Dans la directive require, nous avons utilisé ./log afin de préciser à node.js que le module log n'est pas un module fourni par défaut, mais un fichier disponible dans le même répertoire que le script modtest1.js.
Nous exécutons le script modtest1.js, puis consultons le contenu du fichier application.log :
$ node modtest1.js
$ cat application.log
ceci est ma première ligne de log
ceci est ma seconde ligne de log
$
Nous obtenons le résultat escompté. Et pourtant, nous avons commis une imprudence : en effet, une fois encore, la fonction write de notre stream étant asynchrone, nous n'avons aucune garantie que la première ligne de code sera écrite avant la seconde ! Une version plus correcte du module log ressemblerait plutôt à :
var writer = require("fs").createWriteStream;
var logfile = "application.log" ;
var stream = null ;
var buffer = [] ;
var writeOneLine = function() {
// Si je suis déjà en train d'écrire, ou si il n'y a rien à écrire,
// j'arrete tout de suite
if ( stream !== null || ! buffer.length )return ;
stream = writer(logfile, { flags : "a+", encoding : "utf8" }) ;
var written = stream.write(buffer.shift()+"\n") ;
if (written) {
stream.destroy() ;
stream = null ;
// je m'auto relance
writeOneLine() ;
} else {
stream.on("drain",function() {
stream.destroy() ;
stream = null ;
// je m'auto relance
writeOneLine() ;
}) ;
}
}
exports.log = function ( line ) {
buffer.push(line) ;
writeOneLine() ;
};
On s'aperçoit qu'il est facile de commettre une erreur en mode asynchrone ! Par contre, les bénéfices sont importants : par exemple, dans le cadre d'une application web, on pourra envoyer une réponse au navigateur sans attendre que la ou les lignes de log soient insérées dans le fichier, node.js terminera tranquillement l'écriture dans le fichier, même après que la connexion au navigateur soit fermée.
4.3 Les modules ne sont évalués qu'une fois
C'est bon à savoir : un module node.js n'est évalué que la première fois qu'on le requiert. Les fois suivantes, node.js assignera la référence du module à la variable demandée. Pour illustrer rapidement cette théorie, nous créons trois fichiers : un module que nous allons inclure deux fois, un module intermédiaire et le script de base.
Premier fichier : le module que l'on va inclure deux fois (dummymod1.js) :
console.log("Je suis évalué") ;
exports.dummy = function() {} ;
Second fichier : le module intermédiaire (intermod1.js) :
var dummy = require ("./dummymod1") ;
exports.inter = dummy.dummy ;
Troisième fichier : le script de base, qui inclut dummymod et intermod (modtest2.js) :
var dummy = require("./dummymod1") ;
var inter = require("./intermod1") ;
console.log("fini") ;
Let's go dancing :
$ node modtest2.js
Je suis évalué
fini
$
Comme le titre le laissait présager, la ligne de log « Je suis évalué » n'apparaît qu'une seule fois, alors que la directive require("./dummymod1") a été utilisée à deux endroits.
On peut donc considérer que les modules node.js suivent le design pattern des singleton. Pour les amateurs de spécifications, les modules node suivent la norme CommonJS 1.0.
5. Le monde merveilleux des streams
Afin d'uniformiser toutes les opérations asynchrones qui consistent à « recevoir quelque chose » (je lis un fichier, je reçois une requête réseau) et à « envoyer quelque chose » (j'écris dans un fichier, j'envoie une réponse dans un socket réseau), les développeurs utilisent une interface dite « stream », que l'on peut traduire par « flux ». Il y a trois sorte de streams : ceux que l'on lit, ceux dans lesquels on écrit, et ceux qui font les deux (on les lit et on écrit dedans).
Étant en mode asynchrone, les auteurs de node.js ont, à juste titre, considéré que chaque événement qui se produisait sur un stream (par exemple, tient je viens de recevoir un paquet de données) est... un événement. Tout stream est donc un émetteur d'événements.
5.1 Le monde merveilleux des émetteurs d'événements
Les événements node.js sont représentés par des chaînes de caractères. Par exemple : « data », « end », « close ».
Les émetteurs d'événements de node.js, appelés EventEmitter (le monde est quand même bien fait), s'utilisent de la manière suivante :
Pour écouter un événement, on utilise la méthode on. Par exemple :
stream.on("data",function(data) {console.log("je viens de recevoir ",data) ;}) ;
On peut souhaiter être averti uniquement du premier événement d'une série. Dans ce cas, on utilise la méthode once :
stream.once("data",function(data) {console.log("je viens de recevoir ",data," je ne serai plus jamais appelé") ;}) ;
On peut enfin émettre un événement en utilisant la méthode emit :
stream.emit("data","je suis la donnée émise") ;
Les EventEmitter possèdent d'autres méthodes, je vous laisse le bonheur de les découvrir par vous-même.
5.2 Retour aux flux
Un flux que l'on peut lire (readable stream) va émettre les événements suivants :
- data : lorsqu’un fragment de données est disponible. L'argument donné au callback sera bien entendu ce fragment.
- end : permet de savoir que l'émetteur a fini d'envoyer les données : plus aucun événement de type data ne sera émis par un émetteur qui a émis l'événement end.
- error : lorsqu'on rencontre une erreur de réception des données.
- close : uniquement dans le cadre de streams sur des fichiers, indique que le file descriptor a été fermé.
Un flux dans lequel on écrit (writable stream) possède deux principales méthodes.
La plus importante, la méthode write, permet d'écrire un fragment de données. Cette fonction retourne un booléen. Si ce booléen est true, le stream indique que l'écriture a eu lieu (nous sommes donc en fonctionnement synchrone) et que l'on peut de nouveau appeler la méthode write afin d'écrire le fragment de données suivant. Par contre, si le booléen est false, il faudra attendre que le stream émette l'événement drain avant de pouvoir écrire le prochain fragment de données. Si l’on n'attend pas le drain et que l'on réutilise la fonction write, les données pourront être envoyées dans un ordre aléatoire. En général, ce n'est pas vraiment ce que l'on cherche...
La seconde méthode est end : elle nous permet d'indiquer au flux que l'on a fini d'écrire. Enfin, dans le cadre de streams sur des fichiers, la méthode destroy permet de fermer le file descriptor du fichier.
Un writable stream pourra émettre les événements suivants :
- drain : comme on vient de le voir, indique que le buffer d'écriture est de nouveau vide, et que l'on peut de nouveau écrire un fragment de données via la méthode write.
- error : en cas d'erreur lors de l'écriture d'un fragment de données par la méthode write.
- close : dans le cas d'un stream écrivant dans un fichier, indique que le file descriptor, que l'on voulait fermer via la méthode destroy, a effectivement été fermé.
5.3 Pump it up
Il existe deux manières, très pratiques, de rediriger la sortie d'un readable stream sur l'entrée d'un writable stream.
La première est d'utiliser la méthode pipe du readable stream. Par exemple :
reader.pipe(writer) ;
La seconde est d'utiliser la méthode pump disponible dans le module util de node.js.
var util = require("util") ;
util.pump(reader,writer,function(err) {
if ( err ) {
console.log("il y a eu une erreur",err) ;
} else {
console.log("les données émises par reader ont été écrites dans writer") ;
}
}) ;
6. Les type de données
Nous avons à plusieurs reprises utilisé des fonctions de type write(data) ou écouté des événements de type data auxquels nous avons associé un callback function(data), mais quelle est exactement la nature de data ?
Une chose à savoir : Javascript ne supporte vraiment pas bien les données dites binaires. Il gère les données ascii et utf8 avec grâce, mais pas les données binaires. Afin de contourner le problème, les auteurs de node.js ont mis à notre disposition un objet Buffer, qui est capable de transporter aussi bien du texte que des données binaires.
Par défaut, lorsqu'on lit un readable stream, l'argument data fourni à notre callback est un buffer. Si on souhaite que cet argument soit de type utf8, on peut le préciser en utilisant la méthode setEncoding. Par exemple :
var reader = require("fs").createReadStream ;
var stream = reader("/etc/passwd") ;
stream.setEncoding("utf8") ;
stream.on("data",function(data) {
console.log("maintenant data est un string en utf8",data) ;
}) ;
L'objet Buffer fournit aussi une méthode toString qui nous permet de le convertir en uf8 à la volée :
var reader = require("fs").createReadStream ;
var stream = reader("/etc/passwd") ;
stream.on("data",function(data) {
console.log("data est un buffer",data) ;
console.log("et en utf8 il donne ", data.toString("utf8")) ;
}) ;
7. Un peu de Web !
Maintenant que nous avons (rapidement) balayé les bases de node.js, il est temps de créer notre premier serveur web ! Node.js étant très souvent employé comme serveur web, nous disposons par défaut d'un module HTTP simple d'utilisation, extensible à souhait, qui nous permet de contrôler le moindre byte reçu et envoyé.
Encore une fois, nous partons d'un exemple pour rentrer progressivement dans les détails.
var http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(8888,function() {
console.log('Serveur démarré sur le port 8888');
});
7.1 Le principe du serveur HTTP sous node
On crée un serveur HTTP via la méthode createServer du module http. On lui dit ensuite d'écouter sur le port 8888. On aurait pu le faire écouter sur une IP particulière en ajoutant l'IP en argument à la fonction listen, par exemple : listen(8888, "127.0.0.1", function() {...}).
La méthode createServer prend en argument un callback. À chaque fois qu'un client vient se connecter sur le serveur, ce callback est appelé avec deux arguments : le premier représente la requête HTTP (c'est un readable stream), le second représente la réponse (c'est un writable stream).
7.2 La requête
Pour être plus précis, le callback que l'on passe à la méthode createServer est appelé une fois que node.js a reçu et analysé les headers HTTP. Nous disposons de ce fait, dans l'objet request, des informations suivantes :
- request.method : la méthode HTTP utilisée. Par exemple GET, POST, …
- request.url : l'URL demandée. Par exemple : /index.html?page=2.
- request.httpVersion : la version HTTP que le client a utilisé. Par exemple : 1.1.
- request.headers : un objet contenant les headers communiqués par le client.
Dans le cas d'une requête POST ou PUT, nous avons besoin de récupérer le corps de la requête. L'objet request étant un readable stream, on le fait comme toujours en ajoutant des listeners sur les événements data et end. Ce qui donne :
var http = require('http');
http.createServer(function (request, response) {
var body = "" ;
if ( request.method == "POST" || request.method == "PUT" ) {
request.setEncoding("utf8") ;
request.on("data", function(data) {
body+=data ;
}) ;
}
request.on("end",function() {
console.log("le corps de la requète contient "+body.length+" caractères") ;
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}) ;
}).listen(8888,function() {
console.log('Serveur démarré sur le port 8888');
});
Nous avons aussi besoin d'analyser plus précisément l'URL appelée, afin d'en extraire le chemin (path), la requête, … Pour cela, node met à notre disposition le module url. Le module url analyse une url et renvoie un objet contenant ses différentes composantes. Illustrons par un exemple l'utilisation du module url, en créant un nouveau fichier urltest.js :
var urlmodule = require("url") ;
var url = "/test/index.html?page=1&view=full#second-chapter" ;
console.log(urlmodule.parse(url,true) ;
Ce script, lorsqu'on l'exécute, affiche :
$ node urltest.js
{ href: '/test/index.html?page=1&view=full#second-chapter',
hash: '#second-chapter',
search: '?page=1&view=full',
query: { page: '1', view: 'full' },
pathname: '/test/index.html' }
$
Nous passons à la méthode parse du module url l'url que l'on souhaite analyser, et true en deuxième argument afin que node crée l'objet query à partir de la chaîne de caractères search. Notre serveur web ressemble alors à :
var http = require('http'), url = require("url");
http.createServer(function (request, response) {
request.parsedUrl = url.parse(request.url) ;
var body = "";
if ( request.method == "POST" || request.method == "PUT" ) {
request.setEncoding("utf8") ;
request.on("data", function(data) {
body+=data ;
}) ;
}
request.on("end",function() {
console.log("le corps de la requète contient "+body.length+" caractères") ;
response.writeHead(200, {'Content-Type': 'text/plain'});
response.write("Vous avez demandé la page "+request.parsedUrl.pathname+"\n") ;
response.end('Hello World\n');
}) ;
}).listen(8888,function() {
console.log('Serveur démarré sur le port 8888');
});
7.3 La réponse
Nous avons déjà commencé dans les exemples à appréhender le fonctionnement de l'objet response fourni au callback d'un serveur HTTP. La réponse à une requête HTTP est constituée de deux principales étapes, représentées sous node par deux fonctions.
La première étape est d'envoyer les headers HTTP de réponse. C'est ici que l'on spécifie le code retour HTTP (200 si tout va bien, 404 si on veut indiquer que l'URL demandée n'existe pas, …). On spécifie aussi, si besoin, des headers, qui permettent par exemple de contrôler le cache, d'envoyer un ou des cookies au navigateur, ... La méthode permettant de réaliser cette étape se nomme writeHead. Elle prend comme arguments le code retour HTTP et un objet contenant les headers à envoyer. Il est indispensable d'utiliser cette méthode avant de commencer à transmettre le corps du message. Dans le cas contraire, node.js remonte une exception et arrête le traitement.
La seconde étape consiste bien entendu à envoyer le corps du message. L'objet response met à notre disposition deux méthodes pour ce faire : write et end. On peut en effet envoyer le corps de la réponse progressivement via la méthode write. Par contre, il est indispensable de terminer par la méthode end, car c'est via l'appel à cette méthode que node stipulera au navigateur client que la communication est terminée.
Conclusion
La naissance d'une nouvelle technologie dans le monde de l'open source est toujours fascinante : l'engouement des utilisateurs, la multiplication des sous-projets, composants, modules, l'engorgement des listes de discussion, l'apparition progressive de ces technologies dans les titres de nos flux RSS, l'ébullition des premiers mois, ... Alors, quel avenir pour node.js ? Radieux à mon avis, vu le nombre de développeurs connaissant Javascript, la symbiose technologique entre le navigateur et le serveur, la dévotion des développeurs et la souplesse de l'outil. J'espère en tous cas vous avoir donné envie de consacrer quelques heures pour installer votre première node.
Liens
Node.js : http://nodejs.org
Spécification CommonJS : http://wiki.commonjs.org/wiki/Modules/1.0
Les modules créés pour node.js : https://github.com/ry/node/wiki/modules