CentraleSupélecDépartement informatique
Gâteau du Glouton
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
Coté serveur: javascript pour les services web

Introduction

Dans ce tutoriel, nous allons voir le framework Node.js, qui permet de développer rapidement un web service répondant avec des données formatées ou des pages internet.

Le modèle standard

Dans un site web traditionnel, le serveur répond aux requêtes des clients en renvoyant le contenu d'un fichier: Si un client requiert l'adresse http://domain.com/page.html, typiquement le serveur du domaine domain.com va aller chercher le fichier page.html, le lire et l'envoyer sur le réseau en réponse. C'est ce que propose par défaut les serveurs web tels que le serveur ISS de Microsoft, Apache, ou encore le léger lighttpd.

Un modèle un tout petit peu plus dynamique permet à un serveur internet d'exécuter un script pour répondre à une requête, et à rediriger la sortie du script en réponse à la requête. En général, l'interface utilisée est CGI (pour Common Gateway Interface). Tous les serveurs permettent cela.

Quelques langages usuels de scripts:

  • Perl (avec l'extension ".pl")
  • PHP (avec l'extension ".php")
  • ASP (pour Active Server Pages, essentiellement avec le serveur IIS)
  • Mais aussi Python, Ruby...

Le modèle Node.js

À l'inverse de ce schéma, Node.js est un programme autonome. Il est basé sur le moteur d'exécution JavaScript V8 de Google et a été créé en 2009 par Ryan Dahl (Joyent Inc.). Écrit en C++, c'est essentiellement une plateforme d'exécution javascript avec des APIs spécialisées pour gérer le fait d'être un serveur: la gestion des connections réseaux, mais aussi le système de fichiers,... On a donc pas de "comportement par défaut", et tout doit se programmer en javascript.

Les grands principes sous-tendant Node.js sont:

  • Éxécution pilotée par les événements
  • Appels de fonctions asynchrones (utilisation de callback)
  • Entrées/sorties non bloquantes

Les deux premiers sont "standards" dans la philosophie javascript. Le troisième principe est essentiel de la part d'un serveur: on souhaite qu'il reste réactif autant que possible.

Des exemples de l'utilisation de Node.js en production:

Un serveur Web avec Node.js

Un serveur Node.js consiste au minimum en

  • le programme nodejs
  • un fichier javascript

Un script pour Node.js va faire les choses suivantes:

  • Initialiser des objets correspondant aux librairies avec la fonction require
  • par exemple, la librairie http qui gère le protocole HTTP
  • définir un serveur
  • lancer le serveur

Par exemple, voila un code minimal.

var http = require('http');

var serv = http.createServer( //création d'un serveur web

  function (req, res) { //callback sur les requêtes HTTP
      //construction d'une réponse HTTP
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.write('Hello world !');

      res.end(); //envoi de la réponse
  }

);

serv.listen(8000); //commence à accepter les requêtes

console.log("Server running at http://localhost:8000");

Pour l'exécuter:

  • Placez ce texte dans monserveur.js (avec gedit ou jedit par exemple)
  • Ouvrez un terminal
  • Rendez-vous dans le dossier où vous avez sauvé le fichier:
cd le/répertoire/en/question
  • Tappez la commande
nodejs monserveur.js
  • Dans un navigateur, entrez l'adresse du serveur:
http://localhost:8000
  • Si le message "Hello World" s'affiche: bravo, vous avez un serveur web.

Le concept

Comme dit plus haut, le script Node.js ne fait rien tout seul: il ne fait que réagir à des événements. Ici, on crée un objet serv qui contient les propriétés pour de fait être un serveur. La principale caractéristique d'un serveur est la façon dont il réagit aux requêtes: c'est le but de la fonction (non nommée) en argument de la méthode createServer. Cette fonction est appelée à chaque fois qu'un client se connecte: elle prend en argument req, objet qui décrit la requête, et res, objet que la fonction va peupler et qui décrit la réponse HTTP (voir le cours/tuto HTTP).

La deuxième chose que fait le le script est de demander au serveur de l'objet serv d'écouter le port 8000.

Donc le serveur ne fait rien tant qu'un client ne se connecte pas. Son comportement est ensuite complètement décrit par la fonction non-nommée liée par la méhode createServer.

Voir l'échange HTTP

Avec Firefox, vous pouvez utiliser par exemple l'extension Live Http Headers.

Boucle de traitement

  • Un seul programme (un seul thread) traite les événements dans l'ordre de leur occurrence
  • Les traitements qui concernent les entrées/sorties sont exécutées de manière asynchrone par d'autres threads: « non blocking I/O »

Slogan:

Avec Node.js, on ne fait que spécifier des réponses à des événements

Et donc tout est virtuellement en parallèle.

Exemple: serveur plus évolué

On va utiliser la librairie "fs" qui permet d'accèder au système de fichier. Ici:

  • __dirname correspond au dossier où se trouve le programme javascript (voir ce post et la doc)
  • La fonction readFile() génére un événement lorsque le fichier est ouvert (ou qu'une erreur survient), et la fonction en argument est appelée à ce moment.
  • Le contenu du fichier est du texte. Le serveur n'a à priori pas moyen de savoir plus. Ici, on a donné comme type de contenu text/plain. Vous pouvez essayer text/html, pour voir. En général, on utilise l'extension du fichier pour lui associer un type de contenu -- mais il faut le faire faire au serveur explicitement. On peut par exemple utiliser la librairie node-mime, mais sinon plus simplement utiliser la bibliothèque express : voir plus bas.
var http = require('http');
var fs = require('fs');

//création d'un serveur web
var serv = http.createServer(
    //callback sur les requêtes HTTP
    function traiteRequete(req, res) { 
       //log de l'url demandée
       console.log(req.url);
       //construction d'une réponse HTTP
       fs.readFile(__dirname + req.url, "utf-8", 
          //code exécuté quand le fichier est effectivement ouvert
          function (err, fd) {
              if (err) { // une erreur est survenue
                  res.writeHead(500);
                  res.write(err.message);
                  res.end();
              } else { // sinon, on produit le fichier voulu
                  res.writeHead(200, { "Content-Type" : "text/plain" } );
                  res.write(fd);
                  res.end(); //envoi de la réponse
              }
          });
    });

serv.listen(8000); //commence à accepter les requêtes

console.log("Server running at http://localhost:8000");

Les bibliothèques

Node.js possède une communauté vibrante à l'origine de plus de 240000 projets, centralisée par le gestionnaire de paquets npm (Node Package Manager). Une bibliothèque Node.js s'installe avec la commande

npm install unebibliothequeutile

Notez que pour que cela fonctionne sur les machines du bâtiment Bréguet, il faut d'abord configurer le proxy:

npm config set https-proxy http://proxy.supelec.fr:8080

En standard dans Node :

  • http : pour communiquer via le protocole HTTP
  • fs : pour interagir avec le système de fichiers
  • timer : pour schéduler des traitements
  • crypto : pour chiffrer/déchiffrer des données

Exemple de paquets accessibles le Node Package Manager (NPM) :

  • jquery : pour avoir accès aux facilités de jQuery
  • async : helpers pour faciliter la gestion d'appels asynchrones
  • express : pour construire rapidement des applications web (plus bas dans la page)
  • sqlite3 : pour connecter à une base de données SQLite (dans le tuto correspondant)
  • mongoose : pour connecter à une base de données MongoDB
  • ...

Applications web avec Express

La librairie express permets de développer plus simplement des applications web. En particulier, elle permet d'associer à une "route" une fonction de callback.

Dans ce schéma, une "route" est la donnée d'un mode de requête (GET ou POST) et d'un chemin depuis la racine du site.

Installation

Pour l'installer, configurer le proxy (uniquement sur les machine du bâtiment Bréguet):

npm config set https-proxy http://proxy.supelec.fr:8080

puis

npm install express

Utilisation

On peut par exemple configurer deux routes:

var express = require('express');
var app = express();

app.get('/accueil', function(req, res) {
  res.sendfile(__dirname + "/page.html");
});

app.get('/autre', function(req, res) {
  res.sendfile(__dirname + "/autrepage.html");
});

app.listen(8000);
console.log("App listening on port 8000...");

Ici, à chaque "route" on associe une fonction (qui ici fait quelque chose de simple). Pour que cela fonctionne:

  • Dans le répertoire du fichier javascript, placez un fichier "page.html" et un fichier "autrepage.html".
  • Dans votre navigateur, essayez les addresse
http://localhost:8000/accueil

et

http://localhost:8000/autre

la fonction sendfile

C'est une fonction spécifique de la librairie express, qui envoie le fichier avec le bon type mime en fonction de l'extension.

Avec des paramètres

Il est possible de récupérer les paramètres donnés dans l'url.

var express = require('express');
var app = express();

app.get('/autre', function(req, res) {
  if("donnee" in req.query) {
    var txt = "Donnee reçue :" + req.query.donnee;
    res.send(txt);
  } else {
    res.sendfile(__dirname+'/autrepage.html');
  }
});

app.listen(8000);
console.log('App listening on port 8000...');

Essayez l'adresse http://localhost:8000/autre?donnee=hello pour voir.

Avec express, l'attribut query de la variable req contient la liste des paires (paramètre,valeur) sous la forme d'un objet javascript: on peut donc facilement accèder aux paramètres d'une requête.

Note

Techniquement, le script au dessus est un service web ! Pas très intéressant, certe, mais néanmoins un service web. En utilisant les techniques du TD5, il est possible de faire une page html indépendante qui questionne ce service en ajax.