Node.js : aperçu de l’environnement d’exécution JavaScript

En ce qui concerne les langages de programmation côté serveur comme C ou PHP, les codes sont en général exécutés de manière séquentielle. Cela signifie qu’un serveur ne traite une nouvelle instruction du code que lorsque l’instruction précédente a fini d’être exécutée et a livré un résultat. Dans ce cas, on parle également de tâches synchrones. L’exécution des autres codes est ainsi bloquée aussi longtemps qu’une tâche en cours n’est pas terminée. Ceci peut mener à des ralentissements importants, particulièrement pour les accès au système de données, aux bases de données ou aux services Web.

De nombreux langages de programmation ou environnements d’exécution vont alors donner la possibilité de réaliser les tâches de manière parallèle grâce à ce que l’on appelle les threads. Il s’agit de fils exécutés dans le cadre d’un processus avec lequel les actions sont traitées pendant que le reste est également exécuté. L’inconvénient de cette méthode : plus des fils sont exécutés et plus il est nécessaire de disposer de temps CPU et de mémoire vive (RAM). En d’autres termes, le multithreading est particulièrement gourmand en ressources. De plus, les fils d’exécution supplémentaires s’accompagnent d’un besoin plus grand en programmation. En revanche, l’implémentation de JavaScript côté serveur permet de contourner ce problème. C’est pourquoi Node.js a été créé.

Qu’est-ce que Node.js ?

Node.js est une plateforme logicielle avec une architecture orientée événements qui permet d’utiliser le langage de script JavaScript, initialement développé pour une utilisation côté client, pour une utilisation côté serveur. Cela fonctionne comme PHP, Java, .NET, Ruby ou Python pour écrire du code pour le serveur. Il est utilisé pour le développement d’applications JavaScript côté serveur qui doivent assumer de fortes charges en temps réel. L’environnement d’exécution est apprécié pour la réalisation de serveur Web léger.

La plateforme logicielle, qui a été pensée en 2009 par Ryan Dahl, se base sur le moteur V8 du JavaScript de Google qui intervient aussi sur le navigateur Web Chrome. Lancé par l’entreprise Joyent, le projet dépend depuis 2015 de la fondation Linux sous le nom de Node.js Foundation. Des versions actuelles sont disponibles pour Microsoft Windows, Mac OD, et Linux.

Node.js comprend une librairie de modules JavaScript divers, qui peuvent être chargés grâce à une fonction simple et servir directement comme élément structurel pour le développement d’applications Web. Un exemple est le module HTTP, qui permet grâce à une fonction unique de créer un serveur Web rudimentaire. En outre, le package intégré (dit « NPM » pour Node Package Manager) permet d’installer des modules complémentaires.

L’avantage majeur de Node.js est son architecture orientée événements, avec laquelle un code de programme s’exécute de manière asynchrone. Node.js est donc basé sur un seul fil d’exécution et un système entrée/sortie (ou I/O pour Input/Output) qui permet un traitement parallèle de plusieurs opérations d’écriture et de lecture.

  • I/O asynchrone : parmi les tâches classiques effectuées par un serveur, on compte la réponse aux requêtes, la sauvegarde de données dans une base de données, la lecture de fichiers depuis le disque dur, ou encore l’établissement de connexions vers d’autres composants du réseau. Ces activités sont désignées comme sortantes/entrantes. Les opérations entrantes/sortantes sont exécutées de manière synchrone dans des langages de programmation tels que C ou Java. Ainsi, les tâches sont effectuées les unes après les autres. Cela signifie que le système I/O est bloqué tant que la tâche en cours n’est pas terminée. En revanche, Node.js utilise un I/O asynchrone, qui délègue les opérations d’écriture ou de lecture directement au système d’exploitation ou aux bases de données. Ceci permet de travailler sur un grand nombre de tâches en parallèle sans avoir de blocage. Les applications basées sur Node.js et JavaScript présentent ainsi l’énorme avantage de gagner en rapidité dans de nombreux cas de figure.
  • Fil d’exécution unique : pour compenser le temps d’attente dans les I/O synchrones, des applications serveur basées sur des programmes de langage classiques orientés serveurs s’appuient sur plusieurs threads (avec les inconvénients du multithreading). Ainsi, un serveur Apache HTTP commence par exemple un nouveau fil à chaque requête détaillée. Le nombre de fils possibles (et donc aussi le nombre de requêtes qui peuvent être traitées parallèlement dans un système multi-threadé synchrone) est limité par l’espace mémoire disponible. Node.js en revanche ne fait intervenir qu’un fil d’exécution du fait du system I/O décentralisé ce qui permet de réduire considérablement la complexité mais aussi l’exploitation des ressources.
  • Architecture orientée événements : l’exécution asynchrone d’opérations I/O est réalisée grâce à l’architecture orientée évènements de Node.js. Il se base en réalité essentiellement sur un seul fil (mono-thread) qui se trouve sur une boucle d’évènements. Cette dernière a pour mission d’attendre des évènements et de les gérer. Les évènements sont alors classés en tant que tâches ou résultats. Si la boucle enregistre une tâche, par exemple une requête de base de données, cette dernière va être mise en arrière-plan du processus avec ce que l’on appelle une fonction de rappel (Callback). Le traitement de la tâche n’a pas lieu sur le même fil sur lequel la boucle fonctionne, ce qui permet à cette dernière de passer immédiatement à l’événement suivant. Si une tâche transférée est exécutée, les résultats du processus transféré sont retournés à la boucle comme nouvel évènement grâce à la fonction callback. Par conséquent, la boucle peut déclencher la remise du résultat.

