关于express:三步法解析Express源码

31次阅读

共计 6305 个字符,预计需要花费 16 分钟才能阅读完成。

关注公众号“执鸢者 ”,获取大量教学视频及 私人总结面筋 并进入 业余交换群 .

在抖音上有幸看到一个程序员讲述如何浏览源代码,次要分为三步:领悟思维、把握设计、领会细节。

  1. 领悟思维:只需领会作者设计框架的初衷和目标
  2. 把握设计:只需领会代码的接口和抽象类以及宏观的设计
  3. 领会细节:是基于顶层的形象接口设计,逐步开展代码的画卷

基于上述三步法,急不可待的拿 Express 开刀了。本次源码解析有什么不到位的中央各位读者能够在上面留言,咱们一起交换。

一、领悟思维

在 Express 中文网上,介绍 Express 是基于 Node.js 平台,疾速、凋谢、极简的 Web 开发框架。在这句话外面能够失去解读出以下几点含意:

  1. Express 是基于 Node.js 平台,并且具备疾速、极简的特点,阐明其初衷就是为了通过扩大 Node 的性能来进步开发效率。
  2. 凋谢的特点阐明该框架不会对开发者过多的限度,能够自在的施展设想进行性能的扩大。
  3. Express 是 Web 开发框架,阐明作者的定位就是为了更加不便的帮忙咱们解决 HTTP 的申请和响应。

二、把握设计

了解了作者设计的思维,上面从源码目录、外围设计原理及形象接口三个层面来对 Express 进行整体的把握。

2.1 源码目录

如下所示是 Express 的源码目录,相比拟来说还是比较简单的。

├─application.js— 创立 Express 利用后可间接调用的 api 均在此处(外围)<br/>
├─express.js— 入口文件,创立一个 Express 利用 <br/>
├─request.js— 丰盛了 http 中 request 实例上的性能 <br/>
├─response.js— 丰盛了 http 中 response 实例上的性能 <br/>
├─utils.js— 工具函数 <br/>
├─view.js— 与模板渲染相干的内容 <br/>
├─router— 与路由相干的内容(外围)<br/>
| ├─index.js<br/>
| ├─layer.js<br/>
| └route.js<br/>
├─middleware— 与中间件相干的内容 <br/>
| ├─init.js— 会将新减少在 request 和 response 新减少的性能挂载到原始申请的 request 和 response 的原型上 <br/>
| └query.js— 将申请 url 中的 query 局部增加到 request 的 query 属性上 <br/>

2.2 形象接口

对源码的目录构造有了肯定理解,上面利用 UML 类图对该零碎各个模块的依赖关系进一步理解,为后续源码剖析打好根底。

2.3 设计原理

这一部分是整个 Express 框架的外围,下图是整个框架的运行流程,一看是不是很懵逼,为了搞清楚这一部分,须要明确四个概念:Application、Router、Layer、Route。

为了明确上述四个概念,先引入一段代码

const express = require('./express');
const res = require('./response');
const app = express();
app.get('/test1', (req, res, next) => {console.log('one');
    next();}, (req, res) => {console.log('two');
    res.end('two');
})
app.get('/test2', (req, res, next) => {console.log('three');
    next();}, (req, res) => {console.log('four');
    res.end('four');
})
app.listen(3000);
  1. Application
    示意一个 Express 利用,通过 express()即可进行创立。
  2. Router<
    路由零碎,用于调度整个零碎的运行,在上述代码中该路由零碎蕴含 app.get(‘/test1’,……)和 app.get(‘/test2’,……)两大部分
  3. Layer
    代表一层,对于上述代码中 app.get(‘/test1’,……)和 app.get(‘/test2’,……)都能够成为一个 Layer
  4. Route
    一个 Layer 中会有多个处理函数的状况,这多个处理函数形成了 Route,而 Route 中的每一个函数又成为 Route 中的 Layer。对于上述代码中,app.get(‘/test1’,……)中的两个函数形成一个 Route,每个函数又是 Route 中的 Layer。

