成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

koa-router 源碼淺析

SillyMonkey / 3112人閱讀

摘要:代碼結(jié)構(gòu)執(zhí)行流程上面兩張圖主要將的整體代碼結(jié)構(gòu)和大概的執(zhí)行流程畫了出來,畫的不夠具體。那下面主要講中的幾處的關(guān)鍵代碼解讀一下。全局的路由參數(shù)處理的中間件組成的對象。

代碼結(jié)構(gòu)

執(zhí)行流程

上面兩張圖主要將koa-router的整體代碼結(jié)構(gòu)和大概的執(zhí)行流程畫了出來,畫的不夠具體。那下面主要講koa-router中的幾處的關(guān)鍵代碼解讀一下。

讀代碼首先要找到入口文件,那幾乎所有的node模塊的入口文件都會在package.json文件中的main屬性指明了。koa-router的入口文件就是lib/router.js

第三方模塊

首先先講幾個第三方的node模塊了解一下,因為后面的代碼講解中會用到,不去看具體實現(xiàn),只要知道其功能就行:
koa-compose:
提供給它一個中間件數(shù)組, 返回一個順序執(zhí)行所有中間件的執(zhí)行函數(shù)。
methods:
node中支持的http動詞,就是http.METHODS,可以在終端輸出看看。
path-to-regexp:
將路徑字符串轉(zhuǎn)換成強大的正則表達(dá)式,還可以輸出路徑參數(shù)。

Router & Layer

RouterLayer 分別是兩個構(gòu)造函數(shù),分別在router.jslayer.js中,koa-router的所有代碼也就在這兩個文件中,可以知道它的代碼量并不是很多。

Router: 創(chuàng)建管理整個路由模塊的實例

function Router(opts) {
  if (!(this instanceof Router)) {
    return new Router(opts);
  }

  this.opts = opts || {};
  this.methods = this.opts.methods || [
    "HEAD",
    "OPTIONS",
    "GET",
    "PUT",
    "PATCH",
    "POST",
    "DELETE"
  ];

  this.params = {};
  this.stack = [];
};

首先是

if (!(this instanceof Router)) {
  return new Router(opts);
}

這是常用的去new的方式,所以我們可以在引入koa-router時:

const router = require("koa-router")()

而不用:

const router = new require("koa-router")() // 這樣也是沒問題的

this.methods:
在后面要講的allowedMethods方法中要用到的,目的是響應(yīng)options請求和請求出錯的處理。

this.params:
全局的路由參數(shù)處理的中間件組成的對象。

this.stack:
其實就是各個路由(Layer)實例組成的數(shù)組。每次處理請求時都需要循環(huán)這個數(shù)組找到匹配的路由。

Layer: 創(chuàng)建各個路由實例

function Layer(path, methods, middleware, opts) {
  ...

  this.stack = Array.isArray(middleware) ? middleware : [middleware];

  // 為給后面的allowedMthods處理
  methods.forEach(function(method) {
    var l = this.methods.push(method.toUpperCase());
    if (this.methods[l-1] === "GET") {
      // 如果是get請求,則支持head請求
      this.methods.unshift("HEAD");
    }
  }, this);

  // 確保路由的每個中間件都是函數(shù)
  this.stack.forEach(function(fn) {
    var type = (typeof fn);
    if (type !== "function") {
      throw new Error(
        methods.toString() + " `" + (this.opts.name || path) +"`: `middleware` "
        + "must be a function, not `" + type + "`"
      );
    }
  }, this);
  this.path = path;
  // 利用path-to-rege模塊生產(chǎn)的路徑的正則表達(dá)式
  this.regexp = pathToRegExp(path, this.paramNames, this.opts);

  ...
};

這里的this.stackRouter中的不同,這里的是路由所有的中間件的數(shù)組。(一個路由可以有多個中間件)

router.register()

作用:注冊路由

從上一篇的代碼結(jié)構(gòu)圖中可以看出,Router的幾個實例方法都直接或簡介地調(diào)用了register方法,可見,它應(yīng)該是比較核心的函數(shù), 代碼不長,我們一行行看一下:

Router.prototype.register = function (path, methods, middleware, opts) {
  opts = opts || {};
  var router = this;

  // 全部路由
  var stack = this.stack;

  // 說明路由的path是支持?jǐn)?shù)組的
  // 如果是數(shù)組的話,需要遞歸調(diào)用register來注冊路由
  // 因為一個path對應(yīng)一個路由
  if (Array.isArray(path)) {
    path.forEach(function (p) {
      router.register.call(router, p, methods, middleware, opts);
    });

    return this;
  }

  // 創(chuàng)建路由,路由就是Layer的實例
  // mthods 是路由處理的http方法
  // 最后一個參數(shù)對象最終是傳給Layer模塊中的path-to-regexp模塊接口調(diào)用的
  var route = new Layer(path, methods, middleware, {
    end: opts.end === false ? opts.end : true,
    name: opts.name,
    sensitive: opts.sensitive || this.opts.sensitive || false,
    strict: opts.strict || this.opts.strict || false,
    prefix: opts.prefix || this.opts.prefix || "",
    ignoreCaptures: opts.ignoreCaptures
  });

  // 處理路徑前綴
  if (this.opts.prefix) {
    route.setPrefix(this.opts.prefix);
  }

  // 將全局的路由參數(shù)添加到每個路由中
  Object.keys(this.params).forEach(function (param) {
    route.param(param, this.params[param]);
  }, this);

  // 往路由數(shù)組中添加新創(chuàng)建的路由
  stack.push(route);

  return route;
};
router.verb()