Ce modèle non bloquant orienté évènement a l’avantage de ne jamais laisser inactive l’application sur Node.js en attendant un résultat. Il est ainsi par exemple possible d’exécuter différentes requêtes des bases de données simultanément sans que le programme ne soit retardé. L’établissement d’un site, qui exige différentes requêtes externes, se déroule bien plus rapidement avec Node.js qu’avec un processus de traitement synchrone.

Conseil

Vous pourriez également trouver notre article sur « Deno : un environnement d’exécution pour JavaScript et TypeScript » intéressant.

Installation

Node.js est disponible sur le site Web officiel de la plateforme logicielle et peut être téléchargé avec l’installeur et/ou le paquet binaire, et ce suivant les systèmes d’exploitation. De plus, la Node.js Foundation prévoit avec le code source du logiciel des paquets binaires pour ARM et SunOS, ainsi qu’une image Docker.

Pour installer par exemple Node.js sur un PC Windows, seul le Windows-Installer sera téléchargé, au choix en version 32 ou 34 bits. Outre la version actuelle 6.2.2, la sortie LTS 4.4.7 (support long terme) est disponible, ce que nous avons pris pour base dans ce tutoriel Node.js. Après un double clic sur le fichier .msi, l’installation set-up s’ouvre. Ici, vous devez au préalable accepter les conditions de licence puis pouvez choisir votre emplacement pour tous les éléments à installer.

Pour savoir si l’installation a été effectuée correctement, démarrez Node.js et exécutez un code JavaScript simple. Pour cela, une fonction bien adaptée est console.log(), avec laquelle vous pouvez distribuer des valeurs dans la console Node.js. Classiquement, voici les lignes de codes présentées dans de nombreux tutoriels Node.js :

console.log('Hello world!');

Entrez le code dans la console et confirmez avec le bouton entrée. La fonction indique alors à Node.js comment écrire l’expression « Hello World! » dans la console.

Cet exemple simple montre que Node.js fonctionne bien et que les codes JavaScript peuvent être exécutés. De même, il est possible avec console.log() d’effectuer pour test un calcul dans la console :

console.log (15 + 385 * 20);

Si vous souhaitez émettre des informations détaillées sur la version Node.js installée et son module, vous pouvez recourir à l’instruction suivante de la console Node.js :

process.versions

Node.js-Module

Sur la base de l’environnement d’exécution V8, Node.js vous permet d’écrire des serveurs Web efficaces ainsi que d’autres applications réseau dans le fameux langage de script JavaScript, et met à leur disposition une bibliothèque complète avec des modules de programmes. Ces modules de base bien intégrés à l’environnement d’exécution comprennent des fonctionnalités centrales comme les interactions avec le système d’exploitation, la communication réseau ou des mécanismes de chiffrement. De plus, Node.js donne la possibilité avec son Paket Manager NPM intégré d’installer des modules de programme de manière individuelle.

Modules de base

Pour charger n’importe quel module de base dans une application Node.js, vous aurez simplement besoin de la fonction require(). Cette dernière nécessite une chaîne (« string »), qui déclare comme paramètre le module qui doit être chargé. S’il s’agit d’un module de base, le nom de module suffit. Pour les modules installés ultérieurement, la chaîne doit contenir l’emplacement du module, même quand le module se trouve dans le répertoire actuel (dans ce cas, vous pouvez simplement écrire « ./ »). Les lignes de code suivantes montrent le schéma de base, selon lequel un module peut être chargé dans une application Node.js :

var nommodule = require('emplacement/nommodule');

Si vous cherchez par exemple à charger le module de base « os », voici à quoi ressemblerait le code :

var os = require('os');

Le module « os » offre diverses fonctions grâce auxquelles des informations liées au système d’exploitation peuvent être distribuées. Si un utilisateur souhaite par exemple connaître son espace de mémoire libre en byte, le code suivant intervient :

var os = require('os');
var freemem = os.freemem();
console.log(freemem);

Sur la première ligne, le module « os » est chargé dans la variable de même nom « os ». Le passage de code os.freemem() sur la deuxième ligne représente l’une des fonctions soutenues par le module « os », et sert ainsi à établir la quantité de mémoire libre. La valeur établie sera enregistrée dans la variable freemem. Sur la troisième ligne enfin, la fonction console.log() dont nous avons parlé précédemment, est utilisée pour écrire dans la console la valeur enregistrée de la variable freemem.

Comme le montre la fenêtre console Node.js, une mémoire de 5 gigabytes est actuellement à disposition.

