跳到主要内容

广义中间件-间接中间件处理

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);
}
}
};
}