Características del trabajo y dispositivo interno express.js

Si estaba desarrollando para la plataforma node.js, entonces probablemente escuchó sobre express.js . Este es uno de los frameworks ligeros más populares utilizados para crear aplicaciones web para el nodo.



El autor del material, cuya traducción publicamos hoy, ofrece estudiar las características de la estructura interna del marco expreso a través del análisis de su código fuente y la consideración de un ejemplo de su uso. Él cree que el estudio de los mecanismos subyacentes a las populares bibliotecas de código abierto contribuye a una comprensión más profunda de ellas, les quita la cortina del "misterio" y ayuda a crear mejores aplicaciones basadas en ellas.

Puede resultarle conveniente tener a mano el código fuente express mientras lee este material. Esta versión se usa aquí. Puede leer este artículo sin abrir el código express, ya que aquí, donde sea apropiado, se proporcionan fragmentos de código de esta biblioteca. En aquellos lugares donde se abrevia el código, // ... comentarios de la forma // ...

Un ejemplo básico del uso de express


Para empezar, echemos un vistazo al "¡Hola Mundo!" Tradicional en el desarrollo de nuevas tecnologías informáticas: un ejemplo. Se puede encontrar en el sitio web oficial del marco, servirá como punto de partida en nuestra investigación.

 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!')) 

¡Este código inicia un nuevo servidor HTTP en el puerto 3000 y envía un Hello World! a las solicitudes recibidas en el GET / ruta. Si no entra en detalles, podemos distinguir cuatro etapas de lo que está sucediendo, que podemos analizar:

  1. Crea una nueva aplicación express.
  2. Crea una nueva ruta.
  3. Inicio del servidor HTTP en el número de puerto especificado.
  4. Procesando solicitudes entrantes al servidor.

Crear una nueva aplicación express


El comando var app = express() permite crear una nueva aplicación express. La función createApplication del archivo lib / express.js es la función exportada por defecto; somos nosotros quienes accedemos a ella llamando a la función express() . Estas son algunas cosas importantes a las que debe prestar atención:

 // ... 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; } 

El objeto de app devuelto por esta función es uno de los objetos utilizados en nuestro código de aplicación. El método app.get agrega usando la función mixin de la biblioteca merge-descriptors , que es responsable de asignar los métodos de la app declarados en proto . El objeto proto mismo se importa de lib / application.js .

Crea una nueva ruta


Ahora echemos un vistazo al código responsable de crear el método app.get partir de nuestro ejemplo.

 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; }; }); 

Es interesante observar que, además de las características semánticas, todos los métodos que implementan acciones HTTP, como app.get , app.post , app.put y similares, en términos de funcionalidad, pueden considerarse iguales. Si simplifica el código anterior, reduciéndolo a la implementación de un solo método get , obtendrá algo como lo siguiente:

 app.get = function(path, handler){ // ... var route = this._router.route(path); route.get(handler) return this } 

Aunque la función anterior tiene 2 argumentos, es similar a la app[method] = function(path){...} función app[method] = function(path){...} . El segundo argumento, handler , se obtiene llamando a slice.call(arguments, 1) .

En pocas palabras, la app.<method> simplemente guarda la ruta en el enrutador de la aplicación utilizando su método de route , y luego pasa el handler a la route.<method> .

El método de enrutador route() se declara en 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; }; 

Como era de esperar, la declaración del método route.get en lib / router / route.js es similar a la declaración 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; }; }); 

Cada ruta puede tener varios manejadores; sobre la base de cada manejador, Layer construye una variable de tipo Layer , que es una capa de procesamiento de datos, que luego ingresa a la pila.

Objetos de capa


