koa router實現原理
本文兩個目的
- 瞭解path-to-regexp使用
- koa-router源碼解析
path-to-regexp
path-to-regexp用法簡介。
如何匹配識別路由
想想如果我們要識別路由的話,我們可以怎麼做?
最直觀肯定是正則全匹配
'/string' => '/string'
當路由全匹配 /string 的時候我們可以做出一些反饋操作。例如執行一個callback等。
我們還可以利用正則匹配特性
這樣子匹配模式顯然可操作方式更多元,匹配路徑也更多
例如:
/^\/string\/.*?\/xixi$/
// => '/string/try/xixi'
path-to-regexp就是一種這樣的工具
試想一下如果我們要對路徑解析匹配,我們需要自己再去寫正則表達式。從而達到匹配效果。
可以寫嗎?
肯定可以,可是太費時了。
path-to-regexp 它可以幫助我們簡單地完成這種操作。
簡介path-to-regexp的一些api
how to use it ???
const pathToRegexp = require('path-to-regexp')
// pathToRegexp(path, keys?, options?)
// pathToRegexp.parse(path)
// pathToRegexp.compile(path)
// pathToRegexp(path, keys?, options?)
// path 可以是string/字符串數組/正則表達式
// keys 存放路徑中找到的鍵數組
// options 是一些匹配規則的填充 例如是否爲全匹配 分割符等
// 一個demo
如果我們要實現正常的匹配某些鍵值
eg:
/user/:name
我們實現這樣子的正則如何實現
前部是全匹配,後部用正則分組提取值
eg:
/\/user\/((?!\/).*?)\/?$/.exec('/user/zwkang')
查找匹配正則的字符串 返回一個數組/無值返回一個null
pathToRegexp就是乾的這個活。生成需要的正則表達式匹配。當然裏面還有一些封裝操作,但是本質就是乾的這個。
pathToRegexp('/user/:name').exec('/user/zwkang')
path
option ?
表示可有可無
pathToRegexp('/:foo/:bar?').exec('/test')
pathToRegexp('/:foo/:bar?').exec('/test/route')
* 代表來多少都可以
+ 代表一個或者多個
仔細看你可以發現 這些詞跟正則中的量詞幾乎一致
也可以匹配未命名參數 存儲keys時會根據序列下標存儲
同時也支持正則表達式
parse方法
對path 生成匹配的tokens數組
也就是上文的keys數組
方法適用於string類型
Compile 方法
用compile傳入一個path 返回一個可以填充的函數 生成與path匹配的值
pathToRegexp.compile('/user/:id')({id: 123}) => "/user/123"
適用於字符串
pathToRegexp.tokensToRegExp(tokens, keys?, options?)
pathToRegexp.tokensToFunction(tokens)
名字上可以看出
一個將tokens數組轉化爲正則表達式
一個將tokens數組轉化爲compile方法生成的函數
現在可以捋一捋
pathToRegexp => regexp
parse => path => keys token
compile => path => generator function => value => full path string
koa-router
不知道你是否曾使用過koa-router
notic: 注意koa-router的維護權限變更問題
router實現實際上也是一種基於正則的訪問路徑匹配。
如果是使用koa原生代碼
例子:
匹配路徑/simple 返回一個body爲
寫一些簡單的代碼
假設我們匹配路由 使用一個簡單的中間件匹配ctx.url
app.use(async (ctx, next) => {
const url = ctx.url
if(/^\/simple$/i.test(url)) {
ctx.body = {
name: 'ZWkang'
}
} else {
ctx.body = {
errorCode: 404,
message: 'NOT FOUND'
}
ctx.status = 404
}
return await next()
})
測試代碼
describe('use normal koa path', () => {
it('use error path', (done) => {
request(http.createServer(app.callback()))
.get('/simple/s')
.expect(404)
.end(function (err, res) {
if (err) return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('errorCode', 404)
done();
});
})
it('use right path', (done) => {
request(http.createServer(app.callback()))
.get('/simple')
.expect(200)
.end(function (err, res) {
if (err) return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('name', 'ZWkang')
done();
});
})
})
自己實現url的模式就是這樣
缺點,較爲單一,設定方法較爲簡陋,功能弱小
我們如果使用koa-router的話
// 一個簡單的用法
it('simple use should work', (done) => {
router.get('/simple', (ctx, next) => {
ctx.body = {
path: 'simple'
}
})
app.use(router.routes()).use(router.allowedMethods());
request(http.createServer(app.callback()))
.get('/simple')
.expect(200)
.end(function (err, res) {
if (err) return done(err);
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('path', 'simple');
done();
});
})
讓我們來看看這koa-router到底做了些什麼
前置知識
理解koa運行機制,內部中間件處理模式。
以上面簡單例子我們可以看出
上方測試代碼的一些點解釋
app.callback()
callback是koa的運行機制。方法代表了啥? 代表了其setup的過程
而我們的常用listen方法 實際上也是調用了http.createServer(app.callback()) 這麼一步唯一
從demo 方法入手
router.allowedMethods ===> router.routes ===> router.get
因爲是use調用,那麼我們可以肯定是標準的koa中間件模式
返回的函數類似於
async (ctx, next) => {
// 處理路由邏輯
// 處理業務邏輯
}
源碼的開頭註釋給我們講述了基本的一些用法
我們可以簡單提煉一下
router.verb()
根據http方法指定對應函數
例如router.get().post().put()
.all 方法支持所有http 方法
當路由匹配時,ctx._matchedRoute可以在這裏獲取路徑,如果他是命名路由,這裏可以得到路由名ctx._matchedRouteName
請求匹配的時候不會考慮querystring(?xxxx)
允許使用具名函數
在開發時候可以快速定位路由
* router.get('user', '/users/:id', (ctx, next) => {
* // ...
* });
*
* router.url('user', 3);
* // => "/users/3"
允許多路由使用
* router.get(
* '/users/:id',
* (ctx, next) => {
* return User.findOne(ctx.params.id).then(function(user) {
* ctx.user = user;
* next();
* });
* },
* ctx => {
* console.log(ctx.user);
* // => { id: 17, name: "Alex" }
* }
* );
允許嵌套路由
* var forums = new Router();
* var posts = new Router();
*
* posts.get('/', (ctx, next) => {...});
* posts.get('/:pid', (ctx, next) => {...});
* forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());
*
* // responds to "/forums/123/posts" and "/forums/123/posts/123"
* app.use(forums.routes());
允許路由前綴匹配
var router = new Router({
prefix: '/users'
});
router.get('/', ...); // responds to "/users"
router.get('/:id', ...); // responds to "/users/:id"
捕獲命名的參數添加到ctx.params中
router.get('/:category/:title', (ctx, next) => {
console.log(ctx.params);
// => { category: 'programming', title: 'how-to-node' }
});
代碼整體分析
代碼設計上有些點挺巧妙
- 職責的分離,上層Router做http層method status之類的處理以及routers middlewares相關的處理。低層Layer則關注在路由path的處理上
- middlerware的設計
先從layer文件理解。
layer.js
前面說了,這個文件主要是用來處理對path-to-regexp庫的操作
文件只有300行左右 方法較少,直接截取方法做詳細解釋。
layer構造函數
function Layer(path, methods, middleware, opts) {
this.opts = opts || {};
this.name = this.opts.name || null; // 命名路由
this.methods = []; // 允許方法
// [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
this.paramNames = [];
this.stack = Array.isArray(middleware) ? middleware : [middleware]; // 中間件堆
// 初始化參數
// tips : forEach 第二個參數可以傳遞this
// forEach push數組以後 可以使用數組[l-1]進行判斷末尾元素
// push方法返回值是該數組push後元素個數
// 外部method參數傳入內部
methods.forEach(function(method) {
var l = this.methods.push(method.toUpperCase());
// 如果是GET請求 支持HEAD請求
if (this.methods[l-1] === 'GET') {
this.methods.unshift('HEAD');
}
}, this);
// ensure middleware is a function
// 保證每一個middleware 爲函數
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;
// 利用pathToRegExp 生成路徑的正則表達式
// 與params相關的數組回落入到我們的this.paramNames中
// this.regexp一個生成用來切割的數組
this.regexp = pathToRegExp(path, this.paramNames, this.opts);
debug('defined route %s %s', this.methods, this.opts.prefix + this.path);
};
我們可以關注在輸入與輸出。
輸入:path, methods, middleware, opts
輸出:對象 屬性包括(opts, name, methods, paramNames, stack, path, regexp)
我們之前說過了 layer是根據route path 做處理 判斷是否匹配,連接庫path-to-regexp,這一點很重要。
stack應該與傳入的middleware一致。stack是數組形式,以此可見我們的path對應的route是允許多個的。
我們接下來關注下
根據path-to-regexp結合自身需要的middleware, koa-router給我們處理了什麼封裝
原型鏈上掛載方法有
params
// 獲取路由參數鍵值對
Layer.prototype.params = function (path, captures, existingParams) {
var params = existingParams || {};
for (var len = captures.length, i=0; i<len; i++) {
if (this.paramNames[i]) { // 獲得捕獲組相對應
var c = captures[i]; // 獲得參數值
params[this.paramNames[i].name] = c ? safeDecodeURIComponent(c) : c;
// 填充鍵值對
}
}
// 返回參數鍵值對對象
return params;
};
在構造函數初始化的時候,我們生成this.regexp的時候通過傳入this.paramNames從而將其根據path解析出的param填出
輸入: 路徑,捕獲組,已存在的參數組
輸出: 一個參數鍵值對對象
處理方式很普通。因爲params與captures 是位置相對應的。所以直接可以循環即可。
match
// 判斷是否匹配
Layer.prototype.match = function (path) {
return this.regexp.test(path);
};
首先看的也是輸入值與返回值
輸入: path
輸出: 是否匹配的boolean
我們可以看這個this.regexp是屬性值,證明我們是有能力隨時改變this.regexp 從而影響這個函數的返回值
captures
// 返回參數值
Layer.prototype.captures = function (path) {
if (this.opts.ignoreCaptures) return []; // 忽略捕獲返回空
// match 返回匹配結果的數組
// 從正則可以看出生成的正則是一段全匹配。
/**
* eg:
* var test = []
* pathToRegExp('/:id/name/(.*?)', test)
*
* /^\/((?:[^\/]+?))\/name\/((?:.*?))(?:\/(?=$))?$/i
*
* '/xixi/name/ashdjhk'.match(/^\/((?:[^\/]+?))\/name\/((?:.*?))(?:\/(?=$))?$/i)
*
* ["/xixi/name/ashdjhk", "xixi", "ashdjhk"]
*/
return path.match(this.regexp).slice(1); // [value, value .....]
};
輸入: path路徑
輸出: 捕獲組數組
返回整個捕獲組內容
url
Layer.prototype.url = function(params, options) {
var args = params;
console.log(this);
var url = this.path.replace(/\(\.\*\)/g, "");
var toPath = pathToRegExp.compile(url); //
var replaced;
if (typeof params != "object") {
args = Array.prototype.slice.call(arguments);
if (typeof args[args.length - 1] == "object") {
options = args[args.length - 1];
args = args.slice(0, args.length - 1);
}
}
var tokens = pathToRegExp.parse(url);
var replace = {};
if (args instanceof Array) {
for (var len = tokens.length, i = 0, j = 0; i < len; i++) {
if (tokens[i].name) replace[tokens[i].name] = args[j++];
}
} else if (tokens.some(token => token.name)) {
replace = params; // replace = params
} else {
options = params; // options = params
}
replaced = toPath(replace); // 默認情況下 replace 是默認傳入的鍵值對 //匹配過後就是完整的url
if (options && options.query) {
// 是否存在query
var replaced = new uri(replaced); //
replaced.search(options.query); //添加query 路由查詢
return replaced.toString();
}
return replaced; // 返回URL串
};
layer實例的url方法
實際上一個例如/name/:id
我們解析後會獲得一個{id: xxx}的params對象
根據/name/:id 跟params對象我們是不是可以反推出實際的url?
這個url方法提供的就是這種能力。
param
Layer.prototype.param = function(param, fn) {
var stack = this.stack;
var params = this.paramNames;
var middleware = function(ctx, next) {
return fn.call(this, ctx.params[param], ctx, next);
};
middleware.param = param;
var names = params.map(function(p) {
return String(p.name);
});
var x = names.indexOf(param); // 獲得index
if (x > -1) {
stack.some(function(fn, i) {
// param handlers are always first, so when we find an fn w/o a param property, stop here
// if the param handler at this part of the stack comes after the one we are adding, stop here
// 兩個策略
// 1. param處理器總是在最前面的,當前fn.param不存在。則直接插入 [a,b] mid => [mid, a, b]
// 2. [mid, a, b] mid2 => [mid, mid2, a, b]保證按照params的順序排列
// 保證在正常中間件前
// 保證按照params順序排列
if (!fn.param || names.indexOf(fn.param) > x) {
// 在當前注入中間件
stack.splice(i, 0, middleware);
return true; // 停止some迭代。
}
});
}
return this;
};
這個方法的作用是在當前的stack中添加針對單個param的處理器
實際上就是對layer的stack進行一個操作
setPrefix
Layer.prototype.setPrefix = function(prefix) {
// 調用setPrefix相當於將layer的一些構造重置
if (this.path) {
this.path = prefix + this.path;
this.paramNames = [];
this.regexp = pathToRegExp(this.path, this.paramNames, this.opts);
}
return this;
};
對當前的path加上前綴並且重置當前的一些實例屬性
safeDecodeURIComponent
function safeDecodeURIComponent(text) {
try {
return decodeURIComponent(text);
} catch (e) {
return text;
}
}
保證safeDecodeURIComponent 不會拋出任何錯誤
Layer總結。
layer的stack主要是存儲實際的middleware[s]。
主要的功能是針對pathToRegexp做設計。
提供能力給上層的Router做調用實現的。
Router
Router主要是對上層koa框架的響應(ctx, status等處理),以及鏈接下層layer實例。
初始化
function Router(opts) {
// 自動new
if (!(this instanceof Router)) {
return new Router(opts);
}
this.opts = opts || {};
// methods用於對後面allowedMethod做校驗的
this.methods = this.opts.methods || [
"HEAD",
"OPTIONS",
"GET",
"PUT",
"PATCH",
"POST",
"DELETE"
]; // 初始化http方法
this.params = {}; // 參數鍵值對
this.stack = []; // 存儲路由實例
}
methods.forEach(function(method) {
// 給原型上附加所有http method 方法
Router.prototype[method] = function(name, path, middleware) {
var middleware;
// 兼容參數
// 允許path爲字符串或者正則表達式
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;
}
// 註冊到當前實例上
// 主要是設置一個通用的install middleware 的方法。(mark. tag: function)
this.register(path, [method], middleware, {
name: name
});
// 鏈式調用
return this;
};
});
給Router原型註冊上
http method 的方法,如:Router.prototype.get = xxx
當我們使用實例的時候可以更方便準確使用
router.get('name', path, cb)
這裏的middleware顯然是可以多個。例如router.get(name, path, cb)
我們可以留意到,這裏的主要是調用了另一個方法
notic:
register方法。而這個方法的入參,我們可以留意下。與Layer實例初始化入參極爲相似。
帶着疑惑我們可以進入到register方法內。
Router.prototype.register = function(path, methods, middleware, opts) {
opts = opts || {};
var router = this;
var stack = this.stack;
if (Array.isArray(path)) {
path.forEach(function(p) {
router.register.call(router, p, methods, middleware, opts);
});
return this;
}
var route = new Layer(path, methods, middleware, {
end: opts.end === false ? opts.end : true, // 需要明確聲明爲end
name: opts.name, // 路由的名字
sensitive: opts.sensitive || this.opts.sensitive || false, // 大小寫區分 正則加i
strict: opts.strict || this.opts.strict || false, // 非捕獲分組 加(?:)
prefix: opts.prefix || this.opts.prefix || "", // 前綴字符
ignoreCaptures: opts.ignoreCaptures || false // 給layer使用 忽略捕獲
});
if (this.opts.prefix) {
route.setPrefix(this.opts.prefix);
}
// add parameter middleware
// 添加參數中間件
Object.keys(this.params).forEach(function(param) {
route.param(param, this.params[param]);
}, this);
// 當前Router實例stack push單個layer實例
stack.push(route);
return route;
};
我們可以看到整個register方法,是設計給註冊單一路徑的。
針對多路徑在forEach調用register方法。這種寫法在koa-router實現裏並不少見。。
看了register方法,我們的疑惑得到了證實,果然入參大多是用來初始化layer實例的。
初始化layer實例後,我們將它放置到router實例下的stack中。
根據一些opts再進行處理判斷。不多大抵是無傷大雅的。
這樣一來我們就知道了register的用法。
- 初始化layer實例
- 將其註冊到router實例中。
我們知道我們調用router實例時候。
要使用中間件 我們往往需要完成兩步
- use(router.routes())
- use(router.allowedMethods())
我們知道一個極簡的中間件調用形式總是
app.use(async (ctx, next) => {
await next()
})
我們的不管koa-body 還是koa-router
傳入app.use總是一個
async (ctx, next) => {
await next()
}
這樣的函數,是符合koa 中間件需求的。
帶着這樣的想法
我們可以來到routes方法中一探究竟。
Router.prototype.routes = Router.prototype.middleware = function() {
var router = this;
var dispatch = function dispatch(ctx, next) {
debug("%s %s", ctx.method, ctx.path);
// 獲得路徑
var path = router.opts.routerPath || ctx.routerPath || ctx.path;
// matched已經是進行過處理了 獲得了layer對象承載
var matched = router.match(path, ctx.method);
var layerChain, layer, i;
// 考慮多個router實例的情況
if (ctx.matched) {
// 因爲matched總是一個數組
// 這裏用apply類似於concat
ctx.matched.push.apply(ctx.matched, matched.path);
} else {
// 匹配的路徑
ctx.matched = matched.path;
}
// 當前路由
ctx.router = router;
// 如果存在匹配的路由
if (!matched.route) return next();
// 方法與路徑都匹配的layer
var matchedLayers = matched.pathAndMethod;
// 最後一個layer
var mostSpecificLayer = matchedLayers[matchedLayers.length - 1];
//
ctx._matchedRoute = mostSpecificLayer.path;
// 如果layer存在命名
if (mostSpecificLayer.name) {
ctx._matchedRouteName = mostSpecificLayer.name;
}
// 匹配的layer進行compose操作
// update capture params routerName等
// 例如我們使用了多個路由的話。
// => ctx.capture, ctx.params, ctx.routerName => layer Stack[s]
// => ctx.capture, ctx.params, ctx.routerName => next layer Stack[s]
layerChain = matchedLayers.reduce(function(memo, layer) {
memo.push(function(ctx, next) {
ctx.captures = layer.captures(path, ctx.captures);
ctx.params = layer.params(path, ctx.captures, ctx.params);
ctx.routerName = layer.name;
return next();
});
return memo.concat(layer.stack);
}, []);
return compose(layerChain)(ctx, next);
};
dispatch.router = this;
return dispatch;
};
我們知道路由匹配的本質是實際路由與定義路徑相匹配。
那麼routes生成的中間件實際上就是在考慮做這種匹配的處理。
從返回值我們可以看到
=> dispatch方法。
這個dispacth方法實際上就是我們前面說的極簡方式。
function dispatch(ctx, next) {}
可以說是相差無幾。
我們知道stack當前存儲的是多個layer實例。
而根據路徑的匹配,我們可以知道
一個後端路徑,簡單可以分爲http方法,與路徑定義匹配。
例如:/name/:id
這個時候來了個請求/name/3
是不是匹配了。(params = {id: 3})
但是請求方法如果是get呢? 定義的這個/name/:id是個post的話。
則此時雖然路徑匹配,但是實際並不能完全匹配。
Router.prototype.match = function(path, method) {
var layers = this.stack;
var layer;
var matched = {
path: [],
pathAndMethod: [],
route: false
};
for (var len = layers.length, i = 0; i < len; i++) {
layer = layers[i];
debug("test %s %s", layer.path, layer.regexp);
if (layer.match(path)) {
//如果路徑匹配
matched.path.push(layer);
// matched中壓入layer
if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) {
// 校驗方法
matched.pathAndMethod.push(layer);
// 路徑與方法中都壓入layer
if (layer.methods.length) matched.route = true;
// 證明沒有支持的方法。route爲true 後面跳過中間件處理
}
}
}
return matched;
};
看看這個match方法吧。
對stack中的layaer進行判斷。
返回的matched對象中
path屬性: 僅僅路徑匹配即可。
pathAndMethod屬性: 僅僅http方法與路徑匹配即可。
route屬性: 需要layer的方法長度不爲0(有定義方法。)
所以dispatch中我們首先
ctx.matched = matched.path
得到路徑匹配的layer
實際中間件處理的,是http方法且路徑匹配的layer
這種情況下。而實際上,所謂中間件就是一個個數組
它的堆疊方式可能是多維的,也可能是一維的。
如果一個route進行了匹配
ctx._matchedRoute代表了它的路徑。
這裏ctx._matchedRoute是方法且路徑匹配數組的layer的最後一個。
相信取最後一個大家也知道爲什麼。多個路徑,除開當前處理,在下一個中間件處理時候,總是返回最後一個即可。
最後將符合的layer組合起來
例如 如果有多個layer的情況下,layer也有多個stack的情況下
// 例如我們使用了多個路由的話。
// => ctx.capture, ctx.params, ctx.routerName => layer Stack[?s]
// => ctx.capture, ctx.params, ctx.routerName => next layer Stack[?s]
運行順序就會如上所示
相當於在將多個layer實例的stack展平,且在每一個layer實例前,添加ctx屬性進行使用。
最後用compose將這個展平的數組一起拿來使用。
其實在這裏我們可以留意到,所謂的中間件也不過是一堆數組罷了。
但是這裏的在每個layer實例前使用ctx屬性倒是個不錯的想法。
對中間件的操作例如prefix等。就是不斷的對內部的stack位置屬性的調整。
allowedMethods
Router.prototype.allowedMethods = function(options) {
options = options || {};
var implemented = this.methods;
// 返回一箇中間件用於 app.use註冊。
return function allowedMethods(ctx, next) {
return next().then(function() {
var allowed = {};
// 判斷ctx.status 或者狀態碼爲404
console.log(ctx.matched, ctx.method, implemented);
if (!ctx.status || ctx.status === 404) {
// routes方法生成的ctx.matched
// 就是篩選出來的layer匹配組
ctx.matched.forEach(function(route) {
route.methods.forEach(function(method) {
allowed[method] = method;
});
});
var allowedArr = Object.keys(allowed);
// 實現了的路由匹配
if (!~implemented.indexOf(ctx.method)) {
// 位運算符 ~(-1) === 0 !0 == true
// options參數 throw如果爲true的話則直接扔出錯誤
// 這樣可以給上層中間價做處理
// 默認是拋出一個HttpError
if (options.throw) {
var notImplementedThrowable;
if (typeof options.notImplemented === "function") {
notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function
} else {
notImplementedThrowable = new HttpError.NotImplemented();
}
throw notImplementedThrowable;
} else {
// 否則跑出501
// 501=>服務器未實現方法
ctx.status = 501;
ctx.set("Allow", allowedArr.join(", "));
}
// 如果允許的話
} else if (allowedArr.length) {
// 對options請求進行操作。
// options請求與get請求類似,但是請求沒有請求體 只有頭。
// 常用語查詢操作
if (ctx.method === "OPTIONS") {
ctx.status = 200;
ctx.body = "";
ctx.set("Allow", allowedArr.join(", "));
} else if (!allowed[ctx.method]) {
// 如果允許方法
if (options.throw) {
var notAllowedThrowable;
if (typeof options.methodNotAllowed === "function") {
notAllowedThrowable = options.methodNotAllowed(); // set whatever the user returns from their function
} else {
notAllowedThrowable = new HttpError.MethodNotAllowed();
}
throw notAllowedThrowable;
} else {
// 405 方法不被允許
ctx.status = 405;
ctx.set("Allow", allowedArr.join(", "));
}
}
}
}
});
};
};
這個方法主要是默認的給我們路由中間件添加404 405 501的這些狀態控制。
我們也可以在高層中間件統一處理也可以。
使用位運算符+indexOf也是一種常見的用法。
至此整篇的koa-router源碼就解析完畢了。
雖然Router的源碼還有很多方法沒有仔細查看,但是大多都是給上層提供layer實例的方法連接。
總的來說能吸收的點可能是挺多的。
如果看完了整篇。
- 相信你對koa middleware使用應該是更得心應手了。
- 相信你對koa-router的源碼架構具體方法實現應該是有所瞭解。