一、领悟思想

二、把握设计

2.1 源码目录

├─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 抽象接口

2.3 设计原理

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。

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

三、体会细节

3.1 初始化阶段

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

注意:上述代码均删除了源码中一些异常判断逻辑,方便读者看清整体框架。

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.如果觉得这篇文章还不错,来个分享、点赞、吧,让更多的人也看到

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

03-05 22:17