[JavaScript/JQuery] express 的 middleware 设计

来源:互联网 发布:sai软件下载官方下载 编辑:程序博客网 时间:2024/05/18 03:16

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

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

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

  1. function createApplication() {
  2. var app = function(req, res, next) {
  3. app.handle(req, res, next); //各中间件的处理入口,handle方法通过mixin拓展于proto
  4. };
  5. mixin(app, EventEmitter.prototype, false);
  6. mixin(app, proto, false);
  7. app.request = { __proto__: req, app: app };
  8. app.response = { __proto__: res, app: app };
  9. app.init();
  10. return app;
  11. }
  12. exports = module.exports = createApplication;
复制代码

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

  1. var express = require('express');
  2. var app = express();
复制代码

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

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

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

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

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

第136-146行,延迟实例化一个_router

  1. app.lazyrouter = function lazyrouter() {
  2. if (!this._router) {
  3. this._router = new Router({
  4. caseSensitive: this.enabled('case sensitive routing'),
  5. strict: this.enabled('strict routing')
  6. });
  7. this._router.use(query(this.get('query parser fn')));
  8. this._router.use(middleware.init(this));
  9. }
  10. };
复制代码

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

  1. app.handle = function handle(req, res, callback) {
  2. var router = this._router; //获取已经实例化得router
  3. // final handler
  4. var done = callback || finalhandler(req, res, {
  5. env: this.get('env'),
  6. onerror: logerror.bind(this)
  7. });
  8. // no routes
  9. if (!router) {
  10. debug('no routes defined on app');
  11. done();
  12. return;
  13. }
  14. router.handle(req, res, done); //当http过来时,对于request和response的处理从这个地方开始
  15. };
复制代码

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

  1. app.use = function use(fn) {
  2. var offset = 0;
  3. var path = '/';
  4. // default path to '/'
  5. // disambiguate app.use([fn])
  6. if (typeof fn !== 'function') {
  7. var arg = fn;
  8. while (Array.isArray(arg) && arg.length !== 0) {
  9. arg = arg[0];
  10. }
  11. // first arg is the path
  12. if (typeof arg !== 'function') {
  13. offset = 1;
  14. path = fn;
  15. }
  16. }
  17. var fns = flatten(slice.call(arguments, offset)); //铺平arguments
  18. if (fns.length === 0) {
  19. throw new TypeError('app.use() requires middleware functions');
  20. }
  21. // setup router
  22. this.lazyrouter(); //如果没有route实例则新建一个
  23. var router = this._router;
  24. fns.forEach(function (fn) {
  25. // non-express app //如果传入的不是express实例app
  26. if (!fn || !fn.handle || !fn.set) {
  27. return router.use(path, fn); //将中间件注入到router中
  28. }
  29. debug('.use app under %s', path);
  30. fn.mountpath = path;
  31. fn.parent = this;
  32. // restore .app property on req and res
  33. router.use(path, function mounted_app(req, res, next) {
  34. var orig = req.app;
  35. fn.handle(req, res, function (err) {
  36. req.__proto__ = orig.request;
  37. res.__proto__ = orig.response;
  38. next(err);
  39. });
  40. });
  41. // mounted an app
  42. fn.emit('mount', this);
  43. }, this);
  44. return this;
  45. };
复制代码

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

  1. app.route = function route(path) {
  2. this.lazyrouter();
  3. return this._router.route(path);
  4. };
复制代码

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

  1. var proto = module.exports = function(options) {
  2. var opts = options || {};
  3. function router(req, res, next) {
  4. router.handle(req, res, next); //handle方法继承于proto
  5. }
  6. // mixin Router class functions
  7. router.__proto__ = proto;
  8. router.params = {};
  9. router._params = [];
  10. router.caseSensitive = opts.caseSensitive;
  11. router.mergeParams = opts.mergeParams;
  12. router.strict = opts.strict;
  13. router.stack = []; //初始化一个stack.这个stack中保存了注册的所有中间件
  14. return router;
  15. };
复制代码

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

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

  1. var express = require('express');
  2. var app = express();
  3. var bodyParser = require('body-parser');
  4. app.use(bodyParser.json());
复制代码

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

  1. for (var i = 0; i < callbacks.length; i++) {
  2. var fn = callbacks[i];
  3. if (typeof fn !== 'function') {
  4. throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
  5. }
  6. // add the middleware
  7. debug('use %s %s', path, fn.name || '<anonymous>');
  8. var layer = new Layer(path, { //新建一个layer,layer上挂载了error_handler和request_handler
  9. sensitive: this.caseSensitive,
  10. strict: false,
  11. end: false
  12. }, fn);
  13. layer.route = undefined;
  14. this.stack.push(layer); //route自身会维护一个stack,将每个新建的layer都推入stack当中,这个layer实例最终会对匹配的path,作出error_handle或者request_handle。
  15. }
复制代码

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

  1. proto.route = function route(path) {
  2. var route = new Route(path); //新建一个route,这个Route构建函数内部实现见./route.js,它里面提供了一个空的stack,用以
  3. var layer = new Layer(path, { //新建一个layer,layer的作用见下面的讲解
  4. sensitive: this.caseSensitive,
  5. strict: this.strict,
  6. end: true
  7. }, route.dispatch.bind(route));
  8. layer.route = route;
  9. this.stack.push(layer); //新建一个route,这个route会维护自身的stack
  10. return route;
  11. };
复制代码

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

使用的方法:

  1. var express = require('express');
  2. var app = express();
  3. var router = express.Router();
  4. //没有挂载任何路径的中间件,通过该路由的每个请求都会执行该中间件
  5. router.use(function(req, res, next) {
  6. console.log('route.use');
  7. })
  8. router.get('/test', function(req, res, next) {
  9. console.log('route.get');
  10. });
  11. //最后需要将这个router挂载到应用
  12. app.use('/', router);
复制代码

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

通过app.use()挂载的中间件最终都代理到了router.use()方法下

router.use()方法,新建一个layer,layer上保存了路径,默认为'/',及相应的处理方法,并存入这个app维护的stack中。

通过var router = require('express').Router()新建的router路径级实例,同样可以挂载不同的中间件,不过最后需要将这个router路由注入到app应用当中:app.use('/', router);

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

  1. //将req, res分发给这个route
  2. Route.prototype.dispatch = function dispatch(req, res, done) {
  3. var idx = 0;
  4. var stack = this.stack;
  5. if (stack.length === 0) {
  6. return done();
  7. }
  8. var method = req.method.toLowerCase();
  9. if (method === 'head' && !this.methods['head']) {
  10. method = 'get';
  11. }
  12. req.route = this;
  13. next();
  14. function next(err) {
  15. if (err && err === 'route') {
  16. return done();
  17. }
  18. var layer = stack[idx++];
  19. if (!layer) {
  20. return done(err);
  21. }
  22. if (layer.method && layer.method !== method) { //匹配传入的req请求方式,和layer的method进行对比
  23. return next(err);
  24. }
  25. //调用layer.handle,用以错误处理或者request处理
  26. if (err) {
  27. layer.handle_error(err, req, res, next);
  28. } else {
  29. layer.handle_request(req, res, next);
  30. }
  31. }
  32. };
复制代码

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

0 0
原创粉丝点击