广义中间件-间接中间件处理
koa-router 实现
实现步骤
- 初始化路由实例
- 注册路由请求信息缓存到实例中
- 请求类型
- 请求 path
- 对应的请求后操作
- 注册的路由操作就是子中间件
- 路由实例输出父中间件
- 返回一个父中间件
- 中间件里对每次请求进行遍历匹配缓存中注册的路由操作
- 匹配上请求类型,路径就执行对应路由子中间件
- app.use()路由实例返回的父中间件
实现源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-06-01
解读
const methods = ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'];
class Layer {
constructor(path, methods, middleware, opts) {
this.path = path;
this.methods = methods;
this.middleware = middleware;
this.opts = opts;
}
}
class Router {
constructor(opts = {}) {
this.stack = [];
}
register(path, methods, middleware, opts) {
let route = new Layer(path, methods, middleware, opts);
this.stack.push(route);
return this;
}
routes() {
let stock = this.stack;
return async function (ctx, next) {
let currentPath = ctx.path;
let route;
for (let i = 0; i < stock.length; i++) {
let item = stock[i];
if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) {
route = item.middleware;
break;
}
}
if (typeof route === 'function') {
route(ctx, next);
return;
}
await next();
};
}
}
methods.forEach(method => {
Router.prototype[method.toLowerCase()] = Router.prototype[method] = function (path, middleware) {
this.register(path, [method], middleware);
};
});
module.exports = Router;
使用
const Koa = require('koa');
const Router = require('./index');
const app = new Koa();
const router = new Router();
router.get('/index', async ctx => {
ctx.body = 'index page';
});
router.get('/post', async ctx => {
ctx.body = 'post page';
});
router.get('/list', async ctx => {
ctx.body = 'list page';
});
router.get('/item', async ctx => {
ctx.body = 'item page';
});
app.use(router.routes());
app.use(async ctx => {
ctx.body = '404';
});
app.listen(3000);
console.log('listening on port 3000');
koa-mount 实现
广义中间件,间接中间件方式实现,还有一个官方的中间件 koa-mount
,让多个 Koa.js 子应用合并成一个父应用,用请求的前缀区分子应用。这里基于第三方中间件 koa-mount
用最简单的方式实现 koa-mount
最简单功能。
实现步骤
- 使用过程
- 初始化子应用 Koa.js 实例
- 初始化父应用 Koa.js 实例
- 处理子应用 middleware 属性,将所有中间件用 koa-compose 封装成一个子应用中间件
- 用父应用 app.use()加载处理后的子应用中间件
- mount 实现过程
- 输入子应用的前缀和应用实例
- 获取子应用的中间件集合 middleware 属性
- 用 koa-compose 封装子应用的中间件集合
- 返回一个父中间件
- 父中间件里执行 compose 封装后的子中间件集合
- 执行前把请求 path 子应用前缀去掉
- 执行后把请求 path 子应用前缀还原到原始请求 path
- 父应用 app.use 子应用封装后父中间件,(compose 封装的子应用所有中间件)
实现源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-06-02
依赖
koa-compose
解读
const compose = require('./compose');
function mount(prefix, app) {
let middleware = app.middleware;
let middlewareStream = compose(middleware || []);
if (prefix === '/') {
return middlewareStream;
}
return async function (ctx, next) {
let mountPath = matchPath(ctx.path);
if (!mountPath) {
await next();
return;
}
let originPath = ctx.path;
ctx.path = mountPath;
await middlewareStream(ctx, async () => {
ctx.path = originPath;
await next();
ctx.path = mountPath;
});
ctx.path = originPath;
};
function matchPath(originPath) {
if (originPath.indexOf(prefix) < 0) {
return false;
}
const mountPath = originPath.replace(prefix, '') || '/';
if (mountPath[0] !== '/') {
return false;
}
return mountPath;
}
}
module.exports = mount;
使用
const mount = require('./index');
const Koa = require('koa');
const app1 = new Koa();
const app2 = new Koa();
app1.use(async (ctx, next) => {
await next();
ctx.body = 'app 1';
});
app2.use(async (ctx, next) => {
await next();
ctx.body = 'app 2';
});
const app = new Koa();
app.use(mount('/app1', app1));
app.use(mount('/app2', app2));
app.listen(3000);
console.log('listening on port 3000');
module.exports = compose;
function compose(middleware) {
if (!Array.isArray(middleware)) {
throw new TypeError('Middleware stack must be an array!');
}
return function (ctx, next) {
let index = -1;
return dispatch(0);
function dispatch(i) {
if (i < index) {
return Promise.reject(new Error('next() called multiple times'));
}
index = i;
let fn = middleware[i];
if (i === middleware.length) {
fn = next;
}
if (!fn) {
return Promise.resolve();
}
try {
return Promise.resolve(
fn(ctx, () => {
return dispatch(i + 1);
})
);
} catch (err) {
return Promise.reject(err);
}
}
};
}