博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从源码上理解express中间件
阅读量:6433 次
发布时间:2019-06-23

本文共 11424 字,大约阅读时间需要 38 分钟。

本篇文章从express源码上来理解中间件,同时可以对express有更深层的理解

前言

中间件函数可以执行哪些任务?

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求/响应循环。
  • 调用堆栈中的下一个中间件函数。

我们从一个app.use开始,逐步分析到下一个中间件函数的执行。

初始化服务器

首先从上下载express源码。

建立一个文件test.js文件,引入根目录的index.js文件,实例化express,启动服务器。

let express = require('../index.js');let app = express()function middlewareA(req, res, next) {  console.log('A1');  next();  console.log('A2');}function middlewareB(req, res, next) {  console.log('B1');  next();  console.log('B2');}function middlewareC(req, res, next) {  console.log('C1');  next();  console.log('C2');}app.use(middlewareA);app.use(middlewareB);app.use(middlewareC);app.listen(8888, () => {  console.log("服务器已经启动访问http://127.0.0.1:8888");})复制代码

启动服务器,通过访问http://127.0.0.1:8888服务,打开终端,看看终端日志执行顺序。

从日志我们可以看出,每次next()之后,都会按照顺序依次调用下中间件函数,然后按照执行顺序依次打印A1,B1,C1,此时中间件已经调用完成,再依次打印C2,B2,A2

目录结构

--lib    |__ middleware        |__ init.js        |__ query.js    |__ router        |__ index.js        |__ layer.js        |__ route.js    |__ application.js    |__ express.js    |__ request.js    |__ response.js    |__ utils.js    |__ view.js复制代码

通过实例化的express,我们可以看到,index.js文件实际上是暴露出lib/express的文件。

实例化express

express,通过mixin继承appLication,同时初始化application。

