亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

express中間件加載機(jī)制示例詳解

 更新時(shí)間:2022年08月18日 09:15:18   作者:一只螞蚱  
中間件是一種方法,可以接收客戶端發(fā)來(lái)的請(qǐng)求,可以對(duì)請(qǐng)求做出響應(yīng),也可以將請(qǐng)求繼續(xù)交給下一個(gè)中間件繼續(xù)處理,下面這篇文章主要給大家介紹了關(guān)于express中間件加載機(jī)制的相關(guān)資料,需要的朋友可以參考下

前言

作為node web 框架的鼻祖,express和koa 是每個(gè)寫node的同學(xué)都會(huì)使用的兩個(gè)框架,那么兩個(gè)框架在中間件的加載機(jī)制上有什么區(qū)別?koa的洋蔥模型到底是什么?midway在這兩個(gè)框架之上又做了怎么樣的封裝?本文將帶走進(jìn)這個(gè)一點(diǎn)兒都不神奇的世界~

express 中間件加載

眾所周知,express定義中間件的時(shí)候,使用use方法即可,那么use方法到底做了些什么呢?讓筆者帶你來(lái)扒一扒源碼。 github.com/expressjs/e… 由于原始代碼較長(zhǎng),這里小編就拆開(kāi)分解來(lái)解讀。

  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 a middleware function')
  }

這部分對(duì)應(yīng)源碼的195-218行,主要是獲取需要執(zhí)行的function,以及區(qū)分,傳入的是中間件,還是路由。 通過(guò)源碼可知,用戶在傳入的第一個(gè)參數(shù),如果不是function,則會(huì)判斷是不是數(shù)組,如果是數(shù)組的情況下,就會(huì)判斷數(shù)組的第0項(xiàng)是不是function,這部分邏輯是做什么呢? 這部分是對(duì)入?yún)⒌募嫒?,因?yàn)閑xpress的入?yún)⒖梢杂卸喾N形式,如下:

app.use('/users', usersRouter);
app.use([function (req, res, next) {
  console.log('middleware 1....');
  next();
}, function (req, res, next) {
  console.log('middleWare 2....');
  next();
}])
// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next();
  next(createError(404));
});

用戶可以傳入多中間件,也可以傳入單中間件,以及傳入路由。這部分代碼就是對(duì)這幾種情況的區(qū)分,明確之后用戶傳入的內(nèi)容到底是什么,然后再對(duì)其進(jìn)行針對(duì)性的處理。

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

這一部分是路由的準(zhǔn)備工作,由于use方法允許用戶創(chuàng)建路由,則需要在對(duì)其進(jìn)行處理之前,先初始化路由。這部分暫時(shí)不詳細(xì)展開(kāi)說(shuō),待有緣再進(jìn)行詳細(xì)講解。

接下來(lái)就是中間件的的詳細(xì)處理邏輯

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

這里第一個(gè)if中的判斷就很有意思,如果fn不存在,或者不存在fn.handle, 或者不存在fn.set,那么就會(huì)直接return router.use(path, fn);

那么什么情況下會(huì)發(fā)生這種情況呢?好像我們上邊寫的中間件,路由都滿足這種情況,難不成中間件就是路由?而實(shí)際執(zhí)行debug的時(shí)候,也確實(shí)發(fā)現(xiàn),所有的我們定義的中間件,都走了return router.use(path, fn);這個(gè)方法,很神奇。

而什么情況下會(huì)走到下邊的方法呢?

當(dāng)傳入的function具有handle和set方法時(shí),則會(huì)認(rèn)為執(zhí)行下邊的方法,同樣也是執(zhí)行router.use();

事已至此,如果不了解router到底做了什么,是不可能弄明白中間件加載機(jī)制了,好吧,那么我們就順藤摸瓜,前來(lái)看看router模塊都做了些什么事情吧。

書(shū)接上文,app.lazyrouter 是對(duì)路由進(jìn)行初始化,詳細(xì)代碼如下

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

壽險(xiǎn)判斷 _router是否存在,防止重復(fù)創(chuàng)建。

接下來(lái)就是router定義的詳細(xì)邏輯

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

  function router(req, res, next) {
    router.handle(req, res, next);
  }

  // mixin Router class functions
  setPrototypeOf(router, proto)

  router.params = {};
  router._params = [];
  router.caseSensitive = opts.caseSensitive;
  router.mergeParams = opts.mergeParams;
  router.strict = opts.strict;
  router.stack = [];

  return router;
};