verb => get|put|post|patch|delete
作用:注冊路由

這是koa-router提供的直接注冊相應(yīng)http方法的路由,但最終還是會調(diào)用register方法如:

router.get("/user", function(ctx, next){...})

和下面利用register方法等價:

router.register("/user", ["get"], [function(ctx, next){...}])

可以看到直接使用router.verb注冊路由會方便很多。來看看代碼:
你會發(fā)現(xiàn)router.js的代碼里并沒有Router.prototype.get的代碼出現(xiàn),原因是它還依賴了上面提到的methods模塊來實現(xiàn)。

// 這里的methods就是上面的methods模塊提供的數(shù)組
methods.forEach(function (method) {
  Router.prototype[method] = function (name, path, middleware) {
    var middleware;

    // 這段代碼做了兩件事:
    // 1.name 參數(shù)是可選的,所以要做一些參數(shù)置換的處理
    // 2.將所有路由中間件合并成一個數(shù)組
    if (typeof path === "string" || path instanceof RegExp) {
      middleware = Array.prototype.slice.call(arguments, 2);
    } else {
      middleware = Array.prototype.slice.call(arguments, 1);
      path = name;
      name = null;
    }

    // 調(diào)用register方法
    this.register(path, [method], middleware, {
      name: name
    });

    return this;
  };
});
router.routes()

作用:啟動路由

這是在koa中配置路由的重要一步:

var router = require("koa-router")();
...
app.use(router.routes())

就這樣,koa-router就啟動了,所以我們也一定會很好奇這個routes函數(shù)到底做了什么,但可以肯定router.routes()返回了一個中間件函數(shù)。
函數(shù)體長了一點,簡化一下看下整體輪廓:

Router.prototype.routes = Router.prototype.middleware = function () {
  var router = this;
  var dispatch = function dispatch(ctx, next) {
    ...
  }
  dispatch.router = this;
  return dispatch;
};

這里形成了一個閉包,在routes函數(shù)內(nèi)部返回了一個dispatch函數(shù)作為中間件。
接下來看下dispatch函數(shù)的實現(xiàn):

var dispatch = function dispatch(ctx, next) {
    var path = router.opts.routerPath || ctx.routerPath || ctx.path;

    // router.match函數(shù)內(nèi)部遍歷所有路由(this.stach),
    // 根據(jù)路徑和請求方法找到對應(yīng)的路由
    // 返回的matched對象為: 
    /* 
      var matched = {
        path: [], // 保存了path匹配的路由數(shù)組
        pathAndMethod: [], // 保存了path和methods都匹配的路由數(shù)組
        route: false // 是否有對應(yīng)的路由
      };
    */
    var matched = router.match(path, ctx.method);
    var layerChain, layer, i;
    if (ctx.matched) {
      ctx.matched.push.apply(ctx.matched, matched.path);
    } else {
      ctx.matched = matched.path;
    }

    // 如果沒有對應(yīng)的路由,則直接進(jìn)入下一個中間件
    if (!matched.route) return next();

    // 找到正確的路由的path
    var mostSpecificPath = matched.pathAndMethod[matched.pathAndMethod.length - 1].path;
    ctx._matchedRoute = mostSpecificPath;

    // 使用reduce方法將路由的所有中間件形成一條鏈
    layerChain = matched.pathAndMethod.reduce(function(memo, layer) {

      // 在每個路由的中間件執(zhí)行之前,根據(jù)參數(shù)不同,設(shè)置 ctx.captures 和 ctx.params
      // 這就是為什么我們可以直接在中間件函數(shù)中直接使用 ctx.params 來讀取路由參數(shù)信息了
      memo.push(function(ctx, next) {

        // 返回路由的參數(shù)的key 
        ctx.captures = layer.captures(path, ctx.captures);

        // 返回參數(shù)的key和對應(yīng)的value組成的對象
        ctx.params = layer.params(path, ctx.captures, ctx.params);

        // 執(zhí)行下一個中間件
        return next();
      });

      // 將上面另外加的中間件和已有的路由中間件合并到一起
      // 所以最終 layerChain 將會是一個中間件的數(shù)組
      return memo.concat(layer.stack);
    }, []);

    // 最后調(diào)用上面提到的 compose 模塊提供的方法,返回將 layerChain (中間件的數(shù)組) 
    // 順序執(zhí)行所有中間件的執(zhí)行函數(shù), 并立即執(zhí)行。
    return compose(layerChain)(ctx, next);
  };
router.allowMethods()

作用: 當(dāng)請求出錯時的處理邏輯

同樣也是koa中配置路由的中一步:

