I left off with a look at how KOA handles routing and business code, because fnMiddleware(CTX).then exists, so you need to string business code into the middleware as a next. So let’s take a look at how koa-router is handled.
An overview of
It mainly includes the following two items
- Register: when called
router.get
Will be through the callrouter.register
,register
Through the incomingpath
fn
Instantiate aLayer
Object, which is then pushed into the Route stack of the Router object for invocation on request. - Request: when requested, the logic goes to creteContext -> fnMiddleware to execute -> Routes middleware to execute -> re-compose a compose function and pass it to next, passing it to the previous function execution chain -> complete.
The source code is as follows
1. The register code
To invoke the route. The get, wrap related attributes as a layer | route objects, stored in the route of the stack, to look up for the request.
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;
};
Copy the code
Where, the object of Layer is as follows
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()}\ `The ${this.opts.name || path}\`: \`middleware\` must be a function, not \`${type}\ ` `
);
}
this.path = path;
this.regexp = pathToRegexp(path, this.paramNames, this.opts);
};
Copy the code
2. Request time code
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();
});
returnmemo.concat(layer.stack); } []);return compose(layerChain)(ctx, next);
};
dispatch.router = this;
return dispatch;
};
Copy the code
In short, compose is composed nested within compose and pushes two middleware components, one for ctx.params and one for the business.
The steps are as follows:
- Mount params to the context
- The subsequent business logic is then executed
- If there is middleware behind the whole, the remaining middleware continues to be executed
The test code is as follows
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) = > {
// There is no params property on CTX yet
console.log(ctx);
next();
})
.use(router.routes())
.use(router.allowedMethods())
app.listen(3000);
Copy the code
If you curl http://localhost:3000/ssss will return here 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);
Copy the code
If this is the case, last is returned.