检测 Express 路由中的参数合法性

Oct 15, 2016 06:46 · 2004 words · 4 minutes read Node.js JavaScript Express

本文以 Express 框架为基础,讲诉如何通过一个中间件来检测 Express 路由中传输的参数是否合法。

几乎对于任何应用,前后端都需要进行传输数据。不管是通过 HTTP 请求的 POST 方法还是 GET 方法,数据校验都是必要的操作。

对于大部分 API 来说,可能只需要判断传入的参数是否为 undefined 或 null,所以这个时候,为了减少重复代码,我们可以写一个简单的中间件来处理路由中的参数。

这个中间件的需求如下:

  • 检测路由中的一般参数是否为 undefined、null、[]、”
  • 中间件同时还需要能对特殊参数做处理,如一个参数值在 1-100 之间

最终写出来的处理参数的模块:

/**
 * 检测路由中的参数
 * @method checkParameters
 * @param  {object}        req     http请求
 * @param  {object}        res     http响应
 * @param  {Function}      next
 * @param  {[type]}        special 特殊参数,
 *                                 格式: [{eval: 'req.query.id>0', key:'id', type: 'query'}]
 *                                 key 属性的值必须和eval中对应,且type只能是 query/params/body
 * @return {object}        若参数错误,直接返回json到前端;正确,则next()
 */
function checkParameters(req, res, next, special) {
  // console.log('params: ', req.params);
  // console.log('body: ', req.body);
  // console.log('query: ', req.query);
  console.log('specialParams: ', typeof special);
  // 判断是否传入第四个参数
  if (Array.isArray(special)) {
    // 判断特殊参数
    for (let i = 0; i < special.length; i++) {
      // console.log('special[i]: ', special[i]);
      // console.log('special[i] eval: ', special[i].hasOwnProperty('eval'));
      // console.log('special[i] key: ', special[i].hasOwnProperty('key'));
      // console.log('special[i] type: ', special[i].hasOwnProperty('type'));

      // 判断是否具有 eval, key, type 三个参数
      if (! (special[i].hasOwnProperty('eval')
        && special[i].hasOwnProperty('key')
        && special[i].hasOwnProperty('type'))) {
        return res.json({
          code: 90001,
          msg: '检测参数属否正确时,传入的特殊参数格式不完全',
          detail: special
        });
      }
      // 判断 eval, key, type 三个参数是否为 undefined
      if ( !(typeof special[i]['eval'] !== undefined
      && typeof special[i]['key'] !== undefined
      && typeof special[i]['type'] !== undefined)) {
        return res.json({
          code: 90002,
          msg: '检测参数属否正确时,传入的特殊参数为 undefined',
          detail: special
        });
      }
      const evalString = special[i]['eval'];
      const type = special[i]['type'];
      const key = special[i]['key'];
      // 判断 key 和 eval 是否匹配
      console.log('length: ', evalString
      .split('req.' + type + '.' + key)
      .length);
      const length = evalString.split('req.' + type + '.' + key).length;
      if (length < 1) {
        return res.json({
          code: 90003,
          msg: '检测参数属否正确时,传入的特殊参数为格式不正确',
          detail: special
        });
      }
      // 执行 eval
      if (!eval(evalString)) {
        return res.json({
          code: 90004,
          msg: '检测参数属否正确时,参数不匹配传入的条件 ' + evalString,
          detail: special,
          parameters: req[type]
        });
      }
      // 从普通参数中删除特殊参数
      // console.log('delete: ', req[type][key]);
      delete req[type][key];
    }
  }

  const params = req.params;
  // 去掉 req.params 中 {'0': '', ...} 这一项
  delete params['0'];
  // console.log('params: ', params);
  const body = req.body;
  const query = req.query;
  const common = Object.assign(params, body, query);
  console.log('common: ', common);
  // 检测参数属否为undefined
  for (let i in common) {
    if (typeof common[i] === 'undefined') {
      return res.json({
        code: 90005,
        msg: '检测参数属否正确时,' + i + ' 参数为undefined',
        detail: common
      });
    }
    // 检测参数属否为null
    if (common[i] === null) {
      return res.json({
        code: 90006,
        msg: '检测参数属否正确时,' + i + ' 参数为null',
        detail: common
      });
    }
    // 检测参数是否为空字符串 ''
    if (common[i] === '') {
      return res.json({
        code: 90007,
        msg: '检测参数属否正确时,' + i + ' 参数为空字符串',
        detail: common
      });
    }
    // 检测参数是否为空数组 []
    if (common[i] === '') {
      return res.json({
        code: 90008,
        msg: '检测参数属否正确时,' + i + ' 参数为空数组',
        detail: common
      });
    }
  }

  return next();
}