function createApplication() {  var app = function(req, res, next) {    app.handle(req, res, next);  };  mixin(app, EventEmitter.prototype, false);  mixin(app, proto, false);  // expose the prototype that will get set on requests  app.request = Object.create(req, {    app: { configurable: true, enumerable: true, writable: true, value: app }  })  // expose the prototype that will get set on responses  app.response = Object.create(res, {    app: { configurable: true, enumerable: true, writable: true, value: app }  })  app.init();  return app;}复制代码

而mixin是merge-descriptorsnpm模块。Merge objects using descriptors.

打开application.js文件,发现express的实例化源自var app = exports = module.exports = {}

进一步搜索app.use,找到app.use,而app.use又只是向应用程序路由器添加中间件的Proxy

/** * Proxy `Router#use()` to add middleware to the app router. * See Router#use() documentation for details. * * If the _fn_ parameter is an express app, then it will be * mounted at the _route_ specified. * * @public */app.use = function use(fn) {  var offset = 0;  var path = '/';  // 默认path 为 '/'  // app.use([fn])  //判断app.use传进来的是否是函数  if (typeof fn !== 'function') {    var arg = fn;    while (Array.isArray(arg) && arg.length !== 0) {      arg = arg[0];    }    // 第一个参数是路径    //取出第一个参数,将第一个参数赋值给path。    if (typeof arg !== 'function') {      offset = 1;      path = fn;    }  }  //slice.call(arguments,offset),通过slice转为数据,slice可以改变具有length的类数组。  //arguments是一个类数组对象。    //处理多种中间件使用方式。  // app.use(r1, r2);  // app.use('/', [r1, r2]);  // app.use(mw1, [mw2, r1, r2], subApp);    var fns = flatten(slice.call(arguments, offset));//[funtion]  //抛出错误  if (fns.length === 0) {    throw new TypeError('app.use() requires a middleware function')  }  //设置router  this.lazyrouter();  var router = this._router;  fns.forEach(function (fn) {    // 处理不是express的APP应用的情况,直接调用route.use。    if (!fn || !fn.handle || !fn.set) {    //path default to '/'      return router.use(path, fn);    }        debug('.use app under %s', path);    fn.mountpath = path;    fn.parent = this;    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);      });    });    // app mounted 触发emit    fn.emit('mount', this);  }, this);  return this;};复制代码

定义默认参数offerpath。然后处理fn形参不同类型的情况。将不同类型的中间件使用方式的形参转为扁平化数组,赋值给fns

forEach遍历fns,判断如果fnfn.handlefn.set参数不存在,return出去router.use(path, fn)

否则继续执行router.use

调用handle函数,执行中间件。

代码如下:

/***将一个req, res对分派到应用程序中。中间件执行开始。*如果没有提供回调,则默认错误处理程序将作出响应*在堆栈中冒泡出现错误时。*/app.handle = function handle(req, res, callback) {  var router = this._router;  // 最后报错处理error。  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);};复制代码

惰性添加Router。

从上述代码中可以知道,app.use的作用实际上是将各种应用函数传递给router的一个中间层代理。

而且,在app.use中有调用this.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')));    //初始化router    this._router.use(middleware.init(this));  }};复制代码

这里对Router进行了实例化,同时设置基本的optioncaseSensitive 是否区分大小写,strict 是否设置严格模式。

Router初始化如下:

/** * 用给定的“选项”初始化一个新的“路由器”。 *  * @param {Object} [options] [{ caseSensitive: false, strict: false }] * @return {Router} which is an callable function * @public */var proto = module.exports = function(options) {  var opts = options || {};  function router(req, res, next) {    router.handle(req, res, next);  }  // 混合路由器类函数  setPrototypeOf(router, proto)  router.params = {};  router._params = [];  router.caseSensitive = opts.caseSensitive;  router.mergeParams = opts.mergeParams;  router.strict = opts.strict;  router.stack = [];  return router;};复制代码

调用app.use时,参数都会传递给router.use,因此,打开router/index.js文件,查找router.use

/***使用给定的中间件函数,具有可选路径,默认为“/”。* Use(如' .all ')将用于任何http方法,但不会添加*这些方法的处理程序,所以选项请求不会考虑“。use”*函数,即使它们可以响应。*另一个区别是_route_ path被剥离,不可见*到处理程序函数。这个特性的主要作用是安装*无论“前缀”是什么,处理程序都可以在不更改任何代码的情况下操作*路径名。 * * @public */proto.use = function use(fn) {  var offset = 0;  var path = '/';  // 默认路径  '/'  // 消除歧义 router.use([fn])  // 判断是否是函数  if (typeof fn !== 'function') {    var arg = fn;    while (Array.isArray(arg) && arg.length !== 0) {      arg = arg[0];    }    // 第一个参数是函数    if (typeof arg !== 'function') {      offset = 1;      path = fn;    }  }  //将arguments转为数组,然后扁平化多维数组  var callbacks = flatten(slice.call(arguments, offset));  //如果callbacks内没有传递函数,抛错  if (callbacks.length === 0) {    throw new TypeError('Router.use() requires a middleware function')  }  //循环callbacks数组  for (var i = 0; i < callbacks.length; i++) {    var fn = callbacks[i];        if (typeof fn !== 'function') {      throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))    }    //解析下query和expressInit的含义    // 添加中间件    //匿名 anonymous 函数    debug('use %o %s', path, fn.name || '
') var layer = new Layer(path, { sensitive: this.caseSensitive, //敏感区分大小写 //默认为false strict: false, //严格 end: false //结束 }, fn); layer.route = undefined; this.stack.push(layer); } return this;}复制代码

router.use的主要作用就是将从app.use中传递过来的函数,通过Layer实例化的处理,添加一些处理错误处理请求的方法,以便后续调用处理。同时将传递过来的path,通过path-to-regexp模块把路径转为正则表达式(this.regexp),调用this.regexp.exec(path),将参数提取出来。

Layer代码较多,这里不贴代码了,可以参考。

处理中间件。

处理中间件就是将放入this,stacknew Layout([options],fn),拿出来依次执行。

proto.handle = function handle(req, res, out) {  var self = this;  debug('dispatching %s %s', req.method, req.url);  var idx = 0;  //获取协议与URL地址  var protohost = getProtohost(req.url) || ''  var removed = '';  //是否添加斜杠  var slashAdded = false;  var paramcalled = {};  //存储选项请求的选项  //仅在选项请求时使用  var options = [];  // 中间件和路由  var stack = self.stack;  // 管理inter-router变量  //req.params 请求参数  var parentParams = req.params;  var parentUrl = req.baseUrl || '';  var done = restore(out, req, 'baseUrl', 'next', 'params');  // 设置下一层  req.next = next;  // 对于选项请求,如果没有其他响应,则使用默认响应  if (req.method === 'OPTIONS') {    done = wrap(done, function(old, err) {      if (err || options.length === 0) return old(err);      sendOptionsResponse(res, options, old);    });  }  // 设置基本的req值  req.baseUrl = parentUrl;  req.originalUrl = req.originalUrl || req.url;  next();  function next(err) {    var layerError = err === 'route'      ? null      : err;    //是否添加斜线 默认false    if (slashAdded) {      req.url = req.url.substr(1);      slashAdded = false;    }    // 恢复改变req.url    if (removed.length !== 0) {      req.baseUrl = parentUrl;      req.url = protohost + removed + req.url.substr(protohost.length);      removed = '';    }    // 出口路由器信号    if (layerError === 'router') {      setImmediate(done, null)      return    }    // 不再匹配图层    if (idx >= stack.length) {      setImmediate(done, layerError);      return;    }    // 获取路径pathname    var path = getPathname(req);    if (path == null) {      return done(layerError);    }    // 找到下一个匹配层    var layer;    var match;    var route;    while (match !== true && idx < stack.length) {      layer = stack[idx++];      //try layer.match(path) catch err      //搜索 path matchLayer有两种状态一种是boolean,一种是string。      match = matchLayer(layer, path);      route = layer.route;      if (typeof match !== 'boolean') {        layerError = layerError || match;      }      if (match !== true) {        continue;      }      if (!route) {        //正常处理非路由处理程序        continue;      }      if (layerError) {        // routes do not match with a pending error        match = false;        continue;      }      var method = req.method;      var has_method = route._handles_method(method);      // build up automatic options response      if (!has_method && method === 'OPTIONS') {        appendMethods(options, route._options());      }      // don't even bother matching route      if (!has_method && method !== 'HEAD') {        match = false;        continue;      }    }    // no match    if (match !== true) {      return done(layerError);    }    //重新赋值router。    if (route) {      req.route = route;    }    // 合并参数    req.params = self.mergeParams      ? mergeParams(layer.params, parentParams)      : layer.params;          var layerPath = layer.path;    // 处理参数    self.process_params(layer, paramcalled, req, res, function (err) {      if (err) {        return next(layerError || err);      }      if (route) {        return layer.handle_request(req, res, next);      }    // 处理req.url和layerPath,同时对layer中的请求error和handle_error加tryCatch处理。      trim_prefix(layer, layerError, layerPath, path);    });  }复制代码

执行proto.handle中间件也就的while循环中的一些核心代码,每次调用app.use中的回调函数中的next()都会让idx加一,将stack[idx++];赋值给layer,调用一开始说到的layer.handle_request,然后调用trim_prefix(layer, layerError, layerPath, path),添加一些报错处理。

trim_prefix函数如下:

function trim_prefix(layer, layerError, layerPath, path) {    if (layerPath.length !== 0) {      // Validate path breaks on a path separator      var c = path[layerPath.length]      if (c && c !== '/' && c !== '.') return next(layerError)      // //删除url中与路由匹配的部分      // middleware (.use stuff) needs to have the path stripped      debug('trim prefix (%s) from url %s', layerPath, req.url);      removed = layerPath;      req.url = protohost + req.url.substr(protohost.length + removed.length);      // Ensure leading slash      if (!protohost && req.url[0] !== '/') {        req.url = '/' + req.url;        slashAdded = true;      }      // 设置 base URL (no trailing slash)      req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'        ? removed.substring(0, removed.length - 1)        : removed);    }    debug('%s %s : %s', layer.name, layerPath, req.originalUrl);    if (layerError) {      layer.handle_error(layerError, req, res, next);    } else {      layer.handle_request(req, res, next);    }  }};复制代码

总结

以上就是通过app.use调用之后,一步步执行中间件函数router.handle

next核心代码很简单,但是需要考虑的场景却是很多,通过这次源码阅读,更能进一步的理解express的核心功能。

虽说平常做项目用到express框架很少,或者可以说基本不用,一般都是用Koa或者Egg,可以说基本上些规模的场景的项目用的都是Egg

但是不可否认得是,express框架还是一款非常经典的框架。

以上代码纯属个人理解,如有不合适的地方,望在评论区留言。

转载地址:http://vmxga.baihongyu.com/

你可能感兴趣的文章
OPTIMIZE TABLE
查看>>
flask框架+pygal+sqlit3搭建图形化业务数据分析平台
查看>>
shell实战训练营Day20
查看>>
jQuery 之 TAB切换菜单
查看>>
mysql 数据库集群搭建:(二)3台CentOS-7安装Percona-XtraDB-Cluster-57集群
查看>>
Jenkins实战演练之Windows系统节点管理
查看>>
MySQL高可用架构之MHA
查看>>
1.8 nginx域名跳转
查看>>
PHP面向对象之接口编程
查看>>
使用 Docker Compose 管理多个容器实例
查看>>
ThinkPHP 删除数据记录 delete 方法
查看>>
Gradle学习笔记(二)--创建Java项目
查看>>
IntelliJ IDEA 快捷键
查看>>
qury-easyui DataGrid 整合struts2增删查该入门实例(三)
查看>>
if a point is inside a square with mathematics
查看>>
Ubuntu(Linux)使用Eclipse搭建C/C++编译环境
查看>>
skyline无插件web的数据加载解析
查看>>
python基础学习第一天
查看>>
硬盘存储双寡头之争 希捷重注中国市场或赢大丰收
查看>>
编译安装PHP
查看>>