Node.js Express 应用程序处理启动错误

Posted

技术标签:

【中文标题】Node.js Express 应用程序处理启动错误【英文标题】:Node.js Express app handle startup errors 【发布时间】:2012-10-30 15:31:15 【问题描述】:

我在 Node.js 和 Express 中有应用程序。我需要为它编写测试。我在处理 Express 应用程序错误时遇到问题。我找到了这个How do I catch node.js/express server errors like EADDRINUSE?,但它对我不起作用,我不知道为什么。我想处理 expressApp.listen() 执行时可能发生的错误(EADDRINUSE、EACCES 等)。

express = require('express')
listener = express()

#doesn't work for me
listener.on('uncaughtException', (err) ->
  #do something
)

#doesn't work too
listener.on("error", (err) ->
  #do something
)

#this works, but it caughts all errors in process, I want only in listener
process.on('uncaughtException', (err) ->
  #do something
)

listener.listen(80) #for example 80 to get error

有什么想法吗?

【问题讨论】:

listener.on 'error', ... 应该可以工作。即使该行存在,它是否只是进行正常的堆栈跟踪并崩溃? 是的,如果我这样做'listener.listen(80)',它会打印堆栈跟踪并崩溃。即使使用 'listener.on 'error', ...' 也许在这种情况下发生的错误不是 Express 错误,这就是它无法处理的原因。但这只是假设。 【参考方案1】:

这应该可以解决问题:

listener.listen(80).on('error', function(err)  );

listener.listen 实际上所做的是创建一个 HTTP 服务器并在其上调用监听:

app.listen = function()
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
;

【讨论】:

令人难以置信的是,这是最简单、最正确的答案,而且没有人赞成!诀窍是了解 webServer 对象是由 app.listen 返回的。我用这个:app.listen(80, function() console.log('success'); ).on('error', function(err) if (err.errno === 'EADDRINUSE') console.log('port busy');  else console.log(err); ); 我不明白你的代码在做什么。如果你调用 app.listen 节点已经在我认为的引擎盖下做了一个 http.createServer 或者你基本上覆盖了原生 app.listen() 方法并用你自己的代码覆盖它? 第一行代码是应该使用的,其他都是解释为什么会这样。【参考方案2】:

首先,expressJS 不会抛出 uncaughtException 事件,进程会,所以你的代码不起作用也就不足为奇了。

所以请改用:process.on('uncaughtException',handler)

接下来,expressJS 已经提供了一种标准的错误处理方法,即使用它为此目的提供的中间件函数,如下所示:

app.configure(function()
    app.use(express.errorHandler( dumpExceptions: true, showStack: true ));
);

此函数向客户端返回一条错误消息,带有可选的堆栈跟踪,并记录在 connectJS errorHandler。

(注意errorHandler其实是connectJS的一部分,只被expressJS暴露出来。)

如果现有的 errorHandler 提供的行为不足以满足您的需求,则其源位于 connectJS's errorHandler middleware 并且可以轻松修改以满足您的需求。

当然,与其直接修改这个函数,“正确”的做法是创建自己的errorHandler,以connectJS版本为起点,如:

var myErrorHandler = function(err, req, res, next)
    ...
    // note, using the typical middleware pattern, we'd call next() here, but 
    // since this handler is a "provider", i.e. it terminates the request, we 
    // do not.
;

并将其安装到 expressJS 中:

app.configure(function()
    app.use(myErrorHandler);
);

请参阅 Just Connect it, Already 了解 connectJS 对 filterprovider 中间件的想法的解释,以及 How To Write Middleware for Connect/Express 以获得编写良好的教程。

您可能还会发现这些有用:

How to handle code exceptions in node.js?

Recover from Uncaught Exception in Node.JS

最后,可以在its own tests. 中找到有关测试 expressJS 的极好信息来源

【讨论】:

感谢您提供如此详细的解释,无论如何它都会很有用。在处理 Web 请求时,我不需要处理错误。我需要同时创建服务器并调用 app.listen(...)。现在我明白这里需要 Node.js 错误处理,而不是 Express 中的中间件。 process.on('uncaughtException',handler) 在我的情况下是可以接受的。 @Piane_Ramso,然后是的,将您自己的事件处理程序添加到 process.on('uncaughtException') 绝对是要走的路。如果您很好奇,处理“未捕获”异常的底层代码位于(或附近)node.cc 中的第 1739 行(或附近) 天哪,这很复杂【参考方案3】:

提及:Marius Tibeica 回答完整而精彩,david_p 评论也是。 Rob Raisch 的回答也是如此(有趣的探索)。 https://***.com/a/27040451/7668448https://***.com/a/13326769/7668448

通知

第一种方法不好!我把它留作参考!请参阅更新部分!为了好版本!并解释原因!

 坏版本

对于那些会觉得这很有用的人,这里有一个实现繁忙端口处理的函数 (如果端口忙,它会尝试下一个端口,直到找到一个不忙的端口)

app.portNumber = 4000;
function listen(port) 
    app.portNumber = port;
    app.listen(port, () => 
        console.log("server is running on port :" + app.portNumber);
    ).on('error', function (err) 
        if(err.errno === 'EADDRINUSE') 
            console.log(`----- Port $port is busy, trying with port $port + 1 -----`);
            listen(port + 1)
         else 
            console.log(err);
        
    );


listen(app.portNumber);

函数listen递归调用自身。在端口繁忙错误的情况下。每次增加端口号。

更新完全重做

 回调完整版

首先这个版本是跟nodejshttp.Server.listen()方法签名相同的版本!

function listen(server) 
    const args = Array.from(arguments);
    // __________________________________ overriding the callback method (closure to pass port)
    const lastArgIndex = arguments.length - 1;
    let port = args[1];
    if (typeof args[lastArgIndex] === 'function') 
        const callback = args[lastArgIndex];

        args[lastArgIndex] = function () 
            callback(port);
        
    

    const serverInstance = server.listen.apply(server, args.slice(1))
        .on('error', function (err) 
            if(err.errno === 'EADDRINUSE') 
                console.log(`----- Port $port is busy, trying with port $port + 1 -----`);
                port += 1;
                serverInstance.listen.apply(serverInstance, [port].concat(args.slice(2, lastArgIndex)));
             else 
                console.log(err);
            
        );

    return serverInstance;

签名:

listen(serverOrExpressApp, [port[, host[, backlog]]][, callback])

按照

https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback

回调签名改为

(端口)=> 无效

用法:

const server = listen(app, 3000, (port) => 
    console.log("server is running on port :" + port);
);

// _____________ another example port and host
const server = listen(app, 3000, 'localhost', (port) => 
    console.log("server is running on port :" + port);
);

 解释

与旧示例相反!此方法不调用自身!

关键要素:

app.listen() 第一次调用会返回一个 net.Server 实例 绑定一次事件后,再次调用listen到同一个网络。服务器实例将尝试重新连接! 错误事件监听器始终存在! 每次发生错误时,我们都会重新尝试。 端口变量播放关闭回调!当回调将被调用时,将传递正确的值。

很重要

serverInstance.listen.apply(serverInstance, [port].concat(args.slice(2, lastArgIndex)));

为什么我们在这里跳过回调!?

回调一旦添加!它保存在阵列内部的服务器实例中!如果我们再添加一个!我们将有多个触发器!关于(尝试次数+ 1)的次数。所以我们只在第一次尝试中包含它!

这样我们可以直接返回服务器实例!并继续使用它来尝试!而且做得很干净!

仅简单版端口

这也有助于一瞥更好地理解

function listen(server, port, callback) 
    const serverInstance = server.listen(port, () =>  callback(port) )
        .on('error', function (err) 
            if(err.errno === 'EADDRINUSE') 
                console.log(`----- Port $port is busy, trying with port $port + 1 -----`);
                port += 1;
                serverInstance.listen(port);
             else 
                console.log(err);
            
        );

    return serverInstance;

这里参数端口变量play就关闭了!

ES6 完整版

