Middleware

2018-11-23 12:43:05

中间件的作用

灵活应用中间件机制,可以有效扩展架构的功能。中间件主要的作用有:拦截重整注入

  1. 拦截:比如通过中间件判断用户权限,如果没有权限则中止后续执行
  2. 重整:比如通过中间件对前端发来的数据进行验证,并转化为期望的类型
  3. 注入:比如通过中间件向ctx注入对象,以便为后续代码提供必要的基础功能

拦截

在执行API路由之前,对请求参数进行判断,如果不符合预期,就拒绝

比如中间件test,判断当前运行环境,如果不是测试环境就抛出异常:

egg-born-backend/app/middleware/test.js

module.exports = () => {
  return async function test(ctx, next) {
    if (!ctx.app.meta.isTest) ctx.throw(403);
    // next
    await next();
  };
};

重整

在执行API路由之前,对请求参数进行调整,使其符合预期的格式(如果不符合预期也可以拒绝)

比如中间件validate,验证前端发送的表单数据,并转换为预期的数据类型:

a-validation/backend/src/config/middleware/validate.js

// request.body
//   validate: module(optional), validator, schema(optional)
//   data:
module.exports = (options, app) => {
  return async function validate(ctx, next) {
    // ignore
    const validator = options.validator || (ctx.request.body.validate && ctx.request.body.validate.validator);
    if (!validator) return await next();
    // params
    const module = options.module || (ctx.request.body.validate && ctx.request.body.validate.module) || ctx.module.info.relativeName;
    const schema = options.schema || (ctx.request.body.validate && ctx.request.body.validate.schema);
    const data = ctx.request.body[options.data || 'data'];
    // if error throw 422
    await ctx.meta.validation.validate({
      module,
      validator,
      schema,
      data,
    });
    // next
    await next();
  };
};

注入

在执行API路由之前,向ctx对象注入对象实例,扩展功能

比如中间件cachemem,向ctx注入了基于内存的cache对象:

a-cache/backend/src/config/middleware/cachemem.js

const memFn = require('./adapter/mem.js');
const CACHE = Symbol('CTX#__CACHE');

module.exports = () => {
  return async function cachemem(ctx, next) {
    ctx.cache = ctx.cache || {};
    Object.defineProperty(ctx.cache, 'mem', {
      get() {
        if (ctx.cache[CACHE] === undefined) {
          ctx.cache[CACHE] = new (memFn(ctx))();
        }
        return ctx.cache[CACHE];
      },
    });

    // next
    await next();
  };
};

中间件的类型

EggBorn主要有三个来源的中间件:

  1. EggJS中间件
  2. EggBorn系统中间件
  3. EggBorn模块中间件

EggJS中间件

在EggBorn中可以直接使用EggJS生态的中间件

请参考Egg中间件

EggBorn系统中间件

为满足常见场景的应用,EggBorn内置了三个系统中间件:innertesttransaction

1. inner

后端API路由可通过ctx.performAction调用其他后端API路由,这称之为内部访问

后端API路由指定中间件inner就可以限定此路由只能被内部访问

{ method: 'post', path: 'version/update', controller: version, middlewares: 'inner' }

2. test

后端API路由指定中间件test,可以限定此路由只能在测试环境中使用

{ method: 'post', path: 'version/test', controller: version, middlewares: 'test' }

3. transaction

后端API路由指定中间件transaction,可以指定此API路由开启数据库事务

{ method: 'post', path: 'test/echo', controller: test, middlewares: 'transaction'}

EggBorn模块中间件

EggBorn允许在模块中开发中间件,此中间件可以供本模块和其他模块使用

模块中间件的开发

在这里,我们基于模块test-todo,通过一个虚拟的需求,说明如何开发中间件,并用中间件实现拦截、重整、注入三种功能:

  1. 前端向后端发送两个参数:messagemarkCount
  2. 拦截:中间件判断message是否为空,如果为空则中止后续执行
  3. 重整:将markCount类型强制转换为Integer类型
  4. 注入:注入一个方法,以便在后续代码中调用

声明中间件

src/module/test-todo/backend/src/config/config.js

// middlewares
config.middlewares = {
  middlewareDemo: {
    global: false,
    dependencies: 'instance',
  },
};
名称 说明
global 是否为全局中间件,全局中间价会自动加载,局部中间件需要手动指定
dependencies 标明此中间件依赖哪些中间件,从而在那些中间件后面加载。一般要依赖于instance,因为instance提供了多实例的基础逻辑

定义中间件

src/module/test-todo/backend/src/config/middleware/demo.js

module.exports = options => {
  return async function middlewareDemo(ctx, next) {
    // check message
    if (!ctx.request.body.message) ctx.throw(406);
    // adjust markCount
    ctx.request.body.markCount = parseInt(ctx.request.body.markCount);
    // inject function
    if (!ctx.meta) ctx.meta = {};
    ctx.meta._demoFunction = function() {
      const { message, markCount } = ctx.request.body;
      return `${message}${new Array(markCount + 1).join('!')}`;
    };
    // next
    await next();
  };
};

引用中间件

src/module/test-todo/backend/src/config/middlewares.js

const demo = require('./middleware/demo.js');

module.exports = {
  middlewareDemo: demo,
};

使用中间件

因为middlewareDemo是局部中间件,因此需要手动在API路由上指定

src/module/test-todo/backend/src/routes.js

// test
{ method: 'post', path: 'test/echo', controller: test, middlewares: 'transaction,middlewareDemo' },

后端控制器方法

async echo() {
  const res = this.ctx.meta._demoFunction();
  this.ctx.success(res);
}

中间件的使用

全局中间件

全局中间件自动加载,不需在后端路由中指定

局部中间件

只需设置后端API路由参数middlewares,如:

{ method: 'post', path: 'version/update', controller: version, middlewares: 'inner' }

中间件参数

可通过后端API路由参数meta指定中间件的参数,如:

src/module/test-todo/backend/src/routes.js

// test
{ method: 'post', path: 'test/echo', controller: test, middlewares: 'transaction,middlewareDemo',
  meta: {
    right: {
      type: 'function',
      name: 'testEcho',
    },
  },
},
名称 说明
meta.right 全局中间件right的参数

禁用中间件

可通过两种方式禁用中间件:

1. config参数配置

通过属性ignore指定哪些API路由禁用此中间件

a-instance/backend/src/config/config.js

// middlewares
config.middlewares = {
  instance: {
    global: true,
    dependencies: 'cachemem',
    ignore: /(\/version\/(start|check|update|initModule)|\/a\/instance\/version\/init|\/a\/version\/version\/init)/,
  },
};

2. 后端API路由

直接在后端API路由中通过属性enable禁用中间件

a-base/backend/src/routes.js

{ method: 'post', path: 'auth/echo', controller: auth,
  meta: {
    auth: { 
      enable: false
    }
  } 
}
名称 说明
auth.enable 禁用中间件auth

当某中间件被禁用后,依赖于此中间件的其他中间件也自动被禁用



评论: