child_process
.
1. Fondamentaux des Child Processes
Node.js utilise le modèle d’I/O non bloquant mais parfois on doit :
– Exécuter des binaires système (ex: ffmpeg)
– Paralléliser des tâches CPU-intensive
– Créer des architectures microservices
Architecture Typique :
Processus Parent (Node.js)
|
├── Child Process 1 (ls -la)
├── Child Process 2 (Python script)
└── Child Process 3 (Fork Node.js)
2. Méthodes Détaillées avec Exemples Avancés
2.1 spawn()
– La méthode bas niveau
Contrôle fin des flux d’entrée/sortie :
const { spawn } = require('child_process');
// Exécution avec options avancées
const child = spawn('grep', ['ERROR'], {
stdio: [
'pipe', // stdin
'pipe', // stdout
process.stderr // rediriger stderr vers le parent
],
detached: true,
cwd: '/var/log'
});
// Écriture dans stdin
child.stdin.write('Log line 1\n');
child.stdin.write('Log line 2 ERROR\n');
child.stdin.end(); // Fermeture nécessaire
// Lecture ligne par ligne
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
console.log('Résultat filtré:', chunk.trim());
});
// Gestion des erreurs étendue
child.on('error', (err) => {
console.error('Erreur du processus:', err);
});
child.on('exit', (code, signal) => {
if (code === 0) {
console.log('Succès');
} else {
console.log(`Échec avec code ${code}`);
}
});
2.2 exec()
– Pour l’interaction avec le shell
Avec gestion de timeout et buffer limit :
const { exec } = require('child_process');
exec(
'find / -name "*.log"',
{
timeout: 5000, // Annule après 5s
maxBuffer: 1024 * 1024, // 1MB max
encoding: 'utf8'
},
(error, stdout, stderr) => {
if (error) {
if (error.signal === 'SIGTERM') {
console.log('Processus annulé par timeout');
}
return;
}
console.log('Fichiers logs:', stdout.split('\n'));
}
);
2.3 execFile()
– Exécution sécurisée
Évite l’injection de commandes :
const { execFile } = require('child_process');
// Sécurisé : les arguments sont passés dans un tableau
execFile(
'convert',
['input.jpg', '-resize', '50%', 'output.jpg'],
(error, stdout, stderr) => {
// Gestion des erreurs...
}
);
2.4 fork()
– Communication avancée
Messagerie bidirectionnelle avec sérialisation JSON :
// parent.js
const { fork } = require('child_process');
const child = fork('./compute.js', { silent: true });
child.on('message', (message) => {
console.log('Résultat:', message.result);
});
child.send({
task: 'fibonacci',
data: { n: 40 }
});
// compute.js
process.on('message', (message) => {
if (message.task === 'fibonacci') {
const result = fib(message.data.n);
process.send({ result });
}
});
function fib(n) { /* ... */ }
3. Quand utiliser spawn()
, exec()
ou fork()
?
3.1 child_process.spawn()
: Le spécialiste des flux
Utilisation idéale quand :
– 🗂 Traiter des données en streaming (vidéo, logs, gros fichiers)
– ⏳ Gérer des processus de longue durée
– 🎮 Contrôler manuellement les entrées/sorties
Exemple typique :
// Conversion vidéo avec flux progressif
const ffmpeg = spawn('ffmpeg', ['-i', 'input.mp4', 'output.avi']);
ffmpeg.stderr.on('data', (data) => {
// Affichage progression en temps réel
console.log('Progression:', data.toString());
});
3.2 child_process.exec()
: Le rapide pour commandes shell
Utilisation idéale quand :
– ⚡ Exécuter des commandes courtes
– 📋 Utiliser des fonctionnalités shell (pipes, redirections)
– 🔢 Récupérer un résultat unique
Exemple typique :
// Comptage rapide de fichiers
exec('ls -l | wc -l', (err, stdout) => {
console.log(`Nombre d'éléments: ${stdout}`);
});
3.3 child_process.fork()
: La communication Node.js
Utilisation idéale quand :
– 🧠 Paralléliser des calculs lourds
– 📡 Communiquer entre processus
– 🔄 Créer des architectures en cluster
Exemple typique :
// parent.js
const worker = fork('image-processor.js');
// Envoi de tâche de traitement d'image
worker.send({ image: buffer, options: { width: 800 } });
// Réception résultat
worker.on('message', (processedImage) => {
// Affichage résultat
});
3.4 Comparaison Technique
Méthode | Détails |
---|---|
spawn |
Modèle d’exécution: Flux événementiel Surcoût: Faible Transfert de données: Chunks bruts Cas optimal: Flux vidéo Shell: Non* Performance: Haute Streaming: Oui Sécurité: Élevée Use Case: Flux volumineux |
exec |
Modèle d’exécution: Buffer unique Surcoût: Élevé (shell) Transfert de données: String Cas optimal: Commande shell simple Shell: Oui Performance: Moyenne Streaming: Non Sécurité: Faible Use Case: Commandes courtes |
fork |
Modèle d’exécution: IPC Node.js Surcoût: Moyen Transfert de données: Objets sérialisés Cas optimal: Calcul matriciel Shell: Non Performance: Variable Streaming: Oui Sécurité: Élevée Use Case: Cluster Node.js |
* Peut utiliser un shell avec l’option { shell: true }
4. Gestion des Erreurs Avancée
Codes de Sortie Unix
child.on('exit', (code, signal) => {
if (code === 0) {
console.log('Succès');
} else if (code === 1) {
console.error('Erreur générale');
} else if (code === 2) {
console.error('Abus de shell');
}
if (signal === 'SIGKILL') {
console.log('Processus tué');
}
});
Pattern de Gestion Centralisée
function runCommand(command, args) {
const child = spawn(command, args);
const promise = new Promise((resolve, reject) => {
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => stdout += data);
child.stderr.on('data', (data) => stderr += data);
child.on('close', (code) => {
if (code === 0) {
resolve(stdout);
} else {
const error = new Error(`Command failed: ${stderr}`);
error.code = code;
reject(error);
}
});
});
promise.child = child;
return promise;
}
5. Sécurité Renforcée
exec
avec des entrées utilisateur non nettoyées !
// Dangereux !
exec(`rm ${userInput}`);
// Sécurisé
execFile('rm', [userInput], {
shell: false // Désactive l'interprétation shell
});
Bonnes Pratiques de Sécurité :
– Utiliser execFile
par défaut
– Valider et sanitizer toutes les entrées
– Limiter les permissions avec uid/gid
– Utiliser timeout
et maxBuffer
6. Techniques Avancées JSNAD
Redirection de Flux Complexe
// Rediriger stdout vers un fichier
const fs = require('fs');
const { spawn } = require('child_process');
const out = fs.createWriteStream('output.log');
const child = spawn('program', [], {
stdio: ['ignore', out, 'inherit']
});
Cluster de Processes
const cluster = require('cluster');
const http = require('http');
if (cluster.isMaster) {
// Fork 4 workers
for (let i = 0; i < 4; i++) {
cluster.fork();
}
} else {
http.createServer((req, res) => {
res.end('Hello World\n');
}).listen(8000);
}
7. Debugging des Child Processes
Options utiles :
// Activer le mode debug
NODE_DEBUG=child_process node app.js
// Obtenir des informations système
const psTree = require('ps-tree');
psTree(process.pid, (err, children) => {
console.log('Processus enfants:', children);
});
8. Communication entre parent et enfant
En Node.js, la communication entre un processus parent et un processus enfant se fait via des pipes (flux) en utilisant child_process.fork()
ou d’autres méthodes du module child_process
.
1. Création d’un processus enfant avec fork()
fork()
est une méthode spécifique pour exécuter un script Node.js en tant que processus enfant et permet une communication bidirectionnelle entre le parent et l’enfant via process.send()
et l’événement message
.
Exemple 1 : Communication Parent → Enfant → Parent
Le parent envoie un message à l’enfant, l’enfant le traite et répond.
const { fork } = require("child_process");
// Création du processus enfant
const child = fork("./child.js");
// Envoi d'un message à l'enfant
child.send({ action: "greet", name: "Alice" });
// Réception de la réponse de l'enfant
child.on("message", (message) => {
console.log("Message du processus enfant :", message);
});
// Écoute des messages envoyés par le parent
process.on("message", (message) => {
if (message.action === "greet") {
// Réponse au parent
process.send({ response: `Bonjour, ${message.name} !` });
}
});
Résultat attendu dans la console du parent :
Message du processus enfant : { response: 'Bonjour, Alice !' }
2. Utilisation des flux (spawn et exec)
Avec spawn()
ou exec()
, la communication se fait via stdin
, stdout
, et stderr
.
Exemple 2 : Lire la sortie d’un script enfant
Ici, le parent lance un script enfant et lit son output.
const { spawn } = require("child_process");
const child = spawn("node", ["child.js"]);
child.stdout.on("data", (data) => {
console.log(`Message du processus enfant : ${data}`);
});
child.stderr.on("data", (data) => {
console.error(`Erreur du processus enfant : ${data}`);
});
console.log("Bonjour depuis l'enfant !");
Résultat attendu dans la console du parent :
Message du processus enfant : Bonjour depuis l'enfant !
3. Interaction plus complexe avec stdin
L’enfant peut recevoir des données du parent via stdin
et répondre via stdout
.
Exemple 3 : Envoyer une entrée et recevoir une sortie
const { spawn } = require("child_process");
const child = spawn("node", ["child.js"]);
// Envoi d'une donnée à l'enfant via stdin
child.stdin.write("42\n");
child.stdout.on("data", (data) => {
console.log(`Réponse de l'enfant : ${data}`);
});
process.stdin.on("data", (data) => {
const number = parseInt(data.toString().trim(), 10);
console.log(`Le double de ${number} est ${number * 2}`);
});
Résultat attendu dans la console du parent :
Réponse de l'enfant : Le double de 42 est 84
Résumé
fork()
: Utilisé pour exécuter un script Node.js et communiquer viaprocess.send()
.spawn()
: Utilisé pour exécuter un processus et communiquer viastdin
,stdout
, etstderr
.exec()
: Semblable àspawn()
, mais retourne un buffer complet plutôt qu’un flux.
Chaque méthode est utile selon les besoins, fork()
étant le plus adapté pour une communication bidirectionnelle entre un parent et un script Node.js.
8. Préparation JSNAD : Checklist
– ✓ Savoir choisir entre spawn
/exec
/fork
– ✓ Maîtriser les options stdio
– ✓ Comprendre les codes de sortie
– ✓ Implémenter un système de logging inter-processus
– ✓ Savoir sécuriser les appels système
Conclusion
La maîtrise des child processes est essentielle pour créer des applications Node.js professionnelles. En combinant les bonnes pratiques de sécurité, les patterns de gestion d’erreur et les techniques avancées présentées ici, vous serez parfaitement préparé pour la certification JSNAD et les projets réels.