初始化router對(duì)象,并且對(duì)其進(jìn)行初始化賦值。針對(duì)上文中使用的router.use,我們來(lái)看看其具體都做了什么吧。
由于use方法較長(zhǎng),我們也是拆分開(kāi)來(lái)進(jìn)行探索。

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

這部分代碼與app.use代碼基本上是一致的,只是最后一個(gè)函數(shù)改了名字。這里就不再進(jìn)行詳細(xì)贅述。

接下來(lái)就是重中之重了

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

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

這部分代碼對(duì)于傳入的函數(shù)進(jìn)行了遍歷,然后對(duì)每一個(gè)function都新建了一個(gè)layer層。然后將layer放入了棧中,如果不出意外在真正調(diào)用的時(shí)候,將會(huì)執(zhí)行遍歷這個(gè)棧中的所有l(wèi)ayer,然后對(duì)其進(jìn)行遍歷執(zhí)行。

function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  debug('new %o', path)
  var opts = options || {};

  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // set fast path flags
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

layer代碼相對(duì)簡(jiǎn)單,定義了handle和regexp,并且設(shè)置了兩個(gè)快速檢索的flag。

那么真正調(diào)用的時(shí)候真的如我們想象的那樣嗎?真正的url請(qǐng)求來(lái)了以后express是如何處理的呢?

express在處理請(qǐng)求時(shí),壽險(xiǎn)調(diào)用的是express app 的handle方法,該方法比較簡(jiǎn)單,核心邏輯是調(diào)用router.handle(req, res, done)方法??,接下來(lái)我們就一起扒一扒route的handle方法吧~這段二百行的代碼,究竟做了些什么?好吧,代碼行確實(shí)太多了,相信你也不愿因看我的流水賬,接下來(lái)我就將代碼進(jìn)行一下歸納吧

proto.handle = function handle(req, res, out) {
  var self = this;
  var idx = 0;
  // middleware and routes
  var stack = self.stack;
  req.next = next;
  next();
  function next(err) {
    var layer;
    var match;
    var route;
    // 找到match的layer
    while (match !== true && idx < stack.length) {
      layer = stack[idx++];
      match = matchLayer(layer, path);
      route = layer.route;
      if (match !== true) {
        continue;
      }
      if (!route) {
        // process non-route handlers normally
        continue;
      }
    }
    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (err) {
        return next(layerError || err);
      }

      if (route) {
        // 執(zhí)行l(wèi)ayer的handle_request方法,其實(shí)就是中間件傳入的函數(shù)
        return layer.handle_request(req, res, next);
      }

      trim_prefix(layer, layerError, layerPath, path);
    });

這個(gè)方法的原理其實(shí)很簡(jiǎn)單,初始化idx=0,然后while循環(huán)找到第一個(gè)match的方法,就是我們定義的中間件/路由,然后執(zhí)行相對(duì)應(yīng)的function。

matchLayer方法中用到了layer初始化的時(shí)候定義的this.regexp.fast_slash變量

    if (this.regexp.fast_slash) {
      this.params = {}
      this.path = ''
      return true
    }
    --------------------
    this.regexp.fast_slash = path === '/' && opts.end === false

通過(guò)這個(gè)代碼以及fast_flash定義,以及上邊path的定義我們可以知道,我們初始化的中間件,全部都是以var path = '/';的方式存儲(chǔ)的,layer初始化時(shí)傳入的end=false, 所以中間件的 this.regexp.fast_slash = true,即所有的中間件在所有的路由下都會(huì)執(zhí)行。

按照這個(gè)執(zhí)行邏輯,如果我們自定義一個(gè)path='/'的路由,是不是也都會(huì)執(zhí)行呢?以及如果出現(xiàn)兩個(gè)相同名字的路由,會(huì)怎么處理呢?按照這個(gè)推論,我測(cè)試了如下代碼

app.use('/', function (req, res, next) {
  console.log('hello world');
  next()
});
app.use('/users', function (req, res, next) {
  console.log('/users-------');
  next();
});
//hello world
// /users-------
//GET /users 304 1.280 ms - -

試驗(yàn)結(jié)果與我們的推論一致。

