如何捕获 Hapi NodeJS 应用程序中的每个致命错误,并向客户端发送 500 错误?

How to catch every fatal error in a Hapi NodeJS app, and send a 500 error to clients?

给定一个通过 Hapi 服务 API 的 NodeJS 应用程序,我如何捕获所有错误以便将它们传达给用户?当发生致命错误时,我需要能够向任何向我们的服务器发出请求的客户端发送 500 错误。

我们认为我们已经设置好一切以正确捕获所有错误,但这个错误完全没有发生。

我 "git clone" 从 Github 获取应用程序,然后 "npm install" 然后 "npm run"。然后我用这个应用程序工作了几个小时。然后我合上笔记本电脑回家了。几个小时后,我打开笔记本电脑,想继续工作。但是与外部数据库的连接已经失效。这当然有道理,但我们的应用程序应该已将问题传达给客户。相反,当我 运行 cURL:

  curl "http://nefers-mbp.home:3000/profile/Company/31468" -X GET --header 'Accept: application/json' --header 'x-api-key: 2gt7Pt2LU194KKcNnXHRc564JU'

90 秒后达到默认的 cURL 超时:

  curl: (52) Empty reply from server

这对我们来说是不能接受的。我希望立即发送错误信息。

在我 运行 应用程序所在的终端 window 中,我看到数据库连接已失效,这导致错误:

  160920/111554.302, [ops], memory: 49Mb, uptime (seconds): 24285.523, load: 1.6513671875,2.2822265625,3.7041015625
  { profile_type: 'Company', id: '31468' }
  { method: 'select',
    options: {},
    bindings: [ '2gt7Pt2LU194KKcNUxJU' ],
    sql: 'select `user_id`, `action`, `permission` from `api_permissions` where `api_key` = ?' }
  160920/11124.308, [ops], memory: 49Mb, uptime (seconds): 24315.529, load: 1.55640625,2.204105625,3.6259765625
  Knex:Error Pool2 - Error: Pool.release(): Resource not member of pool
  { Error: read ETIMEDOUT
      at exports._errnoException (util.js:1026:11)
      at TCP.onread (net.js:564:26)
      --------------------
      at Protocol._enqueue (/Users/lsam/projects/mattermare/api/node_modules/mysql/lib/protocol/Protocol.js:141:48)
      at Connection.query (/Users/lsam/projects/mattermare/api/node_modules/mysql/lib/Connection.js:201:25)
      at /Users/lsam/projects/mattermare/api/node_modules/knex/lib/dialects/mysql/index.js:92:18
      at tryCatcher (/Users/lsam/projects/mattermare/api/node_modules/bluebird/js/main/util.js:26:23)
      at Promise._resolveFromResolver (/Users/lsam/projects/mattermare/api/node_modules/bluebird/js/main/promise.js:483:31)
      at new Promise (/Users/lsam/projects/mattermare/api/node_modules/bluebird/js/main/promise.js:71:37)
      at Client._query (/Users/lsam/projects/mattermare/api/node_modules/knex/lib/dialects/mysql/index.js:88:12)
      at Client.query (/Users/lsam/projects/mattermare/api/node_modules/knex/lib/client.js:127:24)
      at Runner.<anonymous> (/Users/lsam/projects/mattermare/api/node_modules/knex/lib/runner.js:116:24)
      at Runner.tryCatcher (/Users/lsam/projects/mattermare/api/node_modules/bluebird/js/main/util.js:26:23)
      at Runner.query (/Users/lsam/projects/mattermare/api/node_modules/bluebird/js/main/method.js:15:34)
      at /Users/lsam/projects/mattermare/api/node_modules/knex/lib/runner.js:44:21
      at /Users/lsam/projects/mattermare/api/node_modules/bluebird/js/main/using.js:176:30
      at bound (domain.js:280:14)
      at runBound (domain.js:293:12)
      at tryCatcher (/Users/lsam/projects/mattermare/api/node_modules/bluebird/js/main/util.js:26:23)
    code: 'ETIMEDOUT',
    errno: 'ETIMEDOUT',
    syscall: 'read',
    fatal: true }

控制台中的这个错误是即时的,但没有传达给等待中的 cURL 请求。这就是我要解决的问题。

我去寻找有帮助的 npm 包,我发现了这个:

https://www.npmjs.com/package/hapi-error

但显然,这只会使错误消息变得漂亮。我们的问题更为根本:我们未能捕捉到这个致命错误。

服务器会自动修复,所以下次我调用 cURL 时它有一个活动的数据库连接,所以下一次调用效果很好。

但是我们需要捕获每一个错误,无论是什么原因,并确保客户端收到合理的消息。我们不能允许客户端只是超时的情况。

我确实知道,如果我在其前面有 Apache 或 Nginx,通过反向代理,它们可能会超时得更快,但作为正确性问题,我们希望 NodeJS 应用程序在发生错误。

我们已经使用 "good" 捕获了最多的错误:

https://www.npmjs.com/package/good

我们注册为:

  {
      "plugin": {
      "register": "good",
        "options": {
            "opsInterval": 30000,
            "reporters": [
            {
                "reporter": "good-console",
                "events": {
                    "log": "*",
                    "ops": "*",
                    "request": "*"
                }
            }
          ]
        }
      }
    }

但是不知什么原因,这个错误没有被捕获。

有没有办法捕获每个错误,并向等待的客户端发送 500 错误?

你能post路线代码吗?听起来您在回调内或在承诺链的末尾调用 reply 。当错误发生时 reply 不会被调用。

如果你使用 promise,你可能会做这样的事情:

const myHandler = (req, reply) {
  knex('users')
    .first()
    .where('id', req.params.id)
    .then((user) => reply(formatUser(user))
}

如果数据库连接失败,则永远不会调用回复,因此服务器将永远等待。您可以修改它以在 promise 链的末尾添加一个 catch,即 .catch((e) => reply().code(500) 或者您可以 return 一个 promise,hapi 会将被拒绝的 promise 变成 500.

const myHandler = (req, reply) {
  const p = knex('users')
    .first()
    .where('id', req.params.id)
    .then((user) => formatUser(user))

  reply(p);
}

不知道这样是否满足你的自动捕获错误的要求,但是从Hapi的角度来看是没有错误的,reply从来没有被调用过。

如果您想为到 return 的路由设置超时,如果 reply 在几毫秒内未被调用,则为 503,您可以在路由选项中执行此操作:

server.route({
  method: 'GET',
  path: '/api/users/{userId}'',
  config: {
    timeout: {
      server: 10000
    }
  },
  handler: myHandler
});

或者在服务器上建立连接时在所有路由上设置超时:

server.connection({ port: 80, routes: { timeout: { server: 10000 } } });

设置超时是一种很好的做法,但保证您的处理程序以某种方式调用回复回调仍然很重要。这样你的路线会失败得更快。