Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

express 的 middleware 设计 #5

Open
CommanderXL opened this issue Jan 4, 2019 · 0 comments
Open

express 的 middleware 设计 #5

CommanderXL opened this issue Jan 4, 2019 · 0 comments
Labels

Comments

@CommanderXL
Copy link
Owner

还没用express写过server,先把部分源码撸了一遍,各位大神求轻拍。

express入口文件在lib文件夹下的express.js,其向外界暴露了一些方法。

最主要的(express.js 第36-47行):

function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);         //各中间件的处理入口,handle方法通过mixin拓展于proto
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  app.request = { __proto__: req, app: app };
  app.response = { __proto__: res, app: app };
  app.init();
  return app;
}

exports = module.exports = createApplication;

我们经常在自己的业务代码中这样写:

    var express = require('express');
    var app = express();

其实就是调用createApplication方法.这样就实例化了一个app。这个app比较特殊,通过mixin集成了一些其他的属性

mixin(app, EventEmitter.prototype, false); //拓展了事件发射器原型对象
mixin(app, proto, false); //拓展了application.js中的属性和方法

在我们业务代码实例化app的时候,调用了app.init()方法完成了一些初始化的配置。init()方法也是从application.js中继承的。

入口文件很清晰,主要是完成方法的暴露以及app的一些初始化操作。

接下来看下application.js中的部分代码逻辑:

第136-146行,延迟实例化一个_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));
  }
};

第157-174行,这是各个middleware的入口,

app.handle = function handle(req, res, callback) {
  var router = this._router;    //获取已经实例化得router

  // final handler
  var done = callback || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
  });

  // no routes
  if (!router) {
    debug('no routes defined on app');
    done();
    return;
  }

  router.handle(req, res, done);    //当http过来时,对于request和response的处理从这个地方开始
};

第187-242行,app.use方法提供了应用级的middleware,但是事实上在214行,this.lazyrouter()新建一个route,第219-221行,然后根据app.use(fn)传入的参数挂载到了route.use()路由级中间件上了。app.use()route.use的一个代理。

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)); //铺平arguments

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

  // setup router
  this.lazyrouter();                                //如果没有route实例则新建一个
  var router = this._router;

  fns.forEach(function (fn) {
    // non-express app                              //如果传入的不是express实例app
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn);                  //将中间件注入到router中
    }

    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) {          
        req.__proto__ = orig.request;
        res.__proto__ = orig.response;
        next(err);
      });
    });

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

  return this;
};

第255-258行,代理到router实例的route()的方法中:

    app.route = function route(path) {
        this.lazyrouter();
        return this._router.route(path);
    };

router实例是通过router/index.js提供构造函数来创建的,在这个文件夹中第42-60行:

var proto = module.exports = function(options) {
  var opts = options || {};

  function router(req, res, next) {
    router.handle(req, res, next);    //handle方法继承于proto
  }

  // mixin Router class functions
  router.__proto__ = proto;

  router.params = {};
  router._params = [];
  router.caseSensitive = opts.caseSensitive;
  router.mergeParams = opts.mergeParams;
  router.strict = opts.strict;
  router.stack = [];        //初始化一个stack.这个stack中保存了注册的所有中间件

  return router;
};

提供了一个router的构造函数,它的原型对象上,第136行提供了proto.handle方法,这个方法的作用就是接收来自httpreqres

第413行,提供了proto.use方法,正如上面所说的app.useroute.use的代理,这个方法的特殊性就在任何的http请求都会经过在app.use上挂载的中间件,例如现在express4.x已经将很多中间件从自身移除,需要你重新通过npm去安装,然后在业务代码中进行引用,例如使用body-parser中间件:

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json());

这样每次http请求过来的时候首先会经过bodyParser.json()这个中间件,它提供了一个向req添加req.body = {}方法,并传向下一个中间件的作用。
同时在route.use内部,第439-458行,

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 %s %s', path, fn.name || '<anonymous>');

    var layer = new Layer(path, {       //新建一个layer,layer上挂载了error_handler和request_handler
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

    layer.route = undefined;            

    this.stack.push(layer);             //route自身会维护一个stack,将每个新建的layer都推入stack当中,这个layer实例最终会对匹配的path,作出error_handle或者request_handle。
  }

第477行,proto.route方法提供了一个新建route的方法。

proto.route = function route(path) {
  var route = new Route(path);      //新建一个route,这个Route构建函数内部实现见./route.js,它里面提供了一个空的stack,用以

  var layer = new Layer(path, {                     //新建一个layer,layer的作用见下面的讲解
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));

  layer.route = route;  

  this.stack.push(layer);   //新建一个route,这个route会维护自身的stack
  return route;
};

var route = require('express').Router(),但是这个方法不同的地方
在于,它会自身维护一个stack,这个stack中有你在这个方法上面定义的所有中间件。同样,你可以通过这个route挂载对于不同路径的req, res的处理。

使用的方法:

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

//没有挂载任何路径的中间件,通过该路由的每个请求都会执行该中间件
router.use(function(req, res, next) {
    console.log('route.use');
})


router.get('/test', function(req, res, next) {
    console.log('route.get'); 
});

//最后需要将这个router挂载到应用
app.use('/', router);

以上部分主要是整个express的中间件的挂载。总结一下:

  1. 通过app.use()挂载的中间件最终都代理到了router.use()方法下
  2. router.use()方法,新建一个layer,layer上保存了路径,默认为'/',及相应的处理方法,并存入这个app维护的stack中。
  3. 通过var router = require('express').Router()新建的router路径级实例,同样可以挂载不同的中间件,不过最后需要将这个router路由注入到app应用当中:app.use('/', router);

接下来讲下当http请求到来的时候,数据的流向: 在你定义中间件的过程中,因为是维护了一个app或者route实例,它们分别都有一个stack。这个stackFIFO的,因此每当一个请求过来的时候,数据从最开始的定义的中间件开始,一直向下按顺序进行传递,因此你可以自己定义,当然,你需要调用next()方法。就比如Route.protoype.dispath方法

//将req, res分发给这个route
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();
  if (method === 'head' && !this.methods['head']) {
    method = 'get';
  }

  req.route = this;

  next();

  function next(err) {
    if (err && err === 'route') {
      return done();
    }

    var layer = stack[idx++];
    if (!layer) {
      return done(err);
    }

    if (layer.method && layer.method !== method) {  //匹配传入的req请求方式,和layer的method进行对比
      return next(err);
    }

//调用layer.handle,用以错误处理或者request处理
    if (err) {
      layer.handle_error(err, req, res, next);      
    } else {
      layer.handle_request(req, res, next);         
    }
  }
};

最后,http请求的处理:
app或者route实例中,自身有一个stack,这个stack就存放了在挂载中间时新建的layer,每个layer实例都保存了对应的路径,以及相应的error_handlerequest_handle

谢谢大家看到这里,欢迎大家斧正。

下一篇写写express路由的实现。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant