Programmation JavaScript Serveur Node.js
Romuald THION
Semestre printemps 2023-2024 UCBL
fetch côté client💡 On a souvent besoin d’un handler pour vérifier la réponse HTTP pour faire la différence entre :
ok (MDN)function checkOK(response) {
if (!response.ok) {
throw new Error(`[${response.status}] ${response.statusText}`);
}
return response;
}🗒️ Remarques :
Error (MDN).response pour chaînage.Promisefetch("https://httpbin.org/status/418")
.then(checkOK)
.then((resp) => console.log(resp.status))
.catch(console.error);async/awaitDémonstration du résultat attendu du TP4-b.
Exemple sur https://lifweb.univ-lyon1.fr/
fetch) pour voushealth via l’interfaceÀ combiner avec jq pour le rendu.
{
"postgresInfos": {
"postgresVersion": "16.2 (Ubuntu 16.2-1.pgdg22.04+1)",
...
"driver": "https://github.com/porsager/postgres"
},
"serverId": "lifweb:369857:lsuib8s3",
"serverStartedAt": "2024-02-20T15:14:54.519Z",
"serverUri": "http://localhost:8001",
...
"title": "lif-web-challenge-server",
"version": "1.1.1"
}fetchhttps://curlconverter.com/ permet de traduire la
commande curl.
🤩 Méthode (non programmable) préférée 🤩
curl ;🚧 Node.js s’aligne progressivement sur EcmaScript et les APIs Web, mais les APIs historiques restent. 🚧
S’utilise comme Python ou OCaml (sans compilation) :
node file.js💡 La majorité des outils de l’écosystème JS utilisent Node.js.
À la différence de Python ou OCaml :
Avec AutoCannon : 2 vCPUs, Nginx HTTPS, accès PostgreSQL. Réseau fibre SFR.
autocannon --connections 512 --workers 16\
"https://lifweb.univ-lyon1.fr/health"
# ┌─────────┬────────┬───────────┬───────────┬─────────┐
# │ Stat │ 97.5% │ Avg │ Stdev │ Max │
# ├─────────┼────────┼───────────┼───────────┼─────────┤
# │ Latency │ 206 ms │ 100.08 ms │ 279.16 ms │ 6635 ms │
# └─────────┴────────┴───────────┴───────────┴─────────┘
# ┌───────────┬─────────┬────────┬────────┬─────────┐
# │ Stat │ 97.5% │ Avg │ Stdev │ Min │
# ├───────────┼─────────┼────────┼────────┼─────────┤
# │ Req/Sec │ 4,535 │ 3,968 │ 417.74 │ 3,061 │
# ├───────────┼─────────┼────────┼────────┼─────────┤
# │ Bytes/Sec │ 3.77 MB │ 3.3 MB │ 348 kB │ 2.54 MB │
# └───────────┴─────────┴────────┴────────┴─────────┘📄 perf.json.
🔮 Standardisation JS backend https://wintercg.org/. 🔮
🏆 Là où Node.js brille : I/O concurrentes. 🏆
🤔 Voir le TP11 de LIF - Système d’Exploitation
💡 Processus/threads de travail auquel sont délégués les traitements via un démultiplexeur.
libuvlibuvInitialement conçue comme la boucle
d’événements de Node.js, la libuv est utilisée comme
boucle d’événements en Python (uvloop), Lua (Luvit), …
while there are still events to process:
e = get the next event
if there is a callback associated with e:
call the callback
☣️ Bloquer le thread principal avec du calcul des I/O synchrones, c’est bloquer la boucle d’événements.☣️
Les handlers doivent :
sync.Don’t Block the Event Loop (or the Worker Pool).
Node.js, lots of ways to block your event-loop (and how to avoid it).
En fait, l’histoire est plus complexe. C’t-à-dire que l’mec a dit : … Vald, « Bonjour ».
🔖 Voir Microtasks - javascript.info, The Node.js Event Loop, Timers, and process.nextTick() - Node.js, When to use queueMicrotask() vs. process.nextTick() - Node.js.
🔁 Il y a plusieurs queues d’événements :
⚙️ API queueMicrotask - MDN, queueMicrotask - Node.js, setImmediate - Node.js, timerPromises.setImmediate - Node.js et process.nextTick - Node.js.
import { setTimeout, setImmediate } from "node:timers";
import { setTimeout as setTimeoutPromise } from "node:timers/promises";
console.info("Start");
setImmediate(() => console.log(0));
queueMicrotask(() => console.log(1));
setTimeout(console.log, 0, 2);
setTimeoutPromise(0, 3).then(console.log);
setTimeout(console.log, 0, 4);
Promise.resolve(5).then(console.log);
console.info("End");💡 Affichera dans l’ordre Start, End,
1, 5, 0, 2,
3 puis 4.
The Node.js Event Loop, Timers, and process.nextTick() - Node.js.
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
💡 Ce diagramme est en fait celui de libuv…
https://nodejs.org/api/events.html#events
Much of the Node.js core API is built around an idiomatic asynchronous event-driven architecture in which certain kinds of objects (called “emitters”) emit named events that cause Function objects (“listeners”) to be called.
Toute l’architecture Node.js est asynchrone, orientée événements :
💡 A la différence de LIF - Programmation Concurrente et Administration :
libuvimport { EventEmitter } from "node:events";
/* ... */
const emitter1 = new EventEmitter();
emitter1.last = hrtime.bigint();
emitter1.on("ping", async (value, time) => {
console.info(`[1] received ${value}@+${time - emitter1.last}`);
emitter1.last = time;
emitter2.emit("ping", value, hrtime.bigint());
});
/* ... idem emitter1 */
emitter1.emit("ping", 0, hrtime.bigint());The Observer pattern defines an object (called subject) that can notify a set of observers (or listeners) when a change in its state occurs. (Node.js Design Patterns)
📖 Un des 23 designs patterns du Gang of Four, classé behavioural pattern, central de la conception des événements Node.js (et navigateur).
EventEmitter
e.on(...)e.emit(...)event : stringEventTarget
t.addEventListener(...)t.dispatchEvent(...)Event : classeconst ee = new EventEmitter();
const listenEmit = (id) => (value) => console.log(`[${id}]: ${value}`);
ee.on("ping", listenEmit("A"));
ee.emit("ping", 42);
const et = new EventTarget();
const listenTarget = (id) => (event) => console.log(`[${id}]: ${event.detail.value}`);
et.addEventListener("ping", listenTarget("A"));
// ici avec un CustomEvent
et.dispatchEvent(new CustomEvent("ping", { detail: { value: 42 } }));👉 EventTarget s’aligne sur le navigateur et le standard. Voir Prefer
EventTarget over EventEmitter.
// callback style CPS
function helloCallback(callback) {
setTimeout(() => callback(undefined, "hello world"), 100);
}
helloCallback((error, message) => console.log(message));
// callback style event
import { EventEmitter } from "node:events";
function helloEvents() {
const eventEmitter = new EventEmitter();
setTimeout(() => eventEmitter.emit("complete", "hello world"), 100);
return eventEmitter;
}
helloEvents().on("complete", (message) => console.log(message));💡 helloCallback() et helloEvents() sont
fonctionnellement équivalents :
👉 EventEmitter apporte une abstraction sur
Continuation Passing Style (CPS).
import net from "node:net";
function handleConnection(socket) {
socket.on("error", (error) => console.error(`error...`));
socket.on("end", () => console.debug(`closed...`));
socket.on("data", (chunk) => socket.write(`server ${chunk}`));
socket.write("Bonjour !\n");
}
const server = net.createServer();
server
.on("connection", handleConnection)
.on("listening", () => console.debug(`listening...`))
.listen(1337);🤔 TP11 de LIF - Système d’Exploitation.
npm : Node Packet Manager. pNpM et Yarn sont des successeurs.
npm est à Node.js ce que pip est à
Python, avec un support natif des environnements type venv
.
package.json{
"name": "cm4_exemples",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "DEBUG=app nodemon server.js"
},
"dependencies": {
"dotenv": "^10.0.0"
},
"devDependencies": {
"eslint": "^7.32.0",
"nodemon": "^2.0.12"
}
}La définition du projet et dépendances, voir docs.npmjs.com dépendances.
JSON = JavaScript Object Notation : une représentation textuelle (une serialization) des objets JavaScript
Avec le fichier package.json précédent :
npm install
# added 296 packages in 2s
npm run dev
# > cm4_exemples@1.0.0 dev
# > cross-env DEBUG=app nodemon server.mjs
# [nodemon] 2.0.19
# [nodemon] to restart at any time, enter `rs`
# [nodemon] watching path(s): *.*
# [nodemon] watching extensions: js,mjs,json
# [nodemon] starting `node server.mjs`
# app Server listening at http://127.0.0.1:5000/ +0msnpx permet d’exécuter des commandes locales au
dossier (dans node_modules/) sans les installer
globalement
Executes
commandeither from a localnode_modules/.bin, or from a central cache, installing any packages needed in order forcommandto run.
npx est implicitement utilisé par les
scripts du package.json.
/tmp/npx❯ npx cowsay "Hello world"
Need to install the following packages:
cowsay@1.5.0
Ok to proceed? (y) y
_____________
< Hello world >
-------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
/tmp/npx❯ ll
/tmp/npx❯
🤯 Plusieurs systèmes d’import co-existent : 🤯
Common JS (CJS)
const maLib = require('laLib');module.exports = { ... };.jsEcmaScript (ESM) :
import maLib from 'laLib';export default { ... };.mjs💡 Préférer les modules ESM 💡
Voir http://lifweb.pages.univ-lyon1.fr/package.json.
💯 Servira désormais de référence. 💯
Voir CM5 sur https://eslint.org/, https://prettier.io/, etc.
📐 On choisit une convention et on s’y tient. 📐
.env et cross-env (GitHub)