什么是中间件

中间件的本质就是一种在特定场景下使用的函数,它负责完成某个特定的功能。Koa没有绑定中间件,但Koa 所有的功能基本上都是通过中间件来实现的。

中间件机制

洋葱模型,它就是 Koa 中间件的一种串行机制,并且是支持异步的。Koa中间件机制就是函数式组合概念 Compose的概念,将一组需要顺序执行的函数复合为一个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制。
image.png

中间件的参数

  1. const middleware = async (ctx, next) => {
  2. next()
  3. };

中间件包含两个参数 ctx 和 next:

  • ctx: 作为上下文使用,包括基本的 ctx.requestctx.response
  • next: 中间件通过next函数联系, 执行 next() 后会将控制权交给下一个中间件, 直到没有中间件执行next后将会沿路折返,将控制权交换给前一个中间件,因此next()后面的代码会在后面中间件执行结束后执行。

简单来说:ctx 就是网络处理上下文,next函数指向下个中间件,内部通过 dispatch 函数形成了一条处理请求的流水线。

常见中间件实现

koa中间件的规范:

  • 一个async函数
  • 接收ctx和next两个参数
  • 任务结束需要执行next 中间件
  1. const middleware= async (ctx, next) => {
  2. // 来到中间件,洋葱圈左边
  3. next() // 进入下一个中间件
  4. // 再次来到中间件,洋葱圈右边
  5. };

router 中间件实现

  1. class Router {
  2. constructor() {
  3. this.stack = [];
  4. }
  5. register(path, methods, middleware) {
  6. let route = {path, methods, middleware}
  7. this.stack.push(route);
  8. }
  9. // 现在只支持get和post,其他的同理
  10. get(path,middleware){
  11. this.register(path, 'get', middleware);
  12. }
  13. post(path,middleware){
  14. this.register(path, 'post', middleware);
  15. }
  16. routes() {
  17. let stock = this.stack;
  18. return async function(ctx, next) {
  19. let currentPath = ctx.url;
  20. let route;
  21. for (let i = 0; i < stock.length; i++) {
  22. let item = stock[i];
  23. if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) {
  24. // 判断path和method
  25. route = item.middleware;
  26. break;
  27. }
  28. }
  29. if (typeof route === 'function') {
  30. route(ctx, next);
  31. return;
  32. }
  33. await next();
  34. };
  35. }
  36. }
  37. module.exports = Router;

router中间件使用

  1. const Koa = require('./koa')
  2. const Router = require('./router')
  3. const app = new Koa()
  4. const router = new Router();
  5. router.get('/index', async ctx => {
  6. console.log('index,xx')
  7. ctx.body = 'index page';
  8. });
  9. router.get('/post', async ctx => { ctx.body = 'post page'; });
  10. router.get('/list', async ctx => { ctx.body = 'list page'; });
  11. router.post('/index', async ctx => { ctx.body = 'post page'; });
  12. // 路由实例输出父中间件 router.routes()
  13. app.use(router.routes());
  14. app.listen(3000, () => {
  15. console.log('server runing on port 9092')
  16. })

静态文件服务 static 中间件实现

其功能类似于 koa-static 中间件:

  • 配置绝对资源目录地址,默认为 static
  • 获取文件或者目录信息
  • 静态文件读取、返回
  1. // static.js
  2. const fs = require("fs");
  3. const path = require("path");
  4. module.exports = (dirPath = "./public") => {
  5. return async (ctx, next) => {
  6. if (ctx.url.indexOf("/public") === 0) {
  7. // public开头 读取文件
  8. const url = path.resolve(__dirname, dirPath);
  9. const fileBaseName = path.basename(url);
  10. const filepath = url + ctx.url.replace("/public", "");
  11. console.log(filepath);
  12. // console.log(ctx.url,url, filepath, fileBaseName)
  13. try {
  14. stats = fs.statSync(filepath);
  15. if (stats.isDirectory()) {
  16. const dir = fs.readdirSync(filepath);
  17. // const
  18. const ret = ['<div style="padding-left:20px">'];
  19. dir.forEach(filename => {
  20. console.log(filename);
  21. // 简单认为不带小数点的格式,就是文件夹,实际应该用statSync
  22. if (filename.indexOf(".") > -1) {
  23. ret.push(
  24. `<p><a style="color:black" href="${
  25. ctx.url
  26. }/${filename}">${filename}</a></p>`
  27. );
  28. } else {
  29. // 文件
  30. ret.push(
  31. `<p><a href="${ctx.url}/${filename}">${filename}</a></p>`
  32. );
  33. }
  34. });
  35. ret.push("</div>");
  36. ctx.body = ret.join("");
  37. } else {
  38. console.log("文件");
  39. const content = fs.readFileSync(filepath);
  40. ctx.body = content;
  41. }
  42. } catch (e) {
  43. // 报错了 文件不存在
  44. ctx.body = "404, not found";
  45. }
  46. } else {
  47. // 否则不是静态资源,直接去下一个中间件
  48. await next();
  49. }
  50. };
  51. };

静态文件服务 static 中间件使用

  1. const static = require('./static')
  2. app.use(static(__dirname + '/public'));

请求拦截中间件实现

请求拦截应用非常广泛:登录状态验证、CORS头设置等

  1. // iptable.js
  2. module.exports = async function (ctx, next) {
  3. const { res, req } = ctx;
  4. const blackList = ['127.0.0.1'];
  5. const ip = getClientIP(req);
  6. if (blackList.includes(ip)) {//出现在黑名单中将被拒绝
  7. ctx.body = "not allowed";
  8. } else {
  9. await next();
  10. }
  11. };
  12. function getClientIP(req) {
  13. return (
  14. req.headers["x-forwarded-for"] || // 判断是否有反向代理 IP
  15. req.connection.remoteAddress || // 判断 connection 的远程 IP
  16. req.socket.remoteAddress || // 判断后端的 socket 的 IP
  17. req.connection.socket.remoteAddress
  18. );
  19. }

请求拦截中间件使用

  1. // app.js
  2. const Koa = require('./koa')
  3. const Router = require('./router')
  4. const app = new Koa()
  5. app.use(require("./interceptor"));
  6. app.listen(3000, '0.0.0.0', () => {
  7. console.log("监听端口3000");
  8. });