Un environnement d'éxecution Javascript

{firstname:'Stéphane' , lastname : 'Michel', job: 'Software Craftsman'}
© Stéphane Michel
Sommaire / Concepts clefs
SOMMAIRE
Résumé des épisodes précédents

Client serveur

GET http://server:8080/index.html



client
serveur
SOAP

POST /DateCreation HTTP/1.0 Host: www.xyz.org Content-Type: text/xml; charset = utf-8 Content-Length: nnn <?xml version = "1.0"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope" SOAP-ENV:encodingStyle = "http://www.w3.org/2001/12/soap-encoding"> <SOAP-ENV:Body xmlns:m = "http://www.xyz.org/datecreation"> <m:GetDateCreation> <m:SchoolName>ESIR</m:SchoolName> </m:GetDateCreation> </SOAP-ENV:Body> </SOAP-ENV:Envelope>

HTTP/1.0 200 OK Content-Type: text/xml; charset = utf-8 Content-Length: nnn <?xml version = "1.0"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope" SOAP-ENV:encodingStyle = "http://www.w3.org/2001/12/soap-encoding"> <SOAP-ENV:Body xmlns:m = "http://www.xyz.org/datecreation"> <m:GetDateCreationResponse> <m:DateCreation>mars 2010</m:DateCreation> </m:GetDateCreationResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
mais ça reste très verbeux...
C'est super structuré,


client
serveur
REST

GET /DateCreation/ESIR HTTP/1.0 Host: www.xyz.org Content-Type: text/xml; charset = utf-8 Content-Length: nnn

HTTP/1.0 200 OK Content-Type: application/json; charset = utf-8 Content-Length: nnn {"creationDate":"mars 2010"}
A ben voilà !
et REST c'est...
Des méthodes HTTP

Des codes de retour HTTP

De bonnes pratiques


Client serveur









Client serveur











GET http://serv1/
GET http://serv1/
GET http://serv2/
GET http://serv2/
GET http://serv3/
GET http://serv3/

Client serveur











GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
ROUTER / PROXY / GATEWAY

Stateful / session








GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
ROUTER / PROXY / GATEWAY




Stateful / session








GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
ROUTER / PROXY / GATEWAY








Stateful








GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
ROUTER / PROXY / GATEWAY




Stateless / JWT








GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
GET http://serv/
ROUTER / PROXY / GATEWAY


JWT
JWT
JWT
JWT
JWT
JWT
JWT
JWT
JWT
JWT
JWT
JWT






Node.js en quelques mots
Créé par Ryan Dahl en 2009
Voir ici pour plus de détails
En quelques mots
Moteur JavaScript Chrome V8
Modèle d'E/S non bloquant
Enorme ecosystème de modules (npm ou yarn)
Près de 350 000 modules
Plus de 35 000 màj par semaine
102 000 publishers
Lifecycle

Version paires : versions maintenues
Release
Maintenance : LTS (Long Term Support)

Statistiques

Source Stackify
Statistiques
Source GitHut

Statistiques

Premier programme
// HelloWorld.js const os = require('os') console.log('Hello World!') console.log(`Running on ${os.hostname} computer and ${os.platform} operating system.`)
[16:07] sogitec@MACPRO8:firstApp $ node --version v8.4.0 [16:08] sogitec@MACPRO8:firstApp $ node HelloWorld.js Hello World! Running on MACPRO8.local computer and darwin operating system.
HelloWorld.js
Exécution du programme
Installation
Home Page
Si vous n'y trouvez pas votre bonheur, sous Linux : binaires pour de multiples distributions
Téléchargements
Si vous souhaitez disposer de plusieurs version de node/npm, la solution est
Node Version Manager
Event Loop
et "reactor pattern"
Temps d'accès aux données
RAM : nanoseconds
DD / réseau : milliseconds
Vitesse de transfert
RAM : GB/s
Réseau : MB/s -> GB/s
I/O is slow
Qu'elles soient réseau ou disque les E/S sont dramatiquement lentes.
Blocking I/O
De plus elles sont généralement bloquantes.
Pas facile de faire des accès concurrents dans ses conditions.
Une solutions classiques des serveurs web est d'utiliser du multi thread ou du multi process.

Source : node.js Design Patterns
Multi-process c'est lourd !
C'est facile à faire (fork) mais...
La mémoire complète (code, données, tas et pile) du process est dupliquée...
Communication inter-process lentes : on passe par l'OS
C'est ce qui a donné l'idée du multi-thread
Multi-thread c'est moins lourd, mais c'est lourd quand même !
Les threads partagent la même mémoires et les mêmes ressources.
La mise au point d'un programme multi-thread est complexe.
Communication inter-thread est rapide.
Mais le CPU ne dispose pas d'autant de coeurs qu'il y a de threads...
L'orchestration des threads sur les différents coeurs du processeurs est complexe et coûteuse en ressources (commutation de contexte).
Du coup node est...
Voir ici pour plus de détails
...par nature "mono-thread"

Source : node.js Design Patterns
En fait pas tout à fait...
1 thread principal pour le code JavaScript
1 pool de threads (workers-pool basé sur libuv) pour les traitements coûteux en I/O (file, DNS, etc.) ou en CPU (crypto, zip, etc.),
Voir ici pour plus de détails
Le tout orchestré par une architecture orientée événement très efficace (Event-driven Architecture)
node.js est mono thread...

Event Loop
Event Loop... c'est pas la fête du slip non plus...
En conséquence, pour un maximum de fluidité (on dit "pour que ça scale bien"), les traitements dans un callback doivent être les plus cours possibles.
Avec node.js tous les clients (cas d'un serveur web) partagent le même thread.
La prise en compte des requêtes clientes en attente ne se fait que si le callback en cours est terminé.

Source : node.js Design Patterns
Au final node.js c'est...
V8 : moteur Javascript rapide et efficace au niveau mémoire
Des wrappers pour exposer la lib libuv de gestion des E/S non bloquantes
Une API Javascript (node-code) de haut niveau
Blocking
vs non-blocking
Blocking vs non-blocking
Voir détail ici
Blocking methods execute synchronously and non-blocking methods execute asynchronously.
Exemples d'appels potentiellement bloquants :
- I/O (file ou network) de la librairie libuv,
- certains modules natifs (BDD sqlite3 par exemple)

Blocking vs non-blocking


Appel synchrone bloquant
Appel asynchrone non-bloquant
Voir détail ici
Blocking vs non-blocking
Les appels asynchrones sont à privilégier puisqu'ils sont non bloquants. Ils rendent la main à l'EventLoop qui se charge de gérer les exécutions concurrentes.
Ce mécanisme est très différent de ce que l'on trouve dans d'autres langages où c'est plutôt l'ajout de nouveaux thread qui permet de gérer les traitements concurrents.
Remarque : la gestion des thread est très gourmande en ressource (mémoire, cpu (changement de contexte)).
Voir détail ici


Principal (et premier) gestionnaire de modules de node.js
Alternative possible : yarn
A faillit remplacer npm en proposant des performances bien supérieures en 2016, mais npm a rattrapé son retard depuis
yarn vs npm ici
Rechercher un module

p : popularité
q : qualité
m : maintenance
Critères de choix
Nb téléchargement, évolution dans le temps
Test unitaire,
couverture de code
Anomalies,
délais de correction
Autres sites de recherche
Si vous hésitez entre plusieurs modules similaires
Etat des lieux 2017 (statistiques d'utilisatrion)
(Exemple pour le back-end)
Etat des lieux 2018
Commandes de base
npm init
- initialisation d'un nouveau projet
- création d'un fichier package.json
npm install --save <module>
npm i --save <module>
- télécharge et installe un module dans le répertoire node_modules
- ajout d'une dépendance dans package.json
- Attention sans --save la dépendance n'est pas ajoutée dans package.json -> le projet devient difficile à partager
- --save-dev au lieu de --save : si le module n'est utile qu'en mode développement (exemple : outillages tests unitaires)
Documentation officielle ici
Installer un module globallement
npm i -g <module>
npm install -g <module>
- télécharge et installe un module globalement pour le compte utilisateur
- le module devient accessible de partout
- pour les modules transversaux et les outils (nodemon)
A éviter dans la mesure du possible car ses modules ne sont pas associés aux projets.
Intéressant spour les modules très lourd (gain de place)
Désinstaller un module
npm uninstall --save <module>
npm uninstall --save-dev <module>
- Supprime le module du répertoire node_module ainsi que du package.json
npm uninstall -g <module>
- Supprime le module globalement
Réinitialiser un projet
npm ci
- supprime le répertoire node_modules et retélécharge l'ensemble des modules
- Se base sur les dépendances du package.json
- Permet de s'assurer que toutes les dépendances sont bien définies dans package.json
npm i
- se contente de télécharger et d'installer les packages manquants
Mettre à jour npm avec npm !
npm i npm@latest -g
Anatomie de package.json
Structure
{
"name": "firstproject",
"version": "0.0.1",
"description": "My first project",
"main": "server.js",
"scripts": {
"start": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "sm",
"license": "ISC",
"dependencies": {
"express": "^4.16.3",
"winston": "^3.1.0"
},
"devDependencies": {
"mocka": "0.0.1"
}
}

npm i --save express npm i --save winston
npm i --save-dev mocka
Plus de détails ici
Versioning

Quelques modules du noyau
console
console.log('hello %s', 'world')
console.error(new Error('Whoops, something bad happened')) console.assert(false, 'Whoops %s work', 'didn\'t')
console.count('abc')
abc: 1
console.time('100-elements') for (let i = 0; i < 100; i++) {} console.timeEnd('100-elements') // prints 100-elements: 225.438ms
console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }]) // ┌─────────┬─────┬─────┐ // │ (index) │ a │ b │ // ├─────────┼─────┼─────┤ // │ 0 │ 1 │ 'Y' │ // │ 1 │ 'Z' │ 2 │ // └─────────┴─────┴─────┘
util
const util = require('util')
util.format('hello %s', 'world') hello world util.log('sortie avec timestamp') 28 Oct 17:55:52 - sortie avec timestamp console.log( util.inspect({ value: 'foo', items: [ 'a', 'b' ], fonc: hello }) ) { value: 'foo', items: [ 'a', 'b' ], fonc: [Function: hello] }
process
// Accès aux variables d'environnement console.log(process.env.PATH)
// Version de Node et libs sous-jacentes
console.log(process.version, process.versions)
// OS d'exécution console.log(process.platform, process.arch) // Paramètres de l'appli console.log(process.argv)
// Events sur le process process.on('exit', (code) => { console.log(`About to exit with code: ${code}`) }) process.on('uncaughtException', (err) => console.error('Kesessa ?', err))
os
const os = require('os') // Caractère de fin de ligne sur la plateforme courante console.log(os.EOL) // Info sur l'OS, idem process.arch et process.platform console.log(os.platform(), os.release(), os.arch(), os.type())
// Détails processeurs et interfaces réseau,
// similaires à cat /proc/cpuinfo et ifconfig
console.log(os.cpus(), os.networkInterfaces()) // Répertoire home de l'utilisateur courant console.log(os.homedir()) // Utilisation mémoire (niveau OS, ne
// pas confondre avec process.memoryUsage() console.log(os.totalmem(), os.freemem())
fs
const fs = require('fs') // Lecture d'un fichier (par défaut, méthode asynchrone) fs.open('/open/some/file.txt', 'r', (err, fd) => {
if (err) throw err fs.close(fd, (err) => { if (err) throw err }) }) // Observer les modifications dans un répertoire fs.watch('./tmp', { encoding: 'buffer' }, (eventType, filename) => { if (filename) { console.log(filename) } }) // lecture de répertoire et informations sur un fichier fs.readdir('.', (err, files) => { // ici il faudrait tester err files.forEach((file) => { fs.stat(path.join(__dirname, file), (err, stat) => { // idem console.log('%s : %d octets, dernière modif. %j',file,stat.size,stat.mtime) }) }) })
crypto
Ensemble de wrapper autour de OpenSSL
Certificate : Génération et manipulation de certificats
Sign, Verify : signature et vérification de signature
Hash : génération de hashcode
Cipher, Decipher : cryptage, décryptage
Crypto : Point d'entrée du module, génération de clefs asymétriques, etc.
et de nombreux autres
Assert : Test d'assertions
Child Process : gestion des multi-process
Cluster : multi-thread
Errors : Gestion des erreurs (try, catch, finally)
Events : Gestion des événements (détaillé plus loin)
http, https : gestion de la pile Http(s)
Stream : Gestion des flux (détaillé plus loin)
TTY : Interaction avec la console
UDP/Datagram : réseau bas niveau, sockets
URL : Manipulation d'URL
Zlib : compactage / décompactage de fichiers
Best of - modules
Extensions du langage
Underscore et Lo-Dash
Underscore
Outillage sur les tableaux, map, etc.
each, map, reduce, filter, etc.
Intéressant si on ne dispose pas de ES6 (voir ici pour plus de détail).
lo-Dash
Gestion des dates
moment.js, date-fsn
moment.js
Outil de manipulation de dates.
A peut prêt toutes les opérations que l'on peut imaginer faire sur des dates : parsing, validation, comparaison, formattage.
date-fns
Outil de manipulation de dates.
Plus de 140 opérations sur les dates.
Peut être un peu plus facile d'utilisation que moment, mais c'est question de goût...
Il est à moment.js ce que lo-dash est à underscore... une alternative qui a dépassée sur certains points l'original...
Logging avancé
winston et bunyan
Winston
Gestion des log dans l'application (console, fichier, etc.)
const logger = winston.createLogger({ transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'combined.log' }) ] }) ...
logger.log({level:'info',message:'Premier log de type info'})
// Raccourcis
logger.log('Log de type info (par défaut)')
logger.error('Une erreur')
logger.warn('Un warning')
logger.debug('Une trace de debug')
Bunyan
Similaire à Winston mais propose par défaut un log au format JSON pour lequel il propose un client (CLI) pour leur visualisation.
const bunyan = require('bunyan') const log = bunyan.createLogger({ name: 'myapp',
streams: [ { stream: process.stdout }, { path: 'logs/dev.log' } ]
}) log.info('hi') log.warn({lang: 'fr'}, 'au revoir')
Contrôle de type
tcomb
tcomb
Contrôle de type sur les objets (sans aller jusqu'à faire du TypeScript).
Permet d'imposer une structure à un objet ou un type à un paramètre de fonction.
Remonte une exception si la structure n'est pas respectée.
Permet de réduire les effets de bord en particulier lorsque l'on a pas la main sur la source des données.
import t from 'tcomb' const GPS = t.struct({ lat: t.Number, lon: t.Number, }, {strict: true}) const Bordeaux = GPS({ lat: 44.8638, lon: -0.6561 }) // OK const Crest = GPS({ lat: 44.7311, long: 4.9861 }) // erreur : long const Toulouse = GPS({ lat: 43.6008, lon: 'r0s3' } // erreur : 'r0s3'
import t from 'tcomb'
function sum(a, b) {
t.Number(a)
t.Number(b)
return a + b
}
sum(1, 's') // throws '[tcomb] Invalid value "s"
// supplied to Number'
Contrôle de code
ESLint

ESLint
Outil de vérification syntaxique pour ECMAScript.
Bonnes pratiques, etc.
npm install --save-dev eslint
// Création du fichier de config (à faire une fois)
./node_modules/.bin/eslint --init
// Lancer eslint sur un fichier js
./node_modules/.bin/eslint myFile.js
S'utilise en ligne de commande ou plus simplement directement intégré à l'IDE (VSCode par exemple via le plugin ESLint de Dirk Baeumer).

HTTP
express, helmet
Permet de réaliser un serveur HTTP
Décrit en détail dans le module Express du cours.
var express = require('express') var app = express() app.get('/', function (req, res) { res.send('Hello World') }) app.listen(3000)

Helmet
Aglomère un ensemble de modules qui œuvrent pour la sécurisation des serveurs HTTP
const express = require('express') const helmet = require('helmet') const app = express() app.use(helmet())
...
ModuleDefault?
contentSecurityPolicy for setting Content Security Policy | |
crossdomain for handling Adobe products' crossdomain requests | |
dnsPrefetchControl controls browser DNS prefetching | ✓ |
expectCt for handling Certificate Transparency | |
featurePolicy to limit your site's features | |
frameguard to prevent clickjacking | ✓ |
hidePoweredBy to remove the X-Powered-By header | ✓ |
hpkp for HTTP Public Key Pinning | |
hsts for HTTP Strict Transport Security | ✓ |
ieNoOpen sets X-Download-Options for IE8+ | ✓ |
noCache to disable client-side caching | |
noSniff to keep clients from sniffing the MIME type | ✓ |
referrerPolicy to hide the Referer header | |
xssFilter adds some small XSS protections | ✓ |

Anatomie des modules
Définition
Module : boîte dans laquelle on pourra embarquer une collection de méthodes ou de variables liées entre elles et qui formeront un ensemble cohérent (Framework).
Le module détermine l’accessibilité de chaque membre.
Pour le code qui la consomme, il s’agit donc d’une boîte noire.
Common.js
Syntaxe historique (avant ES6) popularisée par node.js pour créer des modules
plus de détails ici http://wiki.commonjs.org/wiki/Modules/1.1
// HelloWorld.js const os = require('os') exports.hello = function(){
console.log('Hello World!') console.log( `Running on ${os.hostname} computer and ${os.platform} operating system.` )
}
// index.js const hello = require('./HelloWorld') hello.hello()
[16:40] sogitec@MACPRO8:firstApp $ node index.js Hello World! Running on MACPRO8.local computer and darwin operating system.
Création d'un module avec ES6
// lib.js
const PI = 3.1415926;
function sum(...args) {
log('sum', args)
return args.reduce((num, tot) => tot + num);
}
function mult(...args) {
log('mult', args);
return args.reduce((num, tot) => tot * num);
}
// private function
function log(...msg) {
console.log(...msg)
}
export { PI, sum, mult }
export default sum
Plus de détails ici
Utilisation d'un module avec ES6
// Import complet d'un module
import * as lib from './lib.js'
console.log( lib.mult(1,2,3,4) ) // 24
// Import par défaut
import maFonction from './lib.js'
console.log( maFonction(1,2,3,4) ) // 10 (maFonction = sum)
// Renommage
import { PI as monPi} from './lib.js'
// Import d'une sous partie d'un module
import { sum } from './lib.js'
console.log( sum(1,2,3,4) ) // 10
Plus de détails ici
Ordre de recherche des modules
( require ou import)
PAS d'extension dans le require ou l'import
... = require('monModule') import ... as 'monModule'
- Chemin relatif vers fichier 'monModule' avec extension .js, .json ou .node (addon binaire)
ou
- Dossier 'monModule' avec…
- Fichier package.json doté d’une clé main
- Fichier index.js (fichier par défaut)
- Fichier index.node (addon binaire node par défaut)
Ordre de recherche des modules (suite)
( require ou import)
l'algorithme précis est décrit ici https://nodejs.org/api/modules.html#modules_all_together
- Recherche dans les modules natifs (fs, os, etc.)
- Si le chemin commence par '/', '../' ou './' alors recherche du chemin dans le 'file system' (local relatif (../ et ./) ou local absolu (/)
- Sinon Recherche dans le répertoire node_modules courant puis dans les éventuels répertoires node_modules parents
- Si pas trouvé, recherche dans le répertoire node_modules global (voir npm -g)
Plus de détails ici
Caching
Les modules sont mis en cache au premier chargement.
Si un module est importé plusieurs fois dans un projet, c'est le même objet qui est partagé !
Cas particulier des système non case sensitive (windows) :
require('monmodule') require('MonModule')
correspondent au même fichier mais retourneront deux objets différents
Bonne pratique : Toujours mettre le nom des modules en lowercase
Caching (exemple)
// lib.js let compteur = 0 const increase = () => { compteur++ } const current = () => { return compteur } export default { current, increase }
//index.js import lib1 from './lib.js' import lib2 from './lib.js' console.log(`${lib1.current()}, ${lib2.current()}`) lib1.increase() console.log(`${lib1.current()}, ${lib2.current()}`) lib2.increase() console.log(`${lib1.current()}, ${lib2.current()}`)
Sortie attendue :
0, 0 1, 1 2, 2
Configuration
avec
git init
Initialisation du repository GIT
Initialisation nouveau projet
npm init
Création d'un fichier .gitignore avec https://www.gitignore.io/
Creation projet npm, GIT et .gitignore

Commiter .gitignore lors du premier commit
git add . git commit -m "Initialisation du projet"
Creation arborescence des fichiers

Les sources sous /src
Les sources compilées par babel seront sous /build
npm install --save-dev @babel/core @babel/cli @babel/preset-env npm install --save @babel/polyfill
Installation modules babel
Configuration babel pour node.js
// babel.config.js const presets = [ ['@babel/env', { targets: { node: 'current'}, useBuiltIns: 'usage'} ]] module.exports = { presets }
Configuration Babel 1/2
Configuration package.json pour compiler et lancer le projet
{"name": "monappli", "version": "1.0.0", "description": "ma description", "main": "src/index.js", "scripts": { "build": "npx babel src --out-dir build", "start": "npm run build && node build/index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "@babel/cli": "^7.1.2", "@babel/core": "^7.1.2", "@babel/preset-env": "^7.1.0" }, "dependencies": { "@babel/polyfill": "^7.0.0" }}
Configuration Babel 2/2
Configuration package.json pour compiler et lancer le projet
npm start
Utilisation
npm run build && node build/index.js
npx babel src --out-dir build
Execution de build/index.js
"Compilation" des fichiers de src/ dans le répertoire build/
Outillage
nodemon
npm install -g nodemon

Relance automatiquement l'application lorsqu'une modification de source est réalisée
En phase de développement
// S'utilise à la place de node pour lancer une application nodemon server.js // au lieu de "node server.js"
npm-run-all
npm install npm-run-all --save-dev
Permet de lancer des scripts en parallèle ou en séquence en gérant les aspects cross plateforme (le & pour lancer des process en parallèle ne fonctionne pas sous windows).
{ "...": "...",
"scripts": { "build": "npm-run-all -p 'build:*' -s start",
"build:css": "sass ...",
"build:js": "browserify ...",
"start": "node server.js" }
}
// puis
npm run scripts
S'utilise en ligne de commande ou plus généralement avec npm en configurant package.json
husky
npm install husky --save-dev
S'intègre avec GIT et permet de lancer des traitements/contrôles avant de réaliser des "git push", des "git commit" ou des "git pull" (liste complète des hooks ici)
{ "...": "...",
"scripts": {
"precommit": "lint-staged",
"post-merge": "npm install" } }
// puis
git commit -m "...
S'utilise en configurant package.json
debug
npm install debug --save-dev
Permet de disposer de traces limitées à une sous-partie applicative
import { debug } from "debug" const debugA = debug("module:a") const debugB = debug("module:b") const debugHTTP = debug("http") debugA("Trace from moduleA") debugB("Trace from moduleA") debugHTTP("Trace from HTTP ...")
$ DEBUG=module:b node build/index.js module:b Trace from moduleA +0ms
$ DEBUG=module:* node build/index.js module:a Trace from moduleA +0ms module:b Trace from moduleA +0ms
$ DEBUG=module:*,http node build/index.js module:a Trace from moduleA +0ms module:b Trace from moduleA +0ms http Trace from HTTP ... +0ms
depcheck
npm install -g depcheck
Permet d'analyser les modules du projet pour détecter ceux qui ne servent à rien et ceux qui ne sont pas déclarés dans package.json

Events
Principes
Communication via des fonctions d'écoute et d'émission de messages
Les événements 'error' sont spéciaux : faute de traitement, ils agiront comme une exception (stack trace sur stderr puis arrêt du processus).
Dans Node, la majorité des objets noyau émettent des événements : flux, requête/réponse, watchers, erreurs…
L’API est toujours la même, centralisée dans une classe EventEmitter
Principes
//index.js import EventEmitter from 'events' const emitter = new EventEmitter() emitter.on('alloffame', (date, name, dcd) => { console.log(`${name} born in ${date.getFullYear()} ${dcd ? ', DEAD' : ''}`) }) emitter.once('alloffame', (date, name, bissextil) => { console.log(`First All of Fame received for ${name}`) }) emitter.emit('alloffame', new Date('1930-05-11'), 'Edsger Dijkstra', true) emitter.emit('alloffame', new Date('1955-06-08'), 'Tim Berners-Lee')
Edsger Dijkstra born in 1930 , DEAD First All of Fame received for Edsger Dijkstra Tim Berners-Lee born in 1955
Voir ici pour des compléments
Streams
Intêret
Permet de manipuler des volumes de données énormes sans utiliser beaucoup de ressources.
Le coeur I/O de node !
Buffering vs streaming
Buffering : on attend que le buffer soit plein avant d'envoyer le résultat au destinataire

Source : node.js design patterns
Buffering vs streaming
Source : node.js design patterns
const fs = require("fs") const zlib = require("zlib") const file = process.argv[2] fs.readFile(file, (err, buffer) => { zlib.gzip(buffer, (err, buffer) => { fs.writeFile(file + '.gz', buffer, err => { console.log('File successfully compressed') }) }) }
Exemple buffering : compression d'un fichier
Buffering vs streaming
Streaming : le flux est envoyé au destinataire au fur et à mesure qu'il est lu.
Source : node.js design patterns

Avantages du streaming
Le streaming permet de réaliser des tâches qu'il n'est pas possible de faire avec le buffering comme lire un fichier de plusieurs Go car nécessiterait de mettre le contenu complet en mémoire...
De plus, le moteur Javascript V8 limite la taille des buffers à 0x3FFFFFFF octets soit un peu moins de 1Go.
Si le fichier est plus gros on part en 'out of memory' !
Moins gourmand en mémoire
Le destinataire du flux peut commencer à travailler dessus alors que l’émetteur du flux n'a pas encore terminé son émission.
Plus rapide
Avantages du streaming
Streaming
Source : node.js design patterns
const fs = require('fs') const zlib = require('zlib') const file = process.argv[2] fs.createReadStream(file) .pipe(zlib.createGzip()) .pipe(fs.createWriteStream(file + '.gz') .on('finish', () => console.log('File successfully compressed') )
Exemple streaming : compression d'un fichier (à comparer avec la version buffering, 2 planches plus haut)
Anatomie des streams
Un stream en node.js implémente l'une des 4 interfaces suivantes :
stream.Readable (2 modes : flowing et non-flowing)
stream.Writable
stream.Duplex
stream.Transform
Un stream est aussi une instance de EventEmitter pour la production d'événements :
- end
- error
stream.Readable
const {createReadStream} = require('fs')
createReadStream(__filename, {highWaterMark: 100}) (1)
.on('end', () => console.log('Lecture terminée')) (2)
.on('data', (data) => { (3)
console.log('%d octets reçus', data.length)
})
.on('error', (err) => console.error(err)) (4)
Lecture de flux : flowing mode (ancienne méthode)
Voir doc createReadStream pour plus de détails
(1) taille du buffer : 100 octets (par défaut, 64ko)
(2) Appelé à la fin de la lecture
(3) Appelé pour chaque bloc (de 100 octets ici)
(3) Appelé si une erreur survient
Voir ici pour l'exemple d'origine
stream.Readable
const {createReadStream} = require('fs')
const stream = createReadStream(__filename, {highWaterMark: 100}) (1)
.on('end', () => console.log('Lecture terminée')) (2)
.on('readable', () => { (3)
while (null !== (data=stream.read())) {
console.log('%d octets reçus', data.length)
})
})
.on('error', (err) => console.error(err)) (4)
Lecture de flux : non-flowing mode (nouvelle méthode)
(1) taille du buffer : 100 octets (par défaut, 64ko)
(2) Appelé à la fin de la lecture
(3) Appelé dès que le bloc est disponible
(3) Appelé si une erreur survient
Un peu plus de détails ici
stream.Writeable
const {createWriteStream, readFile} = require('fs')
const {join} = require('path')
const dest = join(__dirname, 'debug.txt')
const stream = createWriteStream(dest) (1)
stream.on('finish', () => {
readFile(dest, (error, data) => { (4)
console.log(String(data))
})
})
stream.on('error', (err) => console.error(err)) (5)
stream.write('Hell') (2)
stream.write('o Worl')
stream.end('d!') (3)
Ecriture de flux
Voir doc createReadStream pour plus de détails
(1) Création d'un flux vers le fichier debug.txt
(2) Ecriture dans le flux (encodage par défaut : utf8)
(3) end indique qu'il que se sont les dernières données
(4) l'event 'finish' est appelé suite au "end"
(5) Si une erreur survient
Voir ici pour l'exemple d'origine
stream.Duplex
flux bidirectionnel
Hérite à la fois de stream.Readable et de stream.Writable
Utile pour les objets qui peuvent être à la fois émettrice et réceptrice.
Exemple : socket réseau
Plus de détails ici
stream.Transform
flux de transformation
Cas particulier de stream.Duplex
Comme lui, hérite à la fois de stream.Readable et de stream.Writable
Permet de réaliser des filtres de transformation sur des flux
Exemples : zlib ou crypto
Plus de détails ici
pipe
const {createReadStream, createWriteStream} = require('fs')
const {join} = require('path')
const filename_copy = join(__dirname, 'copie.js')
const source = createReadStream(__filename) (1)
const dest = createWriteStream(filename_copy) (2)
source.pipe(dest) (3)
.on('finish', () => console.log('Copie terminée !')) (4)
source.pipe(process.stdout) (5)
Les données lues depuis une source (Readable) sont redirigées vers une destination (Writeable) à l’aide de la fonction pipe()
(1) Création d'un flux de lecture
(2) Création d'un flux d'écriture
(3) Redirection du flux de lecture vers le flux d'écriture
(4) La redirection retourne le flux d’écriture, que l’on écoute pour savoir quand il a terminé d’écrire sur le disque
(5) Le flux de la source peut être redirigé vers plusieurs destinations
Voir ici pour l'exemple d'origine
buffer
Les flux utilisent par défaut des objets buffers.
Leur taille est définie à la création et est non modifiable.
Ils se comportent un peu comme un tableau d'integer.
Itérable avec for..of
-
'ascii' - For 7-bit ASCII data only. This encoding is fast and will strip the high bit if set.
-
'utf8' - Multibyte encoded Unicode characters. Many web pages and other document formats use UTF-8.
-
'utf16le' - 2 or 4 bytes, little-endian encoded Unicode characters. Surrogate pairs (U+10000 to U+10FFFF) are supported.
-
'ucs2' - Alias of 'utf16le'.
-
'base64' - Base64 encoding. When creating a Buffer from a string, this encoding will also correctly accept "URL and Filename Safe Alphabet" as specified in RFC4648, Section 5.
-
'latin1' - A way of encoding the Buffer into a one-byte encoded string (as defined by the IANA in RFC1345, page 63, to be the Latin-1 supplement block and C0/C1 control codes).
-
'binary' - Alias for 'latin1'.
-
'hex' - Encode each byte as two hexadecimal characters.
Encodage possibles (c.f. doc officielle)
Voir ici pour plus de détails

Ce qui fait la force de node.js c'est son moteur de pool threads
EventLoop permet à node.js d'exécuter des opérations I/O de manière non bloquante bien qu'il soir mono-thread !
Dans node.js les opérations I/O, et javascript se partagent un thread unique.
Pour de bonnes performances, node.js implique une programmation asynchrone!
Un projet node.js est défini par la présence d'un fichier package.json
bibliographie
node.js
Sites
https://delicious-insights.com/fr/articles/libs-node-js/
26 modules nodes que j'utilises tout le temps • Delicious Insights
https://oncletom.io/node.js/appendix-a/index.html
Node.js • Apprendre par la pratique • Appendix A Sélection de modules npm • Thomas Parisot


https://www.tutorialspoint.com/nodejs/index.htm
tutorials points • Node.js • tutorialspoint
https://nodejs.org/dist/latest-v10.x/docs/api/
Node.js v10.15.0 Documentation • nodejs.org
https://github.com/substack/stream-handbook
Stream Handbook • substack
https://nodeschool.io/fr-fr/
Ateliers open sources Nodeschool • Nodeschool

https://oncletom.io/node.js/chapter-04/index.html
Node.js • Apprendre par la pratique • Chapitre 4 sur node.js • Thomas Parisot
node.js
Livres en ligne gratuits

https://github.com/substack/stream-handbook
stream handbook • Projet Github
https://github.com/workshopper/stream-adventure
stream adventure • Projet Github
https://github.com/azat-co/practicalnode
Practical node.js, 2nd edition • 2018 • Apress
Livres (bible)
Node.js Design Patterns
2nd edition • 2016 •Mario Casciaro - Luciano Mammino • PACKT Publishing
npm
Vidéo
Sites
https://docs.npmjs.com/
npm documentation • npmjs.com
KEYNOTE
node.js interactive north America • Ashley Williams
4- node.js
By steph michel
4- node.js
Planches de formation sur node.js
- 1,197