工作和内部设备express.js的功能

如果您正在为平台node.js开发,那么您可能听说过express.js 。 这是用于为节点创建Web应用程序的最流行的轻量级框架之一。



该材料的作者(我们今天将发布其翻译版本)通过分析源代码的源代码并考虑其使用示例来研究Express框架的内部结构特征。 他认为,对流行的开源库背后的机制的研究有助于加深对它们的了解,消除它们的“神秘感”,并有助于基于它们创建更好的应用程序。

您可能会发现,在阅读本材料时,保持便捷的源代码很方便。 在此使用此版本 。 您可以在不打开明确代码的情况下阅读本文,因为在适当情况下,在此给出了该库的代码片段。 在那些代码缩写的地方, // ...形式的注释

使用快递的基本示例


首先,让我们看一下“ Hello World!”,它是掌握新计算机技术的传统示例。 可以在框架的官方网站上找到它,它将作为我们研究的起点。

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

此代码在端口3000上启动新的HTTP服务器并发送Hello World! GET /路由上收到的请求。 如果您不详细介绍信息,那么我们可以区分正在发生的四个阶段,我们可以对其进行分析:

  1. 创建一个新的快速申请。
  2. 创建一条新路线。
  3. 从指定的端口号启动HTTP服务器。
  4. 处理到服务器的传入请求。

创建一个新的快递申请


var app = express()命令允许您创建一个新的express应用程序。 来自lib / express.js文件createApplication函数是默认的导出函数;是我们通过调用express()函数来访问它的函数。 以下是您应注意的一些重要事项:

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

从此函数返回的app对象是我们的应用程序代码中使用的对象之一。 使用merge-descriptors库的mixin函数添加app.get方法,该函数负责分配proto声明的app方法。 proto对象本身是从lib / application.js导入的。

建立新路线


现在,让我们看一下负责从示例中创建app.get方法的代码

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

有趣的是,除了语义特征之外,在功能方面,可以将实现HTTP操作的所有方法(例如app.getapp.postapp.put等)视为相同。 如果简化上面的代码,将其简化为仅一种get方法的实现,则会得到如下所示的内容:

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

尽管上面的函数有2个参数,但它与函数app[method] = function(path){...}类似。 第二个参数handler是通过调用slice.call(arguments, 1)

简而言之, app.<method>只是使用其route方法将路由保存在应用程序路由器中,然后将handler传递给route.<method>

lib / router / index.js中声明route()路由器方法:

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

毫不奇怪, lib / router / route.js中 route.get方法的声明类似于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; }; }); 

每个路由可以具有多个处理程序;在每个处理程序的基础上,构造一个Layer类型的变量,该变量是一个数据处理层,然后进入堆栈。

图层对象


_routerroute使用Layer类型的对象。 为了理解这样一个对象的本质,让我们看一下它的构造函数

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

在创建类型为Layer对象时Layer会为它们提供路径,某些参数和一个函数。 就我们的路由器而言,此功能是route.dispatchroute.dispatch ,我们将在下面进一步讨论该功能,旨在将请求传输到单独的路由)。 对于路由本身,此函数是在我们的示例代码中声明的处理程序函数。

每个类型为Layer对象都有一个handle_request方法,该方法负责执行初始化对象时传递的函数。

回想一下使用app.get方法创建路线时发生的情况:

  1. 在应用程序的路由器( this._router )中创建一条路由。
  2. dispatch路由方法被分配为相应的Layer对象的处理程序方法,并且将该对象压入路由器堆栈。
  3. 请求处理程序作为处理程序方法传递给Layer对象,然后将该对象压入路由堆栈。

结果,所有处理程序都以路由堆栈内部的Layer类型对象的形式存储在app实例内部,其dispatch方法分配给路由器堆栈中的Layer对象:


在路由器堆栈和路由堆栈上分层对象

传入的HTTP请求将根据此逻辑进行处理。 我们将在下面讨论它们。

HTTP服务器启动


配置路由后,需要启动服务器。 在我们的示例中,我们转到app.listen方法,将端口号和回调函数作为参数传递给它。 为了了解此方法的功能,我们可以参考lib / application.js文件:

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

app.listen只是http.createServer的包装。 这样的观点是有道理的,因为如果您回忆起初我们所讨论的内容,则app只是具有签名function(req, res, next) {...}的函数,它与http.createServer必需的参数兼容http.createServer (此方法的签名是function (req, res) {...} )。

在意识到这一点之后,express.js给我们的一切最终都可以简化为一个非常智能的函数处理程序,该框架不再像以前那样复杂和神秘。

HTTP请求处理


现在我们知道该app只是一个请求处理程序,我们将遵循HTTP请求在快速应用程序内部传递的路径。 该路径将其引向我们声明的处理程序。

首先,请求转到createApplication函数( lib / express.js ):

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

然后转到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); }; 

router.handle方法在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);     }     // ...   }); } }; 

简而言之, router.handle函数遍历堆栈上的所有层,直到找到与请求中指定的路径匹配的层。 然后,将调用handle_request图层方法,该方法将执行预定义的处理函数。 此处理程序函数是dispatch路由方法,在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);   // ... } }; 

就像路由器一样,在处理每个路由期间,将枚举此路由所具有的层,并调用它们的执行层处理程序方法的handle_request方法。 在我们的例子中,这是一个请求处理程序,在应用程序代码中声明。

最后,这里的HTTP请求属于我们应用程序的代码区域。


快速申请中的请求路径

总结


在这里,我们仅检查了express.js库的基本机制,这些基本机制负责Web服务器的操作,但是该库还具有许多其他功能。 我们没有在请求到达处理程序之前就停止检查;我们没有谈论使用resreq变量时可用的辅助方法。 最后,我们没有涉及Express最强大的功能之一。 它包含中间件的使用,中间件的目标是解决几乎所有问题-从解析请求到实现完整的身份验证系统。

我们希望该材料可以帮助您了解Express设备的主要功能,现在,如有必要,您可以通过独立分析该库中您感兴趣的部分源代码来了解其他所有内容。

亲爱的读者们! 您是否使用express.js?

Source: https://habr.com/ru/post/zh-CN414079/


All Articles