module.exports = checkParameters;

函数详解

1. 特殊参数的处理

这个模块,首先对特殊参数做处理。special 这个参数是一个数组,格式如下:

[{
  eval: 'parseInt(req.query.id) > 1 && parseInt(req.query.id) < 10',
  key: 'id',
  type: 'query'
}, {
  eval: 'parseInt(req.query.age) === 11',
  key: 'age',
  type: 'query'
}]

key 属性的值必须和eval中对应,且type只能是 query/params/body。

  • 首先判断函数是否传入第四个参数,并且第四个参数是否为数组,如果这两个条件成立,说明有特殊参数需要处理。
  • 然后判断是否具有 eval, key, type 三个参数。eval 参数是一个参数条件表达式,如 ‘id>0’,最终通过 eval() 函数来执行这个表达式。key 表示参数属性名,type 表示是通过何种方式传递的参数,如 query/params/body。
  • 接下来判断 eval, key, type 三个参数自身是否为 undefined 或 null
  • 再然后判断 判断 key 和 eval 是否匹配。key 和 eval 的匹配,主要是为了后面从一般参数中删除特殊参数
  • 再执行 eval 表达式,判断表达式是否成立,即特殊参数值是否合法
  • 如果以上都为真,说明特殊参数合法,最后则使用 delete 从参数中删除特殊参数

2. 普通参数处理

处理完毕特殊参数后,就需要处理普通参数。普通参数只需要满足:

  • 不为 undefined
  • 不为 null
  • 不为空字符串 ''
  • 不为空数组 []

普通参数,即 req.body req.params req.query 中的所有非特殊参数。

除了在判断特殊参数的时候,需要 delete 特殊参数,还需要 delete req.params['0']。因为对于 req.params,当 URL 没有 path 部分的时候,req.params['0']''

处理完毕之后,若所有参数都合法,则返回 next()

模块使用

1. 创建没有挂载路径的中间件

首先在 Express 项目入口文件,创建一个参数处理中间件:

// app.js
const checkParameters = require('./checkParameters');
// ...
app.use(function(req, res, next) {
  checkParameters(req, res, next);
});

注意这里的 app.use() 需要放在创建具体路由中间件如app.use('/', index) 之前。

当 Express 接收到 HTTP 请求后,首先该中间件会检测参数是否合法(只判断是否为 null 或 undefine 或 “ 或 [])。如果不合法,则直接返回;如果合法,则程序继续执行,由匹配该请求路径的路由继续完成该请求。

2. 创建挂载路径的中间件

如果需要对某个路由的特殊参数做处理,则需要创建一个挂载路径的中间件。

比如为们要判断 /index 路由的 GET 请求中的 id 参数,其中 id 的取值范围是 1-100,URL 形式如 http://localhost:3000/index?id=10,则可以这样使用该模块:

// index.js
const checkParameters = require('./checkParameters');

// 挂载路径的中间件
router.get('/index', function(req, res, next) {
  const special = {
    eval: 'parseInt(req.query.id) >= 1 && parseInt(req.query.id) <= 100',
    key: 'id',
    type: 'query'
  };
  checkParameters(req, res, next, special);
});
// 正常逻辑处理
router.get('/index', function(req, res, next) {
  // 正常逻辑
});

当 Express 接收到一个 HTTP 请求的时候,首先还是没有挂载路径的中间件先处理请求,检测参数;然后该挂载路径的中间件再检测参数;最后才执行正常处理逻辑。


Github Issue: https://github.com/nodejh/nodejh.github.io/issues/5