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
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. - 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 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?
