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

时间:2016-09-20 17:04:36

标签: node.js hapijs

鉴于NodeJS应用程序通过Hapi提供API,我如何捕获所有错误,以便将它们传达给用户?当发生致命错误时,我需要能够向任何向我们的服务器发出请求的客户端发送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

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

在我运行应用程序的终端窗口中,我看到数据库连接已经过时,导致错误:

  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应用程序在错误发生时自己发送错误消息发生。

我们已经使用“好”来捕获大多数错误:

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

我们注册为:

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

但无论出于何种原因,这个错误都没有被发现。

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

1 个答案:

答案 0 :(得分:0)

你能发布路线代码吗?听起来你在回调中或在承诺链的末尾调用reply。当错误发生时reply没有被调用。

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

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

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

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

  reply(p);
}

我不知道这是否符合您自动捕获错误的请求,但从Hapi的角度来看,没有错误,reply从未被调用过。

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

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

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

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

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