理解完上述概念后,联合该幅图,就大略能对整个流程有了直观感触。首先启动服务,而后客户端发动了 http://localhost:3000/test2 的申请,该过程应该如何运行呢?

  1. 启动服务时会顺次执行程序,将该路由零碎中的门路、申请办法、处理函数进行存储(这些信息依据肯定构造存储在 Router、Layer 和 Route 中)
  2. 对相应的地址进行监听,期待申请达到。
  3. 申请达到,首先依据申请的 path 去从上到下进行匹配,门路匹配正确则进入该 Layer,否则跳出该 Layer。
  4. 若匹配到该 Layer,则进行申请形式的匹配,若匹配形式匹配正确,则执行该对应 Route 中的函数。

上述解释的比较简单,后续会在细节局部进一步论述。

三、领会细节

通过上述对 Express 设计原理的剖析,上面将从两个方面做进一步的源码解读,上面流程图是一个常见的 Express 我的项目的过程,首先会进行 app 实例初始化、而后调用一系列中间件,最初建设监听。对于整个工程的运行来说,次要分为两个阶段:初始化阶段、申请解决阶段,上面将以 app.get()为例来论述一下该外围细节。

3.1 初始化阶段

上面利用 app.get()这个路由来理解一下工程的初始化阶段。

  1. 首先来看一下 app.get()的内容 (源代码中 app.get() 是通过遍历 methods 的形式产生)

    app.get = function(path){
        // ……
        this.lazyrouter();
    
        var route = this._router.route(path);
        route.get.apply(route, slice.call(arguments, 1));
        return this;
    };
  2. 在 app.lazyrouter()会实现 router 的实例化过程

    app.lazyrouter = function lazyrouter() {if (!this._router) {
        this._router = new Router({caseSensitive: this.enabled('case sensitive routing'),
          strict: this.enabled('strict routing')
        });
    
        // 此处会应用一些中间件
        this._router.use(query(this.get('query parser fn')));
        this._router.use(middleware.init(this));
      }
    };

    留神:该过程中其实是利用了单例模式,保障整个过程中获取 router 实例的唯一性。

  3. 调用 router.route()办法实现 layer 的实例化、解决及保留,并返回实例化后的 route。(留神源码中是 proto.route)

    router.prototype.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;// 把 route 放到 layer 上
    
      this.stack.push(layer); // 把 layer 放到数组中
      return route;
    };
  4. 将该 app.get()中的函数存储到 route 的 stack 中。(留神源码中也是通过遍历 method 的形式将 get 挂载到 route 的 prototype 上)

    Route.prototype.get = function(){var handles = flatten(slice.call(arguments));
    
        for (var i = 0; i < handles.length; i++) {var handle = handles[i];
          // ……
          // 给 route 增加 layer,这个层中须要寄存办法名和 handler
          var layer = Layer('/', {}, handle);
          layer.method = method;
    
          this.methods[method] = true;
          this.stack.push(layer);
        }
    
        return this;
      };

留神:上述代码均删除了源码中一些异样判断逻辑,不便读者看清整体框架。

通过上述的剖析,能够看出初始化阶段次要做了两件事件:

  1. 将路由解决形式(app.get()、app.post()……)、app.use()等划分为路由零碎中的一个 Layer。
  2. 对于每一个层中的处理函数全副存储至 Route 对象中,一个 Route 对象与一个 Layer 互相映射。

3.2 申请解决阶段