var router = require("koa-router")();
...
app.use(router.routes())
app.use(router.allowMethods())

可以看出,該方法也是閉包內(nèi)返回了中間件函數(shù)。我們將代碼簡化一下:

Router.prototype.allowedMethods = function (options) {
  options = options || {};
  var implemented = this.methods;
  return function allowedMethods(ctx, next) {
    return next().then(function() {
      var allowed = {};

      if (!ctx.status || ctx.status === 404) {
        ...

        if (!~implemented.indexOf(ctx.method)) {
          if (options.throw) {
            ...
          } else {
            ctx.status = 501;
            ctx.set("Allow", allowedArr);
          }
        } else if (allowedArr.length) {
          if (ctx.method === "OPTIONS") {
            ctx.status = 204;
            ctx.set("Allow", allowedArr);
          } else if (!allowed[ctx.method]) {
            if (options.throw) {
              ...
            } else {
              ctx.status = 405;
              ctx.set("Allow", allowedArr);
            }
          }
        }
      }
    });
  };
};

眼尖的同學(xué)可能會看到一些http code404, 501, 204, 405
那這個函數(shù)其實就是當(dāng)所有中間件函數(shù)執(zhí)行完了,并且請求出錯了進(jìn)行相應(yīng)的處理:

如果請求的方法koa-router不支持并且沒有設(shè)置throw選項,則返回 501(未實現(xiàn))

如果是options請求,則返回 204(無內(nèi)容)

如果請求的方法支持但沒有設(shè)置throw選項,則返回 405(不允許此方法 )

總結(jié)

粗略淺析了這么些,能大概知道了koa-router的工作原理。筆者能力有限,有錯誤還請指出。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/80893.html

相關(guān)文章

  • generator探幽(1)--koa中間件機制淺析

    摘要:當(dāng)運行到時,不會暫停,而是直接跳進(jìn)函數(shù)執(zhí)行函數(shù)內(nèi)的代碼。由于函數(shù)中沒有,因此會一直執(zhí)行完函數(shù)中的代碼,并返回至函數(shù)中執(zhí)行后面的代碼。 本系列旨在通過對co,koa等庫源碼的研究,進(jìn)而理解generator在異步編程中的重大作用(ps:所有代碼請在node --harmony或者iojs環(huán)境中運行) koa中間件的形式 相信用過koa的小伙伴一定很熟悉下面這段代碼 var app ...

    Jensen 評論0 收藏0
  • 教你從寫一個迷你koa-router到閱讀koa-router源碼

    摘要:本打算教一步步實現(xiàn),因為要解釋的太多了,所以先簡化成版本,從實現(xiàn)部分功能到閱讀源碼,希望能讓你好理解一些。 本打算教一步步實現(xiàn)koa-router,因為要解釋的太多了,所以先簡化成mini版本,從實現(xiàn)部分功能到閱讀源碼,希望能讓你好理解一些。希望你之前有讀過koa源碼,沒有的話,給你鏈接 最核心需求-路由匹配 router最重要的就是路由匹配,我們就從最核心的入手 router.get...

    yzzz 評論0 收藏0
  • Koa-router 優(yōu)先級問題

    摘要:問題描述在使用作為路由遇到了一個優(yōu)先級問題如下代碼在訪問時路由會優(yōu)先匹配到路由返回這個問題就很尷尬了項目空閑下來去翻看源碼終于找到了原因問題原因的源碼并不長和兩個文件加起來共一千多行代碼建議可以結(jié)合這篇文章閱讀其中造成這個問題的原因 問題描述 在使用Koa-router作為路由遇到了一個優(yōu)先級問題.如下代碼 // routerPage.js file const router = re...

    Paul_King 評論0 收藏0
  • koa源碼閱讀[2]-koa-router

    摘要:第三篇,有關(guān)生態(tài)中比較重要的一個中間件第一篇源碼閱讀第二篇源碼閱讀與是什么首先,因為是一個管理中間件的平臺,而注冊一個中間件使用來執(zhí)行。這里寫入的多個中間件都是針對該生效的。 第三篇,有關(guān)koa生態(tài)中比較重要的一個中間件:koa-router 第一篇:koa源碼閱讀-0 第二篇:koa源碼閱讀-1-koa與koa-compose koa-router是什么 首先,因為koa是一個管...

    oneasp 評論0 收藏0
  • 玩轉(zhuǎn)Koa -- koa-router原理解析

    摘要:四路由注冊構(gòu)造函數(shù)首先看了解一下構(gòu)造函數(shù)限制必須采用關(guān)鍵字服務(wù)器支持的請求方法,后續(xù)方法會用到保存前置處理函數(shù)存儲在構(gòu)造函數(shù)中初始化的和屬性最為重要,前者用來保存前置處理函數(shù),后者用來保存實例化的對象。 一、前言 ??Koa為了保持自身的簡潔,并沒有捆綁中間件。但是在實際的開發(fā)中,我們需要和形形色色的中間件打交道,本文將要分析的是經(jīng)常用到的路由中間件 -- koa-router。 ??...

    wthee 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<