摘要:代碼結構執行流程上面兩張圖主要將的整體代碼結構和大概的執行流程畫了出來,畫的不夠具體。那下面主要講中的幾處的關鍵代碼解讀一下。全局的路由參數處理的中間件組成的對象。
代碼結構 執行流程
上面兩張圖主要將koa-router的整體代碼結構和大概的執行流程畫了出來,畫的不夠具體。那下面主要講koa-router中的幾處的關鍵代碼解讀一下。
讀代碼首先要找到入口文件,那幾乎所有的node模塊的入口文件都會在package.json文件中的main屬性指明了。koa-router的入口文件就是lib/router.js。
第三方模塊首先先講幾個第三方的node模塊了解一下,因為后面的代碼講解中會用到,不去看具體實現,只要知道其功能就行:
koa-compose:
提供給它一個中間件數組, 返回一個順序執行所有中間件的執行函數。
methods:
node中支持的http動詞,就是http.METHODS,可以在終端輸出看看。
path-to-regexp:
將路徑字符串轉換成強大的正則表達式,還可以輸出路徑參數。
Router 和 Layer 分別是兩個構造函數,分別在router.js 和 layer.js中,koa-router的所有代碼也就在這兩個文件中,可以知道它的代碼量并不是很多。
Router: 創建管理整個路由模塊的實例
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方法中要用到的,目的是響應options請求和請求出錯的處理。
this.params:
全局的路由參數處理的中間件組成的對象。
this.stack:
其實就是各個路由(Layer)實例組成的數組。每次處理請求時都需要循環這個數組找到匹配的路由。
Layer: 創建各個路由實例
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); // 確保路由的每個中間件都是函數 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模塊生產的路徑的正則表達式 this.regexp = pathToRegExp(path, this.paramNames, this.opts); ... };
這里的this.stack和Router中的不同,這里的是路由所有的中間件的數組。(一個路由可以有多個中間件)
router.register()作用:注冊路由
從上一篇的代碼結構圖中可以看出,Router的幾個實例方法都直接或簡介地調用了register方法,可見,它應該是比較核心的函數, 代碼不長,我們一行行看一下:
Router.prototype.register = function (path, methods, middleware, opts) { opts = opts || {}; var router = this; // 全部路由 var stack = this.stack; // 說明路由的path是支持數組的 // 如果是數組的話,需要遞歸調用register來注冊路由 // 因為一個path對應一個路由 if (Array.isArray(path)) { path.forEach(function (p) { router.register.call(router, p, methods, middleware, opts); }); return this; } // 創建路由,路由就是Layer的實例 // mthods 是路由處理的http方法 // 最后一個參數對象最終是傳給Layer模塊中的path-to-regexp模塊接口調用的 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); } // 將全局的路由參數添加到每個路由中 Object.keys(this.params).forEach(function (param) { route.param(param, this.params[param]); }, this); // 往路由數組中添加新創建的路由 stack.push(route); return route; };router.verb()
verb => get|put|post|patch|delete
作用:注冊路由
這是koa-router提供的直接注冊相應http方法的路由,但最終還是會調用register方法如:
router.get("/user", function(ctx, next){...})
和下面利用register方法等價:
router.register("/user", ["get"], [function(ctx, next){...}])
可以看到直接使用router.verb注冊路由會方便很多。來看看代碼:
你會發現router.js的代碼里并沒有Router.prototype.get的代碼出現,原因是它還依賴了上面提到的methods模塊來實現。
// 這里的methods就是上面的methods模塊提供的數組 methods.forEach(function (method) { Router.prototype[method] = function (name, path, middleware) { var middleware; // 這段代碼做了兩件事: // 1.name 參數是可選的,所以要做一些參數置換的處理 // 2.將所有路由中間件合并成一個數組 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; } // 調用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函數到底做了什么,但可以肯定router.routes()返回了一個中間件函數。
函數體長了一點,簡化一下看下整體輪廓:
Router.prototype.routes = Router.prototype.middleware = function () { var router = this; var dispatch = function dispatch(ctx, next) { ... } dispatch.router = this; return dispatch; };
這里形成了一個閉包,在routes函數內部返回了一個dispatch函數作為中間件。
接下來看下dispatch函數的實現:
var dispatch = function dispatch(ctx, next) { var path = router.opts.routerPath || ctx.routerPath || ctx.path; // router.match函數內部遍歷所有路由(this.stach), // 根據路徑和請求方法找到對應的路由 // 返回的matched對象為: /* var matched = { path: [], // 保存了path匹配的路由數組 pathAndMethod: [], // 保存了path和methods都匹配的路由數組 route: false // 是否有對應的路由 }; */ 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; } // 如果沒有對應的路由,則直接進入下一個中間件 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) { // 在每個路由的中間件執行之前,根據參數不同,設置 ctx.captures 和 ctx.params // 這就是為什么我們可以直接在中間件函數中直接使用 ctx.params 來讀取路由參數信息了 memo.push(function(ctx, next) { // 返回路由的參數的key ctx.captures = layer.captures(path, ctx.captures); // 返回參數的key和對應的value組成的對象 ctx.params = layer.params(path, ctx.captures, ctx.params); // 執行下一個中間件 return next(); }); // 將上面另外加的中間件和已有的路由中間件合并到一起 // 所以最終 layerChain 將會是一個中間件的數組 return memo.concat(layer.stack); }, []); // 最后調用上面提到的 compose 模塊提供的方法,返回將 layerChain (中間件的數組) // 順序執行所有中間件的執行函數, 并立即執行。 return compose(layerChain)(ctx, next); };router.allowMethods()
作用: 當請求出錯時的處理邏輯
同樣也是koa中配置路由的中一步:
var router = require("koa-router")(); ... app.use(router.routes()) app.use(router.allowMethods())
可以看出,該方法也是閉包內返回了中間件函數。我們將代碼簡化一下:
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); } } } } }); }; };
眼尖的同學可能會看到一些http code : 404, 501, 204, 405
那這個函數其實就是當所有中間件函數執行完了,并且請求出錯了進行相應的處理:
如果請求的方法koa-router不支持并且沒有設置throw選項,則返回 501(未實現)
如果是options請求,則返回 204(無內容)
如果請求的方法支持但沒有設置throw選項,則返回 405(不允許此方法 )
總結粗略淺析了這么些,能大概知道了koa-router的工作原理。筆者能力有限,有錯誤還請指出。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80893.html
摘要:當運行到時,不會暫停,而是直接跳進函數執行函數內的代碼。由于函數中沒有,因此會一直執行完函數中的代碼,并返回至函數中執行后面的代碼。 本系列旨在通過對co,koa等庫源碼的研究,進而理解generator在異步編程中的重大作用(ps:所有代碼請在node --harmony或者iojs環境中運行) koa中間件的形式 相信用過koa的小伙伴一定很熟悉下面這段代碼 var app ...
摘要:本打算教一步步實現,因為要解釋的太多了,所以先簡化成版本,從實現部分功能到閱讀源碼,希望能讓你好理解一些。 本打算教一步步實現koa-router,因為要解釋的太多了,所以先簡化成mini版本,從實現部分功能到閱讀源碼,希望能讓你好理解一些。希望你之前有讀過koa源碼,沒有的話,給你鏈接 最核心需求-路由匹配 router最重要的就是路由匹配,我們就從最核心的入手 router.get...
摘要:問題描述在使用作為路由遇到了一個優先級問題如下代碼在訪問時路由會優先匹配到路由返回這個問題就很尷尬了項目空閑下來去翻看源碼終于找到了原因問題原因的源碼并不長和兩個文件加起來共一千多行代碼建議可以結合這篇文章閱讀其中造成這個問題的原因 問題描述 在使用Koa-router作為路由遇到了一個優先級問題.如下代碼 // routerPage.js file const router = re...
摘要:第三篇,有關生態中比較重要的一個中間件第一篇源碼閱讀第二篇源碼閱讀與是什么首先,因為是一個管理中間件的平臺,而注冊一個中間件使用來執行。這里寫入的多個中間件都是針對該生效的。 第三篇,有關koa生態中比較重要的一個中間件:koa-router 第一篇:koa源碼閱讀-0 第二篇:koa源碼閱讀-1-koa與koa-compose koa-router是什么 首先,因為koa是一個管...
摘要:四路由注冊構造函數首先看了解一下構造函數限制必須采用關鍵字服務器支持的請求方法,后續方法會用到保存前置處理函數存儲在構造函數中初始化的和屬性最為重要,前者用來保存前置處理函數,后者用來保存實例化的對象。 一、前言 ??Koa為了保持自身的簡潔,并沒有捆綁中間件。但是在實際的開發中,我們需要和形形色色的中間件打交道,本文將要分析的是經常用到的路由中間件 -- koa-router。 ??...
閱讀 1875·2019-08-29 16:44
閱讀 2178·2019-08-29 16:30
閱讀 786·2019-08-29 15:12
閱讀 3534·2019-08-26 10:48
閱讀 2664·2019-08-23 18:33
閱讀 3784·2019-08-23 17:01
閱讀 1947·2019-08-23 15:54
閱讀 1309·2019-08-23 15:05