接着上一篇,看下koa是如何处理路由以及业务代码的,因为fnMiddleware(ctx).then 这种东西的存在,所以执行的时候需要把业务代码也都串入到middleware里头作为一个next,所以我们看下Koa-router是如何处理的。
概览
其中主要包括以下两项
- 注册: 当调用
router.get
的时候会通过调用router.register
,register
里通过传入的path
fn
实例化一个Layer
对象, 然后push到router对象的route栈里头,来供请求时的调用。 - 请求:当请求的时候,走的逻辑是 creteContext -> fnMiddleware执行 -> 当遇到routes中间件的时候 -> 重新构造一个compose的函数,并传入next,以此传入之前的函数执行链里头 -> 执行完毕。
源码如下
1. register代码
旨在调用route.get的时候,把相关的属性包装成layer|route对象,存储到route的stack里头,供请求的时候进行查找。
Router.prototype.register = function (path, methods, middleware, opts) {
opts = opts || {};
const router = this;
const stack = this.stack;
// support array of paths
if (Array.isArray(path)) {
for (let i = 0; i < path.length; i++) {
const curPath = path[i];
router.register.call(router, curPath, methods, middleware, opts);
}
return this;
}
// create route
const 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);
}
// add parameter middleware
for (let i = 0; i < Object.keys(this.params).length; i++) {
const param = Object.keys(this.params)[i];
route.param(param, this.params[param]);
}
stack.push(route);
debug('defined route %s %s', route.methods, route.path);
return route;
};
其中,layer的对象如下
function Layer(path, methods, middleware, opts) {
this.opts = opts || {};
this.name = this.opts.name || null;
this.methods = [];
this.paramNames = [];
this.stack = Array.isArray(middleware) ? middleware : [middleware];
for(let i = 0; i < methods.length; i++) {
const l = this.methods.push(methods[i].toUpperCase());
if (this.methods[l-1] === 'GET') this.methods.unshift('HEAD');
}
// ensure middleware is a function
for (let i = 0; i < this.stack.length; i++) {
const fn = this.stack[i];
const type = (typeof fn);
if (type !== 'function')
throw new Error(
`${methods.toString()} \`${this.opts.name || path}\`: \`middleware\` must be a function, not \`${type}\``
);
}
this.path = path;
this.regexp = pathToRegexp(path, this.paramNames, this.opts);
};
2. 请求时候的代码
Router.prototype.routes = Router.prototype.middleware = function () {
const router = this;
let dispatch = function dispatch(ctx, next) {
debug('%s %s', ctx.method, ctx.path);
const path = router.opts.routerPath || ctx.routerPath || ctx.path;
const matched = router.match(path, ctx.method);
let layerChain;
if (ctx.matched) {
ctx.matched.push.apply(ctx.matched, matched.path);
} else {
ctx.matched = matched.path;
}
ctx.router = router;
if (!matched.route) return next();
const matchedLayers = matched.pathAndMethod
const mostSpecificLayer = matchedLayers[matchedLayers.length - 1]
ctx._matchedRoute = mostSpecificLayer.path;
if (mostSpecificLayer.name) {
ctx._matchedRouteName = mostSpecificLayer.name;
}
layerChain = matchedLayers.reduce(function(memo, layer) {
memo.push(function(ctx, next) {
ctx.captures = layer.captures(path, ctx.captures);
ctx.params = ctx.request.params = layer.params(path, ctx.captures, ctx.params);
ctx.routerPath = layer.path;
ctx.routerName = layer.name;
ctx._matchedRoute = layer.path;
if (layer.name) {
ctx._matchedRouteName = layer.name;
}
return next();
});
return memo.concat(layer.stack);
}, []);
return compose(layerChain)(ctx, next);
};
dispatch.router = this;
return dispatch;
};
一言以蔽之,就是compose中嵌套compose,并且里头会push两个中间件,一个用来处理ctx.params,一个是业务的中间件。
步骤如下:
- 把params挂载到context上
- 然后执行后续的业务逻辑
- 如果整体之后还有中间件,则会继续执行剩下的中间件
测试代码如下
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();
router.get('/', (ctx, next) => {
ctx.body = '/';
});
router.get('/ssss', (ctx, next) => {
ctx.body = 'sssss';
});
app
.use((ctx, next) => {
// 这里ctx上还没有params属性
console.log(ctx);
next();
})
.use(router.routes())
.use(router.allowedMethods())
app.listen(3000);
这里如果你 curl http://localhost:3000/ssss
会返回 ssss
;
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();
router.get('/', (ctx, next) => {
ctx.body = '/';
});
router.get('/ssss', (ctx, next) => {
ctx.body = 'sssss';
next();
});
app
.use((ctx, next) => {
console.log(ctx);
next();
})
.use(router.routes())
.use(router.allowedMethods())
.use((ctx, next) => {
ctx.body = 'last'
})
app.listen(3000);
如果是这种情况,则会返回 last
。