CentraleSupélecDépartement informatique
Gâteau du Glouton
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
TD Jeudi — NodeJS

L'objectif de cette partie est de vous permettre de réaliser un service web pour la partie configuration de l'appli web.

Notre application a besoin de faire deux choses:

  1. Fournir les pages statiques html, css et javascript lors de la connection d'un client
    • Ceci est réalisé par le premier mini-site que vous avez implémenté
  2. Garder sur le serveur les paramètres de configuration de notre application, et les délivrer sous la forme du fichier JSON
    • Ceci va être réalisé par un micro-service

On aura donc 2 serveurs web:

Voir le tuto en rapport et les slides

Pour la livraison à la fin de la journée

Sur Edunao !

1 - Un micro-service pour la configuration

Le développement d'une application web avec Node peut être très fastidieuse, c'est pourquoi nous utiliserons la bibliothèque Express.js qui fournit un ensemble de fonctions facilitant le développement de ce type d'applications.

1.1 - Configuration

Clonez le repl https://replit.com/@CSappliWeb2022/ConfigMeteo.

Observez les différentes parties:

  • Dans la zone "fichiers", vous avez un fichier server.js : c'est le code javascript exécuté par NodeJS. Il pourrait y avoir d'autres fichiers
  • En dessous de ce fichier, il y a les fichiers de configuration du programme NodeJS. Ils sont apparu car j'ai avant installé pour vous la librairie express. Vous pouvez en installer d'autres...
  • À droite, il n'y a pour le moment qu'une console qui dit "Node" : ce n'est pas la console du navigateur mais celle du programme NodeJS.

Pour l'instant le programme ne tourne pas. Lancez le programme en appuyant sur "Run".

  • Maintenant au dessus de la console vous avez la racine de votre site. Pour moi, il s'agit de https://ConfigMeteo.csappliweb2022.repl.co.
  • En dessous, vous avez toujours la console NodeJS. Le programme s'exécute: "App listening on port 8080..." s'affiche. C'est la commande de la ligne 9 dans server.js.

