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

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

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

前言

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

express 中間件加載

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

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

這部分對應(yīng)源碼的195-218行,主要是獲取需要執(zhí)行的function,以及區(qū)分,傳入的是中間件,還是路由。 通過源碼可知,用戶在傳入的第一個參數(shù),如果不是function,則會判斷是不是數(shù)組,如果是數(shù)組的情況下,就會判斷數(shù)組的第0項是不是function,這部分邏輯是做什么呢? 這部分是對入?yún)⒌募嫒?,因為express的入?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));
});

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

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

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

接下來就是中間件的的詳細(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);

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

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

而什么情況下會走到下邊的方法呢?

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

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

書接上文,app.lazyrouter 是對路由進(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));
  }
};

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

接下來就是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對象,并且對其進(jìn)行初始化賦值。針對上文中使用的router.use,我們來看看其具體都做了什么吧。
由于use方法較長,我們也是拆分開來進(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代碼基本上是一致的,只是最后一個函數(shù)改了名字。這里就不再進(jìn)行詳細(xì)贅述。

接下來就是重中之重了

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

這部分代碼對于傳入的函數(shù)進(jìn)行了遍歷,然后對每一個function都新建了一個layer層。然后將layer放入了棧中,如果不出意外在真正調(diào)用的時候,將會執(zhí)行遍歷這個棧中的所有l(wèi)ayer,然后對其進(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代碼相對簡單,定義了handle和regexp,并且設(shè)置了兩個快速檢索的flag。

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

express在處理請求時,壽險調(diào)用的是express app 的handle方法,該方法比較簡單,核心邏輯是調(diào)用router.handle(req, res, done)方法??,接下來我們就一起扒一扒route的handle方法吧~這段二百行的代碼,究竟做了些什么?好吧,代碼行確實太多了,相信你也不愿因看我的流水賬,接下來我就將代碼進(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ù)
        return layer.handle_request(req, res, next);
      }

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

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

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

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

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

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

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

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

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

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

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

總結(jié)

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

相關(guān)文章

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

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

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

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

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

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

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

    node.js文件的壓縮解壓問題

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

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

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

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

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

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

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

    node.js包管理工具Yarn使用簡介

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

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

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

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

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

最新評論