function listen(server, ...args) 
    // __________________________________ overriding the callback method (closure to pass port)
    const lastArgIndex = args.length - 1;
    let port = args[0];
    if (typeof args[lastArgIndex] === 'function') 
        const callback = args[lastArgIndex];

        args[lastArgIndex] = function () 
            callback(port);
        
    

    const serverInstance = server.listen(server, ...args)
        .on('error', function (err) 
            if(err.errno === 'EADDRINUSE') 
                console.log(`----- Port $port is busy, trying with port $port + 1 -----`);
                port += 1;
                serverInstance.listen(...[port, ...args.slice(1, lastArgIndex)])
             else 
                console.log(err);
            
        );

    return serverInstance;

为什么旧版本不好

说对了,这不是真的!但是第一个版本!我们在每次失败时调用函数本身!并且每次它都会创建一个新实例!垃圾收集器会动一些肌肉!

没关系,因为这个函数只执行一次并且在开始时!

旧版本没有返回服务器实例!

额外(@sakib11)

你可以看看@sakib11 的评论,看看他遇到了什么问题!可以考虑周到!

我还在评论中提到了承诺版本和闭包吸气剂模式!我不认为他们有趣!上面的方式只是尊重与nodejs相同的签名!而且回调就好了!而且我们正在将我们的服务器参考写掉!带有承诺版本!一个承诺得到回报,我们通过了所有的元素!服务器实例 + 端口!

如果你想知道闭包吸气剂模式! (这里不好)

在我们的方法中,我们创建了一个引用服务器实例的 ref!如果我们不能像我们正在做的那样返回服务器实例(想象这是不可能的!所以每次创建一个新实例!该模式包括创建一个闭包(在该范围内的方法)并返回它!

所以使用

const getServer = listen(port, () => 
   console.log('Server running at port ' + getServer().address().port);
   const io = socketIo(getServer(), );
); 

但这只是开销,特别是我们需要等待服务器完成! 除非我们以使用回调的方式设置它!或返回一个承诺!

而且它只是过于复杂了!而且一点都不好!

只是因为我提到了它!

上面的方法可以调整!添加尝试次数限制!并添加一些事件或钩子!不过好!通常我们只需要一个简单的函数来尝试并实现它!对我来说,以上就足够了!

好的链接

https://nodejs.org/api/http.html#http_http_createserver_options_requestlistener

https://nodejs.org/api/http.html#http_class_http_server

https://expressjs.com/en/4x/api.html#app.listen

来自文档

app.listen() 方法返回一个 http.Server 对象,并且(对于 HTTP)是一种方便的方法:

app.listen = function () 
  var server = http.createServer(this)
  return server.listen.apply(server, arguments)

【讨论】:

完美解决我的问题,谢谢兄弟 很高兴听到这个。我的荣幸。谢谢 我正在尝试类似的东西,递归位的工作方式与此完全相同,但我也试图返回 app.listen 以将其传递给 socket.io,但由于某种原因它不起作用。我猜附加错误事件处理程序会返回不同的东西? 你是怎么返回值的!在我的示例中,如果我们返回第一个 app.listen().on(),我们将获得一个服务器实例!但是如果失败了!重试的回调之一将被调用!并且将创建一个新的服务器实例!所以这应该是你的问题! app.listen 和所有的 .on 链接都将返回一个 net.Server 实例!您可以将其附加到 socket.io!你主要有两种选择!使用 http.createServer。获取您的服务器实例!然后在我的函数中代替 app.listen 使用 server.listen!并将其附加到socket.io!你有参考! 否则,如果我们想在函数本身内执行它!那么我们有很多选择!使用 onSuccess 回调或 promise 可能是最简单的!您在解析时通过了成功连接的服务器!

以上是关于Node.js Express 应用程序处理启动错误的主要内容,如果未能解决你的问题,请参考以下文章

启动 node.js express 服务器作为服务

node.js + express.js:使用 mongodb/mongoose 处理会话

node.js 框架express关于报错页面的配置

Node.js 和 Express 会话处理 - 后退按钮问题

node.js 之express全局安装并且启动一个微服务

Node.js Express 应用程序在负载测试下花费太多时间来处理出站 HTTP 请求