7

Express

基于 Node.js 平台,快速、开放、极简的 web 开发框架

安装

//应用生成器工具
npm install express-generator -g

//创建express应用包
express app

//安装依赖
npm install

成功生成后,会产生以下的目录和文件:

|---bin
|---node_module
|---public
|---routes
|---view
|---app.js
|---package.json

接下来我们通过:

npm start 

启动程序后,访问127.0.0.1:3000,就能访问到express的页面了。

接下来通过研究源码,来探讨express路由原理的实现。

路由

我们通过查看app.jsindex.js文件:

app.js

var index = require('./routes/index');

app.use('/', index);

//或
app.get('/', index);

routes/index.js

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

可以看出,express的路由大概实现 定义一份路由规则文件,再通过app.use()或者app[METHOD]来建立路由规则访问联系,虽然两者的结果一样,但是存在本质上的区别。

下图是主要涉及的几个文件:

图片描述

接下来我们通过源码首先看看app.use()具体是一个什么样实现思路。

app.use

我们打开node_module里的express文件夹。打开lib/application.js文件。

app.use = function use(fn) {
    var offset = 0;
    var path = '/';

    // default path to '/'
    // disambiguate app.use([fn])
    if (typeof fn !== 'function') {
        var arg = fn;

        while (Array.isArray(arg) && arg.length !== 0) {
            arg = arg[0];
        }

        // first arg is the path
        if (typeof arg !== 'function') {
            offset = 1;
            path = fn;
        }
    }

    var fns = flatten(slice.call(arguments, offset));

    if (fns.length === 0) {
        throw new TypeError('app.use() requires middleware functions');
    }

    // setup router
    this.lazyrouter();
    var router = this._router;

    fns.forEach(function(fn) {
        // non-express app
        if (!fn || !fn.handle || !fn.set) {
            return router.use(path, fn);
        }

        debug('.use app under %s', path);
        fn.mountpath = path;
        fn.parent = this;

        // restore .app property on req and res
        router.use(path, function mounted_app(req, res, next) {
            var orig = req.app;
            fn.handle(req, res, function(err) {
                setPrototypeOf(req, orig.request)
                setPrototypeOf(res, orig.response)
                next(err);
            });
        });

        // mounted an app
        fn.emit('mount', this);
    }, this);

    return this;
};

看到use里部分的代码,开始做了判断处理use挂载的是路径还是function,并且通过lazyrouter()方法实例router类,并且全局只存在一个router实例对象,最终调用router.use()方法。

接着,我们到lib/router/index.js 看router.use方法的实现:

proto.use = function use(fn) {
  var offset = 0;
  var path = '/';

  // default path to '/'
  // disambiguate router.use([fn])
  if (typeof fn !== 'function') {
    var arg = fn;

    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0];
    }

    // first arg is the path
    if (typeof arg !== 'function') {
      offset = 1;
      path = fn;
    }
  }

  var callbacks = flatten(slice.call(arguments, offset));

  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires middleware functions');
  }

  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    if (typeof fn !== 'function') {
      throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
    }

    // add the middleware
    debug('use %o %s', path, fn.name || '<anonymous>')

    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

    layer.route = undefined;

    this.stack.push(layer);
  }

  return this;
};

通过对比app.use方法,router.use前半部分处理相同,但后面实例化一个Layer类,并且丢进stack里。

Layer类保存Router和Route一些数据信息:

图片描述

相同点:

path都是存放挂载路径,options.end用来判断是否是路由中间件。

不同点:

Router和Route的区别是一个是添非路由中间件,另一个是添加路由中间件。

他们的layer.route指向也不一样,一个指向undefined,另一个没有route属性。

文章进行到一半,我们小总结一下,app.use()方法是用来添加非路由中间件的,最终是调用router实例方法,会实例划一个Layer类对象用于存放数据,并且把layer对象push进router.stack里,全局只有一个router。

app[METHOD]

我们通过源码去探讨路由中间件app[METHOD]是一个怎样的原理:

app.jsapp.use('\',index)改成app.get('\',index).

application.js:

methods.forEach(function(method) {
    app[method] = function(path) {
        if (method === 'get' && arguments.length === 1) {
            // app.get(setting)
            return this.set(path);
        }

        this.lazyrouter();

        var route = this._router.route(path);
        route[method].apply(route, slice.call(arguments, 1));
        return this;
    };
});

可以看出,代码里做了一个app.get方法的判断处理,get方法只有一个参数时,是获取app的本地变量,后面还是实例化router对象,并且用router上的route方法放回的对象去调用Route类上的route[METHOD].

/lib/router/route.js

methods.forEach(function(method){
  Route.prototype[method] = function(){
    var handles = flatten(slice.call(arguments));

    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];

      if (typeof handle !== 'function') {
        var type = toString.call(handle);
        var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
        throw new Error(msg);
      }

      debug('%s %o', method, this.path)

      var layer = Layer('/', {}, handle);
      layer.method = method;

      this.methods[method] = true;
      this.stack.push(layer);
    }

    return this;
  };
});

在route里有一个实例化的layer,且放在stack里,与Router的layer不同的是,Route的没有layer.route且layer.method存放http方法。

到这里,我们大概可以总结下路由中间件和非路由中间件的联系,如下图:

图片描述

app初始化时,会push两个方法(init,query)进router.stack里。我们可以通过app.use往app添加非路由中间件,也可以通过app[METHOD]添加路由中间件,同样是push layer实例对象,但route是指向Route实例化的对象。

完整的Router逻辑过程,如图:

图片描述

总结

  1. express中添加中间件方法有app.use和app[METHOD],当然还有内置的Router类,app.use用来添加非路由中间件,app[METHOD]用来添加路由中间件。
  2. Layer类封装中间的path和handle(fns的处理)
  3. Router和Route都有对应的stack,但是Route在整个app中只有一个,而Route可以又多个。放在Router
    stack里的路由中间件,通过Layer.route指向Route,与Route stack相关联起来

NicolaChin
35 声望2 粉丝

互相交流和提高