然而,當(dāng)我在測(cè)試的時(shí)候,發(fā)現(xiàn)中間件寫的位置也會(huì)有影響,寫在router之后的中間件就不會(huì)被執(zhí)行到,這個(gè)是什么原因呢? 通過(guò)看源碼發(fā)現(xiàn),在路由處理時(shí),執(zhí)行了res.send ,之后并未執(zhí)行next()命令,導(dǎo)致其之后的代碼并未執(zhí)行。

總結(jié): express使用use方法加載中間件,中間件和路由以layer的形式保存到stack中,待真正需要使用的時(shí)候,再對(duì)其進(jìn)行遍歷,找到真正需要用到的中間件和路由。我們還可以通過(guò)路由的加載順序,攔截路由。

好吧,硬肝了兩天,終于把express的中間件加載機(jī)制給肝完了,邏輯層層深入,柳暗花明,其中還有很多地方值得深思,比如app.use方法那里傳入的到底還能是什么呢?留給有興趣的讀者深入研究吧。

總結(jié)

到此這篇關(guān)于express中間件加載機(jī)制的文章就介紹到這了,更多相關(guān)express中間件加載內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Node.js全局可用變量、函數(shù)和對(duì)象示例詳解

    Node.js全局可用變量、函數(shù)和對(duì)象示例詳解

    JavaScript中有一個(gè)特殊的對(duì)象,稱為全局對(duì)象(Global Object),它及其所有屬性都可以在程序的任何地方訪問(wèn),即全局變量,下面這篇文章主要給大家介紹了關(guān)于Node.js全局可用變量、函數(shù)和對(duì)象的相關(guān)資料,需要的朋友可以參考下
    2023-03-03
  • 快速掌握Node.js模塊封裝及使用

    快速掌握Node.js模塊封裝及使用

    這篇文章主要為大家詳細(xì)介紹了Node.js模塊封裝及使用,幫助大家快速掌握Node.js模塊封裝及使用,感興趣的小伙伴們可以參考一下
    2016-03-03
  • 使用upstart把nodejs應(yīng)用封裝為系統(tǒng)服務(wù)實(shí)例

    使用upstart把nodejs應(yīng)用封裝為系統(tǒng)服務(wù)實(shí)例

    這篇文章主要介紹了使用upstart把nodejs應(yīng)用封裝為系統(tǒng)服務(wù)實(shí)例,需要的朋友可以參考下
    2014-06-06
  • node.js文件的壓縮解壓?jiǎn)栴}

    node.js文件的壓縮解壓?jiǎn)栴}

    這篇文章主要介紹了node.js文件的壓縮解壓?jiǎn)栴},具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • Node.js中fs模塊的使用方法

    Node.js中fs模塊的使用方法

    這篇文章介紹了Node.js中fs模塊的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • 使用Node.js搭建Web服務(wù)器

    使用Node.js搭建Web服務(wù)器

    這篇文章介紹了使用Node.js搭建Web服務(wù)器的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • NodeJS、NPM安裝配置步驟(windows版本) 以及環(huán)境變量詳解

    NodeJS、NPM安裝配置步驟(windows版本) 以及環(huán)境變量詳解

    本篇文章主要介紹了NodeJS、NPM安裝配置步驟(windows版本) 以及環(huán)境變量詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • node.js包管理工具Yarn使用簡(jiǎn)介

    node.js包管理工具Yarn使用簡(jiǎn)介

    這篇文章介紹了JS包管理工具Yarn的基本用法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • 初識(shí)NodeJS服務(wù)端開(kāi)發(fā)入門(Express+MySQL)

    初識(shí)NodeJS服務(wù)端開(kāi)發(fā)入門(Express+MySQL)

    本篇文章主要介紹了初識(shí)NodeJS服務(wù)端開(kāi)發(fā)入門(Express+MySQL),可以對(duì)數(shù)據(jù)庫(kù)中的一張表進(jìn)行簡(jiǎn)單的CRUD操作,有興趣的可以了解一下。
    2017-04-04
  • Node.js中path.join()優(yōu)勢(shì)例舉分析

    Node.js中path.join()優(yōu)勢(shì)例舉分析

    在本篇文章里小編給大家整理的是一篇關(guān)于Node.js中path.join()優(yōu)勢(shì)例舉分析,有興趣的朋友們可以學(xué)習(xí)下。
    2021-08-08

最新評(píng)論