NodeJS RESTful API - 如何正确处理“未定义”请求变量?

Posted

技术标签:

【中文标题】NodeJS RESTful API - 如何正确处理“未定义”请求变量?【英文标题】:NodeJS RESTful API - How to handle 'undefined' request variables properly? 【发布时间】:2019-03-09 09:58:51 【问题描述】:

我正在使用 NodeJS 和 Express 开发一个 RESTful API。 我注意到传入的请求有时缺少一些预期的变量,这会导致程序崩溃,说它无法将变量的值设置为 'undefined' 值 - 因为请求没有到达值。 示例: 应用程序需要 variableY,但正在发送 variableX:

 formData:  variableX: 'valueX' 

程序期望接收变量Y,代码如下:

const checkVariables = Joi.validate( 
    variableY: req.body.variableY,
, schema);

应用程序崩溃并出现以下错误:

TypeError: Cannot read property 'variableY' of undefined

我想了一些方法来处理这个问题,包括在应用程序启动时声明变量并一起使用它们,使用try-catch。 另一种方法是使用if-elseif-chainingcase-switch,但正如你所理解的,我当然正在寻找实现这一目标的最干净的方法。 有什么想法吗?

谢谢。

** 编辑 ** 仅使用对象取得进展并设法实现结果。一旦试图到达它的任何内部字段,无论如何都会抛出错误,例如:if(req.body.variableY == undefined)console.log('The expected variable is undefined'); //true

当验证处理“未定义”对象内的字段时:if(req.body.variableY.dataId == undefined)console.log('The expected variable is undefined'); //crashes 再次抛出以下错误:TypeError: Cannot read property 'variableX' of undefined

在做了更多的挖掘之后,找到了这个 *** 线程:How to check if object property exists with a variable holding the property name? 尝试使用 hasOwnProperty,但抛出相同类型的错误:TypeError: Cannot read property 'hasOwnProperty' of undefined

尝试使用try-catch 包装变量声明,仍然没有用:

try
    var variableX = req.body.variableX
    var variableXDataId = req.body.variableX.dataId

catch(e)
    res.status(400).send('Wrong request error: Please check your request variables and try again');