Un liste d’interfaces réseau ou le nom d’hébergement peut également être transmis avec d’autres fonctions du module « os », en l’occurence os.networkInterfaces() ou os.hostname(). Pour en savoir plus sur les fonctions du module os et sur les autres modules de bases de Node.js, consultez la documentation officielle du projet logiciel.

Installation de modules

L’installation de modules de programmes s’effectue avec Node.js grâce au Package Manager (NPM) intégré. Les modules d’extension ne doivent en effet pas être téléchargés manuellement depuis des pages externes puis copiés dans un répertoire. L’installation se limite à une seule ligne de code, qui doit être entrée dans la console du système d’exploitation (et non dans la console Node.js !) Avec les systèmes d’exploitation Windows, il s’agit de l’interpréteur de commandes cmd.exe, qui s’appelle grâce à l’entrée établie dans la boîte de dialogue d’exécution Windows (touche Windows + R). Seule la commande npm install est nécessaire ainsi que le nom du module installé :

npm install nommodule

Tous les modules complémentaires disponibles pour Node.js peuvent être consultés sur le site Web officiel de NPM grâce à la fonction de recherche.

Si vous cherchez par exemple à installer le module de programme « colors », naviguez dans la console du système d’exploitation dans le répertoire souhaité et confirmez l’instruction suivante avec la touche entrée :

npm install colors

La capture d’écran ci-dessous montre l’installation du module color dans le répertoire mis en place précédemment C:\NodeModules :

Pour charger le module maintenant dans une application, la fonction require() déjà énoncée est utilisée. L’exemple de code suivant montre, comment le module « color » est mis en place pour mettre en valeur des éléments dans la console Node.js :

var colors = require('C:/NodeModules/node_modules/colors');
console.log('hello'.green); // outputs green text 
console.log('I like cake and pies'.bold.red) // outputs red bold text 
console.log('inverse the color'.inverse); // inverses the color 
console.log('All work and no play makes Jack a dull boy!'.rainbow); // rainbow

Sur la première ligne de code, le module « colors » de l’emplacement C:/ NodeModules/node_modules/colors est chargé dans la variable du même nom « colors ». Les lignes 2 à 5 comprennent chacune une fonction console.log(), dont les chaînes sont mises en valeur de différentes façons grâce au module color. Vous pouvez ainsi configurer des couleurs (par ex., .red, .green pour le rouge et le vert), des mises en valeur (comme .bold, .inverse pour le gras et l’inversement des couleurs texte/fond) et des séquences (comme .rainbow, pour un texte couleur arc-en-ciel).

En pratique : 8 lignes de code pour votre propre serveur

Une utilisation classique de Node.js est le développement de serveurs Web légers. Un serveur simple « Hello World! » peut se programmer en quelques secondes grâce au module « http » préprogrammé. Tout ce dont vous avez besoin, ce sont les 8 lignes de code JavaScript ci-dessous, qui se créent dans un éditeur de texte et se sauvegardent en tant que fichier JavaScript dans le répertoire voulu. Dans ce tutoriel Node.js, le fichier est nommé webserver.js et déposé dans le répertoire MyServer.

var http = require('http');
var port = 8080;

var server = http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello world!');
});
server.listen(port);

console.log('The server is available at http://127.0.0.1:' + port + '/');

Sur la première ligne de code, le module de serveur Web « http » est chargé grâce à la fonction require() et sauvegardé dans la variable de même nom http.

var http = require('http');

Ensuite, le numéro de port 8080 dans la deuxième ligne est défini et enregistré dans la variable port.

var port = 8080;

Les lignes suivantes de 3 à 6 définissent à l’aide de la fonction createServer() du module http une fonction Callback, qui prend les requêtes du navigateur (req) et donne une réponse (res). La réponse au navigateur est composée d’un header comportant un code statut http 200 (OK) ainsi qu’un texte. Ce dernier est introduit avec la fonction res.end(). Contenu de la réponse : « Hello World! ».

var server = http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hallo Welt!');
});

La septième ligne du code sert à connecter le serveur Web avec la fonction listen() au numéro de port défini dans la ligne 2.

server.listen(port);

La dernière ligne de code émet dans la console l’adresse Web sous laquelle le serveur Web peut être atteint.

console.log('The server is available at http://127.0.0.1:' + port + '/');

Dans cet exemple, il s’agit de l’adresse localhost 127.0.0.1. Le serveur Web est disponible seulement à l’intérieur du système même. Pour le lancer, le fichier JavaScript créé doit être exécuté sur la console du système d’exploitation. Une instruction s’impose donc selon le schéma suivant :

node emplacementfichier/script.js

Dans l’exemple actuel, la commande sera la suivante :

node " C:\MyWebServer\webserver.js

Si vous avez entré toutes les données correctement dans le script et indiqué l’emplacement du fichier sans erreur, la console va émettre l’adresse du serveur :

Le serveur est maintenant prêt à prendre les requêtes. Vous pouvez effectuer des tests grâce à l’adresse Web 127.0.01:8080 dans n’importe quel navigateur Web. Si le serveur a été programmé correctement, la fenêtre de navigateur affiche « Hello World!».