Caractéristiques du travail et du périphérique interne express.js

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:

  1. Créez une nouvelle application express.
  2. Créez un nouvel itinéraire.
  3. Démarrage du serveur HTTP au numéro de port spécifié.
  4. 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 :

  1. Un itinéraire est créé dans le routeur de l'application ( this._router ).
  2. La méthode de routage de dispatch est affectée en tant que méthode de gestionnaire de l'objet Layer correspondant, et cet objet est poussé sur la pile du routeur.
  3. Le gestionnaire de demande est transmis à l'objet Layer tant 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 routage

Les 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 express

Ré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?

Source: https://habr.com/ru/post/fr414079/


All Articles