当服务启动后即进入监听状态,期待申请达到后进行解决。

  1. app.listen()使服务进入监听状态(本质上是调用了 http 模块)

    app.listen = function listen() {var server = http.createServer(this);
      return server.listen.apply(server, arguments);
    };
  2. 当连贯建设会调用 app 实例,app 实例中会立刻执行 app.handle()函数,app.handle()函数会立刻调用路由零碎的处理函数 router.handle()

    app.handle = function handle(req, res, callback) {
      var router = this._router;
      // 如果路由零碎中解决不了这个申请,就调用 done 办法
      var done = callback || finalhandler(req, res, {env: this.get('env'),
        onerror: logerror.bind(this)
      });
      //……
      router.handle(req, res, done);
    };
  3. router.handle()次要是依据门路获取是否有匹配的 layer,当匹配到之后则调用 layer.prototype.handle_request()去执行 route 中内容的解决

    router.prototype.handle = function handle(req, res, out) {
      // 这个中央参数 out 就是 done,当所有都匹配不到,就从路由零碎中进去,名字很形象
      var self = this;
      // ……
      var stack = self.stack;
      
      // ……
    
      next();
    
      function next(err) {
        // ……
        // get pathname of request
        var path = getPathname(req);
    
        // find next matching layer
        var layer;
        var match;
        var route;
    
        while (match !== true && idx < stack.length) {layer = stack[idx++];
          match = matchLayer(layer, path);
          route = layer.route;
          // ……
        }
    
        // no match
        if (match !== true) {return done(layerError);
        }
        // ……
    
        // Capture one-time layer values
        req.params = self.mergeParams
          ? mergeParams(layer.params, parentParams)
          : layer.params;
        var layerPath = layer.path;
    
        // this should be done for the layer
        self.process_params(layer, paramcalled, req, res, function (err) {if (err) {return next(layerError || err);
          }
    
          if (route) {return layer.handle_request(req, res, next);
          }
    
          trim_prefix(layer, layerError, layerPath, path);
        });
      }
    
      function trim_prefix(layer, layerError, layerPath, path) {
        // ……
    
        if (layerError) {layer.handle_error(layerError, req, res, next);
        } else {layer.handle_request(req, res, next);
        }
      }
    };
    
  4. layer.handle_request()会调用 route.dispatch()触发 route 中内容的执行

    Layer.prototype.handle_request = function handle(req, res, next) {
      var fn = this.handle;
    
      if (fn.length > 3) {
        // not a standard request handler
        return next();}
    
      try {fn(req, res, next);
      } catch (err) {next(err);
      }
    };
  5. route 中的通过判断申请的办法和 route 中 layer 的办法是否匹配,匹配的话则执行相应函数,若所有 route 中的 layer 都不匹配,则调到外层的 layer 中继续执行。

    Route.prototype.dispatch = function dispatch(req, res, done) {
      var idx = 0;
      var stack = this.stack;
      if (stack.length === 0) {return done();
      }
    
      var method = req.method.toLowerCase();
      // ……
        
      next();
      // 此 next 办法是用户调用的 next,如果调用 next 会执行内层的 next 办法,如果没有匹配到会调用外层的 next 办法
      function next(err) {
        // ……
    
        var layer = stack[idx++];
        if (!layer) {return done(err);
        }
    
        if (layer.method && layer.method !== method) {return next(err);
        }
    
        // 如果以后 route 中的 layer 的办法匹配到了,执行此 layer 上的 handler
        if (err) {layer.handle_error(err, req, res, next);
        } else {layer.handle_request(req, res, next);
        }
      }
    };

通过上述的剖析,能够看出初始化阶段次要做了两件事件:

  1. 首先判断 layer 中的 path 和申请的 path 是否统一,统一则会进入 route 进行解决,否则调到下一层 layer
  2. 在 route 中会判断 route 中的 layer 与申请办法是否统一,统一的话则函数执行,否则不执行,所有 route 中的 layer 执行完后跳到上层的 layer 进行执行。

1. 如果感觉这篇文章还不错,来个分享、点赞、吧,让更多的人也看到

2. 关注公众号执鸢者,支付学习材料,定期为你推送原创深度好文

欢送大家关注公众号(回复“书籍”获取大量前端学习材料, 回复“前端视频”获取大量前端教学视频)

正文完
 0