因为这是一个非常基本的验证,应该由大多数 RESTful API 解决(验证您在请求中获得了预期的传入变量,因此程序不会因出现无法处理的错误而崩溃 - 什么此类问题(预期/意外请求验证)的常见解决方案是什么?

谢谢。

【问题讨论】:

【参考方案1】:

解决方案是设置一个默认的空对象来替换父级的 undefined:

// checking for body.variableX.variableZ with object destructuring ES6
const body =  = request;
const variableX = , variableY = body;
const variableZ = variableX.variableZ;

// or prior ES6
var body = request.body || ;
var variableX = body.variableX || ;
var variableY = variableX.variableY;

// or in a statement
var variableY = request.body && request.body.variableX ? request.body.variableX.variableY : undefined;

基于此,您可以创建自己的函数,例如 getValue(request, 'body.variableX.variableY'),如果任何父级或最终值未定义,则返回 null:

// asumes the value in the path is either object or undefined
function getValue(rootObj, path = '') 
    const parts = key.split('.');
    let value = rootObj || ;
    let part;
    while ((part = parts.shift()) && value !== null) 
        value = value[part] || null;
    
    return value;
;

【讨论】:

【参考方案2】:

您可以使用快速验证器https://www.npmjs.com/package/express-validator 验证传入的请求。然后将其添加到您的控制器,其中 a、b、c、d 是您要验证的参数

const nonEmptyFields = ['a', 'b', 'c', 'd'];

  nonEmptyFields.forEach(field => req.assert(field, `$field cannot be blank`).notEmpty());

  const errors = req.validationErrors();

  if (errors) 
    return res.status(400).send(errors);
  

要验证字段内的字段,您可以尝试这样做

typeof(req.body && req.body.name !== undefined)

【讨论】:

谢谢linux。那么字段中的字段呢?例如 req.body.variableX.dataId?【参考方案3】:

您可以采取另一种方法,在到达checkVariables之前检查req.body

let body = req.body;

// data - your req.body
// requiredKeys - is an array of strings , [ key1, key2 ... keyN]  | string[]

     const setKeys = ( data, requiredKeys )=>

         if( !typeof requiredKeys.length )
            requiredKeys = [];
         

         if(requiredKeys.length) requiredKeys.forEach( k =>

             k = k.replace(/\+/g,'/');

             let keysList = [];

             if( /\/+/g.test(k))
               keysList = k.split('/');
             else
              keysList = [k];
             

             let [firstKey, ...rest] = keysList;

             if( typeof data[firstKey] === 'undefined' )
               data[firstKey] = ;
             

             if( rest.length )

                data[firstKey] = setKeys(data[firstKey], [rest.join('/')] );

             

         )

         return data;

      

let checkedData= setKeys(body, ['variableT','variableP/noname/emptyObj','custom/object/does/not/exist/but/it/will/be/created/here']);

const checkVariables = Joi.validate(checkedData, schema);

更新

您将在下面找到一个工作示例,说明在 /(比如说 /usersStatus/:id )请求期间事情应该如何工作:

const express = require('express')
const app = express()
const port = 3000

const setKeys = (data, requiredKeys) => 

  if (!typeof requiredKeys.length) 
    requiredKeys = [];
  

  if (requiredKeys.length) requiredKeys.forEach(k => 

    k = k.replace(/\+/g, '/');

    let keysList = [];

    if (/\/+/g.test(k)) 
      keysList = k.split('/');
     else 
      keysList = [k];
    

    let [firstKey, ...rest] = keysList;

    if (typeof data[firstKey] === 'undefined') 
      data[firstKey] = ;
    

    if (rest.length) 

      data[firstKey] = setKeys(data[firstKey], [rest.join('/')]);

    

  )

  return data;



/**
 * Mock some data
 */
const getUserData = (req, res, next) => 

  if (typeof req.body === 'undefined') 
    req.body = ;
  

  req.body = 
    variableY: 
      someName: 23
    ,
    variableZ: 
      name: 3,
      type: 
        id: 5,
        typeName: 'something',
        tags: ['a', 'b', 'c']
      
    
  ;

  console.log('Middleware 1 getUserData');

  next();



/**
 * 1. Setup our middleware for checking keys
 *    "requiredKeys" is an array of strings
 */
const middlewareSetKeys = (requiredKeys, wrappedMiddleware) => 

  return (req, res, next) => 

    console.log('Middleware 2 middlewareSetKeys');

    if (typeof req.body === "undefined") 
      console.log('Leaving Middleware 2 since we don\'t have req.body');
      next();
    

    /**
     *  Update "req.body" with keys that we want to have available
     *  in our next middleware
     */
    req.body = setKeys(req.body, requiredKeys);

    if (typeof wrappedMiddleware === 'function') 

      return wrappedMiddleware.call(this, req, res, next);

     else 
      next();
    

  



/**
 *  2. Let's assume a "user status" situation
 *      2.1.  We need userInfo from database
 *      2.2.  Some info won't be retrieved, unless the user accesed some parts of the website to trigger some mechanisms that allows those fields to be exposed, therefore the lack of keys
 *      2.3.  But we know those keys/objects, and we still want to be present so our code won't crash.
 */

// lets call our getUserData
app.get(
  '/', // this path is for some userInfo
  getUserData, // this returns userInfo and appends it to `req.data`
  middlewareSetKeys([
    'userActivity/daily/jobs', // these won't exist in getUserData because the user is lazy and he didn't apply for any JOBS
    'userStatus/active/two-weeks-ago', // these won't exist in getUserData because the user joined two days ago. BUT WE STILL NEED IT coz reazons.
  ]), // We set our desired-later-to-use keys
  (req, res, next) => 

    /**
     * 3. Now our req.body will have our keys 
     *    even if they didn't exist in the getUserData middleware
     */
    console.log('Middleware 3 Your middleware');

    console.log(req.body);
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify(req.body, null, 2))

  )

app.listen(port, () => console.log(`Example app listening on port $port!`))

【讨论】:

它仅适用于'top'对象,一旦您尝试验证未定义对象内的字段,再次引发以下错误:TypeError: Cannot read property 'variableY' of undefined 找到此帖子后:[@ 987654321@ 尝试使用 hasOwnProperty,但抛出了相同类型的错误:TypeError: Cannot read property 'hasOwnProperty' of undefined 无论如何要克服这个问题?这是验证传入请求的基础。 我已经通过深度对象扫描更新了我的答案。根据您的编辑,您似乎需要已经有一个必填字段/键的列表并根据该列表检查req.body,您不能只调用任何未定义的键而不让脚本检查它们。

以上是关于NodeJS RESTful API - 如何正确处理“未定义”请求变量?的主要内容,如果未能解决你的问题,请参考以下文章

Nodejs RESTFul架构实践之api篇(转)

nodejs + mongodb 编写 restful 风格博客 api

RESTful API Design With NodeJS & Restify

使用 restful API 在 nodejs 上实现微风

如何正确使用Hadoop YARN Restful api提交spark应用

nodejs express restful api插件restframework-express使用