Tanto _router como route usan objetos de tipo Layer . Para entender la esencia de tal objeto, veamos su constructor :

 function Layer(path, options, fn) { // ... this.handle = fn; this.regexp = pathRegexp(path, this.keys = [], opts); // ... } 

Al crear objetos de tipo Layer se les da una ruta, ciertos parámetros y una función. En el caso de nuestro enrutador, esta función es route.dispatch (hablaremos más sobre esto a continuación, en términos generales, está diseñado para transmitir una solicitud a una ruta separada). En el caso de la ruta en sí, esta función es una función de controlador declarada en el código de nuestro ejemplo.

Cada objeto de tipo Layer tiene un método handle_request , que es responsable de ejecutar la función pasada cuando se inicializó el objeto.

Recuerde lo que sucede al crear una ruta utilizando el método app.get :

  1. Se crea una ruta en el enrutador de la aplicación ( this._router ).
  2. El método de ruta de dispatch se asigna como el método de controlador del objeto de Layer correspondiente, y este objeto se inserta en la pila del enrutador.
  3. El controlador de solicitud se pasa al objeto Layer como un método de controlador, y este objeto se inserta en la pila de ruta.

Como resultado, todos los controladores se almacenan dentro de la instancia de la app en forma de objetos del tipo de Layer que están dentro de la pila de rutas, cuyos métodos de dispatch se asignan a los objetos de Layer que están en la pila de enrutadores:


Objetos de capa en la pila del enrutador y la pila de ruta

Las solicitudes HTTP entrantes se procesan de acuerdo con esta lógica. Hablaremos de ellos a continuación.

Inicio del servidor HTTP


Después de configurar las rutas, debe iniciar el servidor. En nuestro ejemplo, recurrimos al método app.listen , pasándole el número de puerto y la función de devolución de llamada como argumentos. Para comprender las características de este método, podemos consultar el archivo lib / application.js :

 app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); }; 

app.listen ser solo un contenedor de http.createServer . Este punto de vista tiene sentido, ya que si recuerda lo que hablamos al principio, la app es solo una función con una function(req, res, next) {...} firma function(req, res, next) {...} , que es compatible con los argumentos necesarios para http.createServer (la firma de este método es la function (req, res) {...} ).

Después de darse cuenta de que, al final, todo lo que express.js nos brinda puede reducirse a un controlador de funciones muy inteligente, el marco ya no parece tan complicado y misterioso como antes.

Procesamiento de solicitudes HTTP


Ahora que sabemos que la app es solo un controlador de solicitudes, seguiremos la ruta que pasa una solicitud HTTP dentro de una aplicación rápida. Este camino lo lleva al controlador declarado por nosotros.

Primero, la solicitud va a la función createApplication ( lib / express.js ):

 var app = function(req, res, next) {   app.handle(req, res, next); }; 

Luego va al método 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); }; 

El método router.handle declara en 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 describe lo que está sucediendo en pocas palabras, la función router.handle atraviesa todas las capas de la pila hasta que encuentra la que coincide con la ruta especificada en la solicitud. Luego, se handle_request método de capa handle_request , que ejecutará la función de controlador predefinida. Esta función de controlador es un método de ruta de dispatch , que se declara en 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);   // ... } }; 

Al igual que en el caso del enrutador, durante el procesamiento de cada ruta, se enumeran las capas que tiene esta ruta y se handle_request sus métodos handle_request que ejecutan los métodos de controlador de capa. En nuestro caso, este es un controlador de solicitud, que se declara en el código de la aplicación.

Aquí, finalmente, la solicitud HTTP cae en el área de código de nuestra aplicación.


Solicitar ruta en aplicación expresa

Resumen


Aquí examinamos solo los mecanismos básicos de la biblioteca express.js, aquellos que son responsables del funcionamiento del servidor web, pero esta biblioteca también tiene muchas otras características. No nos detuvimos en las comprobaciones por las que pasan las solicitudes antes de que lleguen a los controladores; no hablamos sobre los métodos auxiliares que están disponibles al trabajar con las variables res y req . Y finalmente, no tocamos una de las características más poderosas de express. Consiste en el uso de middleware, que puede estar dirigido a resolver casi cualquier problema, desde analizar las solicitudes hasta implementar un sistema de autenticación completo.

Esperamos que este material le haya ayudado a comprender las características principales del dispositivo express, y ahora, si es necesario, puede comprender todo lo demás analizando de forma independiente las partes del código fuente de esta biblioteca que le interesen.

Estimados lectores! ¿Usas express.js?

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


All Articles