Si vous développiez pour la plateforme node.js, vous avez probablement entendu parler d' 
express.js . Il s'agit de l'un des frameworks lĂ©gers les plus populaires utilisĂ©s pour crĂ©er des applications Web pour le nĆud.

L'auteur du matériel, dont nous publions la traduction aujourd'hui, propose d'étudier les caractéristiques de la structure interne du framework express en analysant son code source et en considérant un exemple de son utilisation. Il pense que l'étude des mécanismes sous-jacents aux bibliothÚques open source populaires contribue à une meilleure compréhension de celles-ci, leur enlÚve le rideau du «mystÚre» et aide à créer de meilleures applications basées sur elles.
Vous pouvez trouver pratique de garder le code source express à portée de main lors de la lecture de ce document. 
Cette version est utilisĂ©e ici. Vous pouvez lire cet article sans ouvrir le code express, car ici, le cas Ă©chĂ©ant, des fragments de code de cette bibliothĂšque sont donnĂ©s. Dans les endroits oĂč le code est abrĂ©gĂ©, les commentaires de la forme 
// ...Un exemple de base de l'utilisation express
Pour commencer, jetons un Ćil au «Bonjour tout le monde!» Traditionnel dans le dĂ©veloppement de nouvelles technologies informatiques - un exemple. Il peut ĂȘtre trouvĂ© sur le site officiel du framework, il servira de point de dĂ©part Ă  nos recherches.
 const express = require('express') const app = express() app.get('/', (req, res) => res.send('Hello World!')) app.listen(3000, () => console.log('Example app listening on port 3000!')) 
Ce code démarre un nouveau serveur HTTP sur le port 3000 et envoie un 
Hello World! aux demandes reçues sur le 
GET / route. Si vous n'entrez pas dans les détails, nous pouvons distinguer quatre étapes de ce qui se passe, que nous pouvons analyser:
- Créez une nouvelle application express.
- Créez un nouvel itinéraire.
- Démarrage du serveur HTTP au numéro de port spécifié.
- Traitement des demandes entrantes au serveur.
Création d'une nouvelle application express
La commande 
var app = express() vous permet de créer une nouvelle application express. La fonction 
createApplication du fichier lib / express.js est la fonction exportée par défaut; c'est nous qui y 
accédons en appelant la fonction 
express() . Voici quelques points importants auxquels vous devez faire attention:
 // ... var mixin = require('merge-descriptors'); var proto = require('./application'); // ... function createApplication() { //    ,     . //     : `function(req, res, next)` var app = function(req, res, next) {   app.handle(req, res, next); }; // ... //  `mixin`    `proto`  `app` //     -  `get`,     . mixin(app, proto, false); // ... return app; } 
L'objet 
app renvoyé par cette fonction est l'un des objets utilisés dans notre code d'application. La méthode 
app.get ajoutée à l'aide de la fonction 
mixin de la bibliothĂšque 
merge-descriptors , qui est responsable de l'attribution des méthodes d' 
app déclarées dans 
proto . L'objet 
proto lui-mĂȘme est importĂ© de 
lib / application.js .
Créer un nouvel itinéraire
Voyons maintenant le 
code qui est responsable de la création de la méthode 
app.get partir de notre exemple.
 var slice = Array.prototype.slice; // ... /** *   `.VERB(...)` `router.VERB(...)`. */ // `methods`    HTTP, (  ['get','post',...]) methods.forEach(function(method){ //    app.get app[method] = function(path){   //     //          var route = this._router.route(path);   //        route[method].apply(route, slice.call(arguments, 1));   //   `app`,          return this; }; }); 
