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.Promise
fetch("https://httpbin.org/status/418")
.then(checkOK)
.then((resp) => console.log(resp.status))
.catch(console.error);
async/await
Dé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"
}
fetch
https://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.
libuv
libuv
Initialement 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 :
libuv
import { 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/ +0ms
npx
permet d’exécuter des commandes locales au
dossier (dans node_modules/
) sans les installer
globalement
Executes
command
either from a localnode_modules/.bin
, or from a central cache, installing any packages needed in order forcommand
to 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 = { ... };
.js
EcmaScript (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)