Ajouter l’export protégé en streaming
Objectif : Servir un export volumineux authentifié sans accumuler les données en mémoire.
Un export volumineux peut immobiliser des ressources et exposer des données sensibles. Vous allez le réserver aux utilisateurs authentifiés, appliquer la limite prévue pour cette route, puis transférer les lignes au fur et à mesure vers la réponse HTTP.
Les leçons précédentes ont établi le pipeline de streams, l’authentification et la limitation. Ici, vous les assemblez : l’authentification doit passer avant la consommation du quota, et le quota avant la création du flux, qui peut déclencher des lectures en base.
Composer la route d’export
La route reçoit ses dépendances au lieu de les construire. Elle attend trois interfaces déjà fournies par l’assemblage de l’application : un middleware d’authentification, un middleware de limitation des exports et une fonction qui crée le flux CSV. Pour raccorder votre service existant, faites correspondre ces interfaces à ses signatures réelles ; ne créez pas un second service d’export.
const express = require('express');
const { pipeline } = require('node:stream/promises');
function creerRouteCommandes({
authentification,
limitationExport,
creerFluxExport,
journal
}) {
if (!authentification || !limitationExport || !creerFluxExport || !journal) {
throw new TypeError('Les dépendances de la route commandes sont obligatoires.');
}
const routeur = express.Router();
routeur.get(
'/commandes/export.csv',
authentification,
limitationExport,
async (requete, reponse) => {
const controleur = new AbortController();
let clientDeconnecte = false;
const interrompreSiNecessaire = () => {
if (!reponse.writableFinished) {
clientDeconnecte = true;
controleur.abort();
}
};
reponse.once('close', interrompreSiNecessaire);
try {
reponse.status(200);
reponse.setHeader('Content-Type', 'text/csv; charset=utf-8');
reponse.setHeader(
'Content-Disposition',
'attachment; filename="commandes.csv"'
);
const flux = await creerFluxExport({
utilisateurId: requete.utilisateur.id,
signal: controleur.signal,
identifiantCorrelation: requete.identifiantCorrelation
});
await pipeline(flux, reponse, { signal: controleur.signal });
} catch (erreur) {
if (clientDeconnecte || controleur.signal.aborted) {
journal.info({
evenement: 'export_interrompu',
identifiantCorrelation: requete.identifiantCorrelation
});
return;
}
journal.error({
evenement: 'echec_export',
identifiantCorrelation: requete.identifiantCorrelation,
erreur: erreur.message
});
if (!reponse.headersSent) {
reponse.status(500).json({ erreur: 'Export indisponible.' });
} else {
reponse.destroy(erreur);
}
} finally {
reponse.removeListener('close', interrompreSiNecessaire);
}
}
);
return routeur;
}
module.exports = { creerRouteCommandes };Le code suppose que le middleware d’authentification ajoute requete.utilisateur.id et que la couche de journalisation fournit info et error. Il transmet aussi l’identifiant de corrélation, établi en amont, au service d’export. Ces champs doivent être raccordés aux conventions déjà utilisées par votre application.
Maîtriser le transfert et ses échecs
La fonction injectée crée le flux seulement après le passage des deux middlewares. Elle reçoit le signal d’annulation : le service peut le transmettre aux opérations de lecture qu’il sait interrompre et arrêter de demander de nouveaux lots. Le pipeline relie ensuite directement la source à la réponse ; il ne construit pas une chaîne CSV complète en mémoire.
Les en-têtes identifient le contenu comme CSV et demandent au navigateur de le télécharger. Si la génération échoue avant l’envoi des en-têtes, le serveur peut encore répondre par une erreur JSON générique. Après le début du téléchargement, la réponse ne peut plus être remplacée proprement : le code ferme alors le flux de réponse.
La limitation par utilisateur borne la fréquence ou le volume des exports, tandis que le traitement par lots borne la quantité lue à chaque étape. Ces protections sont complémentaires : elles réduisent le risque qu’un seul client consomme durablement la capacité du service. La configuration des quotas reste injectée, conformément au middleware déjà ajouté au projet.
À retenir
- Montez l’authentification et la limitation avant la création du flux d’export.
- Transférez les données avec pipeline, sans les agréger dans une chaîne en mémoire.
- Transmettez un signal d’annulation au service et interrompez le pipeline à la déconnexion.
- Distinguez les erreurs avant et après l’envoi des en-têtes HTTP.
- Retirez les écouteurs dans tous les cas et journalisez l’interruption sans exposer de détail au client.
La prochaine leçon portera sur les tests des scénarios de production, notamment ceux qui vérifient les routes protégées et les comportements d’échec.