最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2022年08月18日 09:15:18   作者:一只螞蚱  
中間件是一種方法,可以接收客戶端發(fā)來的請(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方法到底做了些什么呢?讓筆者帶你來扒一扒源碼。 github.com/expressjs/e… 由于原始代碼較長(zhǎng),這里小編就拆開分解來解讀。

  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ū)分,傳入的是中間件,還是路由。 通過源碼可知,用戶在傳入的第一個(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ì)展開說,待有緣再進(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);

這里第一個(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ī)制了,好吧,那么我們就順藤摸瓜,前來看看router模塊都做了些什么事情吧。

書接上文,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)建。

接下來就是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,我們來看看其具體都做了什么吧。
由于use方法較長(zhǎng),我們也是拆分開來進(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ì)贅述。

接下來就是重中之重了

  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)求來了以后express是如何處理的呢?

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

通過這個(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è)是什么原因呢? 通過看源碼發(fā)現(xiàn),在路由處理時(shí),執(zhí)行了res.send ,之后并未執(zhí)行next()命令,導(dǎo)致其之后的代碼并未執(zhí)行。

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

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

總結(jié)

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

相關(guān)文章

  • Nest.js散列與加密實(shí)例詳解

    Nest.js散列與加密實(shí)例詳解

    這篇文章主要給大家介紹了關(guān)于Nest.js散列與加密的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • Node.js 中的流Stream模塊簡(jiǎn)介及如何使用流進(jìn)行數(shù)據(jù)處理

    Node.js 中的流Stream模塊簡(jiǎn)介及如何使用流進(jìn)行數(shù)據(jù)處理

    Node.js中的流(Stream)模塊用于高效處理流式數(shù)據(jù),包括可讀流、可寫流、雙邊流和轉(zhuǎn)換流等,通過`fs.createReadStream`和`.pipe`方法可以方便地讀取文件并寫入控制臺(tái)或處理網(wǎng)絡(luò)請(qǐng)求,在實(shí)際開發(fā)中,需要注意錯(cuò)誤處理、資源管理和性能優(yōu)化等問題
    2025-03-03
  • node.js 開發(fā)指南 – Node.js 連接 MySQL 并進(jìn)行數(shù)據(jù)庫操作

    node.js 開發(fā)指南 – Node.js 連接 MySQL 并進(jìn)行數(shù)據(jù)庫操作

    通常在NodeJS開發(fā)中我們經(jīng)常涉及到操作數(shù)據(jù)庫,尤其是 MySQL ,作為應(yīng)用最為廣泛的開源數(shù)據(jù)庫則成為我們的首選,本篇就來介紹下如何通過NodeJS來操作 MySQL 數(shù)據(jù)庫。
    2014-07-07
  • Nodejs中執(zhí)行的shell命令的代碼分享

    Nodejs中執(zhí)行的shell命令的代碼分享

    雖然nodejs運(yùn)行時(shí)提供了和OS交互的諸多API命令,但是有些操作(例如:特定系統(tǒng)信息獲?。┻€是使用shell命令更加方便一些,下面就跟隨小編一起來看看有哪些是宜在nodejs中執(zhí)行的shell代碼吧
    2024-02-02
  • Node.js中的CommonJS模塊化規(guī)范詳解

    Node.js中的CommonJS模塊化規(guī)范詳解

    這篇文章主要介紹了Node.js中的CommonJS模塊化規(guī)范,本文主要介紹了?CommonJS?規(guī)范在?Node?中的簡(jiǎn)單應(yīng)用,主要就是導(dǎo)入和導(dǎo)出模塊,需要的朋友可以參考下
    2023-02-02
  • Nodejs如何解決跨域(CORS)

    Nodejs如何解決跨域(CORS)

    這篇文章主要介紹了Nodejs如何解決跨域(CORS)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • 安裝nodejs和yarn及配置淘寶源過程記錄

    安裝nodejs和yarn及配置淘寶源過程記錄

    這篇文章主要介紹了安裝nodejs和yarn及配置淘寶源過程記錄的相關(guān)資料,需要的朋友可以參考下
    2021-10-10
  • 在Node.js中將SVG圖像轉(zhuǎn)換為PNG,JPEG,TIFF,WEBP和HEIF格式的方法

    在Node.js中將SVG圖像轉(zhuǎn)換為PNG,JPEG,TIFF,WEBP和HEIF格式的方法

    這篇文章主要介紹了在Node.js中將SVG圖像轉(zhuǎn)換為PNG,JPEG,TIFF,WEBP和HEIF格式的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • nodejs遞歸文件夾獲取所有文件路徑實(shí)例

    nodejs遞歸文件夾獲取所有文件路徑實(shí)例

    這篇文章主要為大家介紹了nodejs遞歸文件夾獲取所有文件路徑實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 詳解nodejs微信公眾號(hào)開發(fā)——4.自動(dòng)回復(fù)各種消息

    詳解nodejs微信公眾號(hào)開發(fā)——4.自動(dòng)回復(fù)各種消息

    這篇文章主要介紹了詳解nodejs微信公眾號(hào)開發(fā)——4.自動(dòng)回復(fù)各種消息,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-04-04

最新評(píng)論

潜江市| 弥渡县| 阿拉尔市| 桂平市| 乐亭县| 来安县| 丰镇市| 惠州市| 九龙坡区| 剑阁县| 平湖市| 衡东县| 浮山县| 尼木县| 九龙坡区| 自贡市| 广灵县| 天水市| 泸定县| 大庆市| 渝中区| 姜堰市| 寻甸| 通许县| 磴口县| 曲麻莱县| 自治县| 务川| 大洼县| 普宁市| 六盘水市| 鸡东县| 蓬莱市| 新密市| 庆阳市| 烟台市| 新蔡县| 富平县| 泰来县| 宁强县| 弥勒县|