À l'inverse d'un repl purement statique comme la dernière fois, ici vous avez les manettes pour décider de comment votre serveur (le mien est https://ConfigMeteo.csappliweb2022.repl.co) réagit aux requêtes HTTP: tout se programme dans server.js. En particulier, si vous n'aimez pas le fait que le fichier racine soit index.html, pointez la racine sur un autre !

D'ailleurs, vous noterez que pour le moment, la page répond "Cannot GET /". On va changer cela.

Définition d'une route

Nous allons maintenant ajouter une "route", c'est-à-dire une requête HTTP que votre application va accepter et traiter. Une route est constituée de deux éléments : un verbe HTTP (par exemple : GET, POST, PUT…) et un chemin (par exemple : /voici/un/chemin, /chemin, /un/autre/long/chemin…).

  • Ajouter au code précédent la route suivante, qui définit que l'application accepte une requête GET sur le chemin / (l'application répondrait donc pour moi sur l'adresse https://ConfigMeteo.csappliweb2022.repl.co) :
app.get('/', function(req, res) {
    //ici construire la réponse HTTP
});
  • Dans le corps de la fonction de callback de cette route, ajouter le code nécessaire pour que l'application réponde un texte de votre choix. Utiliser pour cela la commande res.send("du texte");

Test

  • Appuyez (ou ré-appuyez) sur "Run".
  • Regardez dans le panneau en haut à gauche, ou allez à l'url de votre site et observez le résultat.

Note

Quand vous faites des changements dans server.js, il faut "relancer" NodeJS.

1.2 - Notre serveur comme un service

Pour l'instant, on charge les préférence à l'aide d'un fichier JSON écrit en dur.

On va plutôt transformer la délivrance de ce contenu en un "service web", qui sera généré automatiquement par le serveur nouvellement créé. On va allouer pour cela une nouvelle route /config.

app.get('/config', function(req, res) {
   ...
});

À faire

  • Créez dans server.js une nouvelle variable globale qui contient le contenu du fichier JSON:
var config = {
    "temp": {
        "unite": 1,
        "state": 1
    },
    "pression": {
        "unite": 1,
        "state": 1
    },
    "nuage": {
        "visib": {
            "unite": 0,
            "state": 1
        },
        "status": 1
    },
    "vent": {
        "unite": 0,
        "state": 1
    }
}
  • Créez une nouvelle route /config comme indiqué.
  • La fonction de callback de cette nouvelle route va simplement renvoyer le contenu de la variable sous forme json. À la place de res.send, on utilise la fonction res.json(config).
  • Relancez le serveur, et testez avec votre navigateur en allant à l'adresse https://xxx.xxx.repl.co/config
  • modifiez l'appel à la fonction fetch dans le fichier javascript meteo.js: à la place de "config.json", utilisez "https://xxx.xxx.repl.co/config" (n'oubliez pas le "/" qui indique la racine de l'adresse).
  • Effacez votre fichier json et rechargez votre site : en principe, l'appel à fetch doit s'effectuer et les bonnes choses doivent s'afficher.
    • Si vous utilisez l'exemple, si vous voyez des XXXX à la place des unités, c'est que cela ne marche pas...

2 - Rendons le fichier config.html fonctionnel

Notre application météo possède un fichier que nous n'avons pour le moment pas développé outre mesure : le fichier config.html. Nous allons remédier à cela dans cet exercice.

Il s'agit d'un formulaire qui permet en théorie de modifier les paramètres de configuration de l'application. Donc concrètement, les valeurs du formulaire vont être envoyées au micro-service qui les utilise pour changer sa variable globale config.

3.1 - Remplissage du formulaire au démarrage

Lorsque vous chargez la page, celle-ci comporte un formulaire vide... C'est un peu agaçant: on aimerait que le formulaire reflète les choix contenus dans la variable config du serveur. Pour cela, nous avons essentiellement deux choix:

  1. Utiliser un template au niveau du serveur pour générer une page config.html directement remplie
  2. Avoir une page fixe, mais qui fait un appel AJAX lorsque la page est chargée au service web /config et qui utilise les valeurs récupérées pour cocher les bonnes cases

Nous allons utiliser cette deuxième méthode, mais vous êtes invités à regarder les templates si cela vous intéresse. Il en existe beaucoup...

À faire

  • Chargez la page et assurez-vous que les valeurs correspondant à la variable config sont bien sélectionnées.

3.2 - Envoi des valeurs au serveur

La balise <form> admet deux attributs intéressant:

  • action : l'adresse à appeler en lui passant les paramètres
  • method : peut être get ou post. Essayez les deux et regarder le résultat: dans le premier cas, les valeurs du formulaire sont sur la ligne d'addresse. Dans l'autre, les valeurs sont "cachés" : elles sont dans le corps de l'appel HTTP.

Au niveau du serveur...

Derrière l'adresse à passer au formulaire se trouve le serveur: il faut qu'il réagisse correctement en mettant à jour la variable config. On va définir une route spécifique pour cela.

  • Définissez une nouvelle route /set, en mode GET, qui récupère le paramètre monparam et affiche sa valeur.
app.get('/set', function(req, res) {
  if("monparam" in req.query) {
    var txt = "Donnee reçue :" + req.query.monparam;
    res.send(txt);
  } else {
    res.send("pas de paramètre monparam");
  }
});
app.get('/config', function(req, res) {
   ...
});
  • Relancez le serveur, et allez à l'adresse http://xxx.xxx.repl.co/set?monparam=toto avec votre navigateur. Relancez avec une valeur autre que "toto".
  • Dans le fichier config.html, faitez que le formulaire appelle "/set" en mode GET.
  • Modifiez la fonction de callback de la route /set pour qu'elle affiche l'un des paramètres.
  • Testez en ouvrant la page config.html et en utilisant le bouton <submit>.
  • Au lieu de renvoyer du texte, faites une fonction de callback qui ressemble à cela:
app.get('/set', function(req, res) {
  // Mise à jour de la variable config
  config.XXX  =  req.query.XXX
  config.YYY  =  req.query.YYY
  ...

  // Redirigez vers config.html
  res.redirect("/config.html");
});
  • Testez : En principe, si vous modifiez un paramètre dans le formulaire et cliquez sur le bouton <submit>, le formulaire se recharge avec les bonnes valeurs.
  • Notez que cela n'est vrai que si vous chargez le script APRÈS le chargement de la page : si vous placez le script dans l'entête du document sans attendre l'évenement load, le formulaire se rechargera vierge (essayez!)
  • Vous avez en principe maintenant une application "complète" : la page configuration est fonctionelle.

3.3 - Discussion

Le choix que nous avons fait dans cette implémentation est très "classique": le formulaire est équipé d'un bouton "submit" qui envoie les infos en mode POST et qui recharge la page. La page est "statique".

Un autre choix, plus moderne et "AJAX" dans l'esprit aurait été de ne pas utiliser de bouton "submit" mais de faire des appels asynchrones au serveur pour mettre à jour la variable globale config à chaque modification d'un champ du formulaire.

Vous pouvez implémenter cette possibilité si vous le souhaitez, il n'y a à priori pas de difficultés particulières.

4 - Optionnel : Utilisation d'une base de donnée

Pour le moment, la configuration est stockée au niveau du serveur dans une variable. Si le serveur s'arrête, on perd les informations. On va dans cet exercice utiliser une base de donnée SQLite3 pour stocker et lire ces données. Voir le tuto sur SQlite3.

Le choix que nous faisons dans cet exercice est le suivant:

  • Au démarrage du serveur, on va lire la base de donnée et initialiser la variable globale config
  • À chaque appel à la route /set, après mise-à-jour de la variable globale config on va mettre à jour la base de donnée.

Que mettre dans la base de donnée ?

  • Il s'agit d'une base de donnée relationnelle, c'est-à-dire essentiellement un tableau. On pourrait donc mettre une ligne par parametre de configuration (une pour le vent, une pour la pression, etc...).
  • Pour les besoin de la cause, c'est un peu lourd. On va simplement mettre une seule ligne dans la base de donnée, et associer à default une chaine de caractère qui sera le contenu de la variable config.

4.0 - Vérifions que je ne mens pas

Vérifiez que relancer le serveur efface toutes les modifications !

4.1 - Installation de SQlite3

Pour installer une bibliothèque avec NodeJS dans repl.it, il faut cliquer ici:

Vous devriez voir apparaitre un panneau dans lequel vous pouvez chercher des librairies: on veut sqlite3:

  • Sélectionnez et cliquez sur le symbole "+". Plein de choses vont se passer dans la console.
  • Maintenant, vous pouvez l'importer avec
var sqlite3 = require('sqlite3');
sqlite3.verbose(); // pour obtenir des informations sur l'exécution
                   // des requêtes SQL (utile pour le débug)

4.2 - Création d'une base de donnée config.db3

On pourrait le faire en local, en installant des outils sur votre machine, mais on peut le faire directement avec repl.it. Je vous ai fait un petit repl pour cela: https://repl.it/@CSappliWeb2022/sqlite.

  • Ouvrez-le, lancez-le.
  • Dans la console NodeJS, vous devriez voir apparaître
Hint: hit control+c anytime to enter REPL.
Database { open: false, filename: 'config.db3', mode: 65542 }
default : {"temp":{"unite":1,"state":1},"pression":{"unite":1,"state":1},
"nuage":{"visib":{"unite":0,"state":1},"status":1},"vent":{"unite":0,"sta
te":1}}
  • Si c'est ça, le fichier config.db3 dans la liste des fichiers est la base de donnée que l'on va utiliser.
  • Téléchargez-le et mettez-le dans votre projet meteo-NodeJS à vous.

Note

Vous avez ici un petit exemple minimal de comment fabriquer une base de donnée sqlite3 avec repl.it sans rien installer... Vous pouvez vous en inspirer si vous avez besoin d'une base de donnée dans votre projet par exemple.

4.3 - Connexion de l'application Express à la base de donnée config.db3

  • Puis créez une connexion sur la base de données contenue dans le fichier "config.db3" :
var db = new sqlite3.Database(__dirname + '/config.db3');
  • Si vous faites
db.all("COMMANDE SQL", function(err,rows) {
    console.log(rows[0].value);
});

avec votre commande SQL vous devriez voir dans le terminal s'afficher la chaine JSON.

  • Plutôt que de l'afficher, utiliser la fonction JSON.parse(chaine-de-caractere) pour définir la variable config.
  • Maintenant, on est prêt à travailler: en principe, la variable config est chargée au démarrage du serveur par le contenu de la base de donnée.
  • La dernière chose à faire est de mettre à jour la base de donnée lors de l'appel à la route /set.
    • juste après la redéfinition de la variable config dans la fonction de callback, faites un appel à la base de donnée pour écrire avec JSON.stringify(config) le contenu de la variable config dans la base. Utilisez par exemple la commande db.run("COMMANDE SQL") avec la bonne commande.
    • Encore une fois, essayez avant en utilisant la ligne de commande sqlite3 pour valider votre commande.
  • Ouf ! Maintenant tout est complet. Si vous fermez le serveur puis le relancez, en principe la configuration est maintenant préservée !

Conclusion

L'application est très simple :

  • il n'y a pas de notion d'utilisateurs
  • la ville n'est pas configurable
  • ...

On peut la complexifier à loisir. Néanmoins, dans cette série de TDs nous avons vu comment faire une application relativement complète, avec un back-end qui fait appel à une base de donnée. C'est la base.

Pour aller plus loin:

  • Pour gérer des utilisateurs, on pourrait utiliser des cookies de session, par exemple avec la librairie express-session.
  • On pourrait aussi ajouter la ville en paramètre
  • L'application pourrait aussi donner les prévisions...