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:
- Crea una nueva aplicación express.
- Crea una nueva ruta.
- Inicio del servidor HTTP en el número de puerto especificado.
- 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
:
- Se crea una ruta en el enrutador de la aplicación (
this._router
). - 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. - 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 rutaLas 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 expresaResumen
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?