Il est intéressant de noter qu'en plus des fonctionnalités sémantiques, toutes les méthodes qui implémentent des actions HTTP, telles que 
app.get , 
app.post , 
app.put et similaires, en termes de fonctionnalitĂ©, peuvent ĂȘtre considĂ©rĂ©es comme identiques. Si vous simplifiez le code ci-dessus, en le rĂ©duisant Ă  l'implĂ©mentation d'une seule mĂ©thode 
get , vous obtenez quelque chose comme ceci:
 app.get = function(path, handler){ // ... var route = this._router.route(path); route.get(handler) return this } 
Bien que la fonction ci-dessus ait 2 arguments, elle est similaire Ă  la fonction 
app[method] = function(path){...} . Le deuxiĂšme argument, 
handler , est obtenu en appelant 
slice.call(arguments, 1) .
En résumé, 
app.<method> enregistre simplement l'itinéraire dans le routeur d'application à l'aide de sa méthode de 
route , puis passe le 
handler Ă  
route.<method> .
La méthode du routeur 
route() est déclarée dans 
lib / router / index.js :
 // proto -     `_router` proto.route = function route(path) { var route = new Route(path); var layer = new Layer(path, {   sensitive: this.caseSensitive,   strict: this.strict,   end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; }; 
Sans surprise, la déclaration de la méthode 
route.get dans 
lib / router / route.js est similaire à la déclaration de 
app.get :
 methods.forEach(function (method) { Route.prototype[method] = function () {   // `flatten`   ,  [1,[2,3]],      var handles = flatten(slice.call(arguments));   for (var i = 0; i < handles.length; i++) {     var handle = handles[i];         // ...     //   ,  ,    Layer,     //            var layer = Layer('/', {}, handle);     // ...     this.stack.push(layer);   }   return this; }; }); 
Chaque route peut avoir plusieurs gestionnaires; sur la base de chaque gestionnaire, une variable de type 
Layer construite, qui est une couche de traitement des données, qui pénÚtre ensuite dans la pile.
Objets de calque
_router et 
route utilisent des objets de type 
Layer . Afin de comprendre l'essence d'un tel objet, regardons son 
constructeur :
 function Layer(path, options, fn) { // ... this.handle = fn; this.regexp = pathRegexp(path, this.keys = [], opts); // ... } 
Lors de la création d'objets de type 
Layer ils reçoivent un chemin, certains paramÚtres et une fonction. Dans le cas de notre routeur, cette fonction est 
route.dispatch (nous en parlerons plus loin ci-dessous, en termes gĂ©nĂ©raux, elle est conçue pour transmettre une requĂȘte Ă  une route distincte). Dans le cas de la route elle-mĂȘme, cette fonction est une fonction de gestionnaire dĂ©clarĂ©e dans le code de notre exemple.
Chaque objet de type 
Layer possÚde une méthode 
handle_request , qui est responsable de l'exécution de la fonction passée lors de l'initialisation de l'objet.
Rappelez-vous ce qui se passe lors de la création d'un itinéraire à l'aide de la méthode 
app.get :
- Un itinéraire est créé dans le routeur de l'application ( this._router).
- La méthode de routage de dispatchest affectée en tant que méthode de gestionnaire de l'objetLayercorrespondant, et cet objet est poussé sur la pile du routeur.
- Le gestionnaire de demande est transmis à l'objet Layertant que méthode de gestionnaire, et cet objet est poussé sur la pile de routes.
Par conséquent, tous les gestionnaires sont stockés dans l'instance d' 
app sous la forme d'objets du type 
Layer qui se trouvent dans la pile de routes, dont 
dispatch méthodes de 
dispatch sont affectées aux objets 
Layer qui se trouvent dans la pile du routeur:
Objets de calque sur la pile du routeur et la pile de routageLes requĂȘtes HTTP entrantes sont traitĂ©es conformĂ©ment Ă  cette logique. Nous en parlerons ci-dessous.
Démarrage du serveur HTTP
AprÚs avoir configuré les itinéraires, vous devez démarrer le serveur. Dans notre exemple, nous passons à la méthode 
app.listen , en lui passant le numéro de port et la fonction de rappel comme arguments. Afin de comprendre les fonctionnalités de cette méthode, nous pouvons nous référer au fichier 
lib / application.js :
 app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); }; 
app.listen ĂȘtre juste un wrapper autour de 
http.createServer . Un tel point de vue est logique, car si vous vous souvenez de ce dont nous avons parlé au tout début, l' 
app n'est qu'une fonction avec la 
function(req, res, next) {...} signature 
function(req, res, next) {...} , qui est compatible avec les arguments nécessaires pour 
http.createServer (la signature de cette méthode est 
function (req, res) {...} ).
AprĂšs avoir rĂ©alisĂ© que, finalement, tout ce que express.js nous donne peut ĂȘtre rĂ©duit Ă  un gestionnaire de fonctions trĂšs intelligent, le cadre ne semble plus aussi compliquĂ© et mystĂ©rieux qu'auparavant.
Traitement des requĂȘtes HTTP
Maintenant que nous savons que l' 
app n'est qu'un gestionnaire de demandes, nous suivrons le chemin qu'une demande HTTP passe dans une application express. Ce chemin le mÚne au gestionnaire déclaré par nous.
Tout d'abord, la demande va Ă  la fonction 
createApplication ( 
lib / express.js ):
 var app = function(req, res, next) {   app.handle(req, res, next); }; 
Ensuite, il passe à la méthode 
app.handle ( 
lib / application.js ):
 app.handle = function handle(req, res, callback) { // `this._router` -  ,    ,  `app.get` var router = this._router; // ... //     `handle` router.handle(req, res, done); }; 
La méthode 
router.handle déclarée dans 
lib / router / index.js :
 proto.handle = function handle(req, res, out) { var self = this; //... // self.stack -  ,      // Layer (  ) var stack = self.stack; // ... next(); function next(err) {   // ...   //        var path = getPathname(req);   // ...   var layer;   var match;   var route;   while (match !== true && idx < stack.length) {     layer = stack[idx++];     match = matchLayer(layer, path);     route = layer.route;     // ...     if (match !== true) {       continue;     }     // ...      HTTP,       }  // ...      // process_params    ,          self.process_params(layer, paramcalled, req, res, function (err) {     // ...     if (route) {       //       `layer.handle_request`       //         `next`       //  ,   `next`     ,              //  ,   `next`   ,            return layer.handle_request(req, res, next);     }     // ...   }); } }; 
Si vous décrivez ce qui se passe en un mot, la fonction 
router.handle parcourt toutes les couches de la pile jusqu'à ce qu'elle trouve celle qui correspond au chemin spécifié dans la demande. Ensuite, la méthode de couche 
handle_request sera appelée, qui exécutera la fonction de gestionnaire prédéfinie. Cette fonction de gestionnaire est une méthode de routage de 
dispatch , qui est déclarée dans 
lib / route / route.js :
 Route.prototype.dispatch = function dispatch(req, res, done) { var stack = this.stack; // ... next(); function next(err) {   // ...   var layer = stack[idx++];   // ...    layer.handle_request(req, res, next);   // ... } }; 
Tout comme dans le cas du routeur, lors du traitement de chaque route, les couches que cette route possÚde sont énumérées et leurs méthodes 
handle_request qui exécutent les méthodes du gestionnaire de couches sont 
handle_request . Dans notre cas, il s'agit d'un gestionnaire de requĂȘtes, qui est dĂ©clarĂ© dans le code d'application.
Ici, enfin, la requĂȘte HTTP tombe dans la zone de code de notre application.
Chemin de demande dans l'application expressRésumé
Ici, nous n'avons examinĂ© que les mĂ©canismes de base de la bibliothĂšque express.js, ceux qui sont responsables du fonctionnement du serveur Web, mais cette bibliothĂšque possĂšde Ă©galement de nombreuses autres fonctionnalitĂ©s. Nous ne nous sommes pas arrĂȘtĂ©s aux vĂ©rifications que les requĂȘtes passent avant qu'elles n'atteignent les gestionnaires; nous n'avons pas parlĂ© des mĂ©thodes d'assistance qui sont disponibles lorsque vous travaillez avec les variables 
res et 
req . Et enfin, nous n'avons pas abordĂ© l'une des fonctionnalitĂ©s les plus puissantes de l'express. Il consiste Ă  utiliser un middleware, qui peut viser Ă  rĂ©soudre presque tous les problĂšmes - de l'analyse des requĂȘtes Ă  la mise en Ćuvre d'un systĂšme d'authentification complet.
Nous espérons que ce matériel vous a aidé à comprendre les principales caractéristiques de l'appareil express, et maintenant, si nécessaire, vous pouvez comprendre tout le reste en analysant indépendamment les parties du code source de cette bibliothÚque qui vous intéressent.
Chers lecteurs! Utilisez-vous express.js?
