简体   繁体   English

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

[英]Node.js Express app handle startup errors

I have app in Node.js and Express.我在 Node.js 和 Express 中有应用程序。 I need to write tests for it.我需要为它编写测试。 I have a problem with handling Express app errors.我在处理 Express 应用程序错误时遇到问题。 I found this How do I catch node.js/express server errors like EADDRINUSE?我发现了这个如何捕获像 EADDRINUSE 这样的 node.js/express 服务器错误? , but it doesn't work for me, I don't know why. ,但它对我不起作用,我不知道为什么。 I want to handle errors, which can occured while expressApp.listen() is executing (EADDRINUSE, EACCES etc.).我想处理在执行 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

Any ideas?有任何想法吗?

This should do the trick:这应该可以解决问题:

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

What listener.listen actually does is create a HTTP server and call listen on it: listener.listen实际上做的是创建一个 HTTP 服务器并在其上调用 listen:

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

First off, expressJS does not throw the uncaughtException event, process does, so it's no surprise your code doesn't work.首先,expressJS 不会抛出uncaughtException事件,process 会抛出,因此您的代码不起作用也就不足为奇了。

So use: process.on('uncaughtException',handler) instead.所以使用: process.on('uncaughtException',handler)代替。

Next, expressJS already provides a standard means of error handling which is to use the middleware function it provides for this purpose, as in:接下来,expressJS 已经提供了一种标准的错误处理方法,即使用它为此提供的中间件功能,如:

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

This function returns an error message to the client, with optional stacktrace, and is documented at connectJS errorHandler .此函数向客户端返回一条错误消息,带有可选的堆栈跟踪,并记录在connectJS errorHandler中。

(Note that errorHandler is actually part of connectJS and is only exposed by expressJS.) (请注意,errorHandler 实际上是 connectJS 的一部分,仅由 expressJS 公开。)

If the behavior the existing errorHandler provides is not sufficient for your needs, its source is located at connectJS's errorHandler middleware and can be easily modified to suit your needs.如果现有的 errorHandler 提供的行为不能满足您的需求,其源位于connectJS 的errorHandler中间件,可以轻松修改以满足您的需求。

Of course, rather than modifying this function directly, the "correct" way to do this is to create your own errorHandler, using the connectJS version as a starting point, as in:当然,与其直接修改这个函数,“正确”的方法是创建你自己的 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.
};

And install it into expressJS as:并将其安装到 expressJS 中:

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

See Just Connect it, Already for an explanation of connectJS's idea of filter and provider middleware and How To Write Middleware for Connect/Express for a well-written tutorial.请参阅Just Connect it,已经了解了 connectJS 的filterprovider中间件的想法,以及如何为 Connect/Express 编写中间件以获得编写良好的教程。

You might also find these useful:这些对你也可能有用:

Finally, an excellent source of information regarding testing expressJS can be found in its own tests.最后,可以在其自己的测试中找到有关测试 expressJS 的极佳信息来源。

Mention: Marius Tibeica answer is complete and great, also david_p comment is.提及: Marius Tibeica的回答是完整而伟大的, david_p评论也是。 As too is Rob Raisch answer (interesting to explore). Rob Raisch 的回答也是如此(探索很有趣)。 https://stackoverflow.com/a/27040451/7668448 https://stackoverflow.com/a/27040451/7668448
https://stackoverflow.com/a/13326769/7668448 https://stackoverflow.com/a/13326769/7668448

NOTICE注意

This first method is a bad one!第一种方法很糟糕! I leave it as a reference!我把它留作参考! See the Update section!请参阅更新部分! For good versions!为了好版本! And also for the explanation for why!以及为什么的解释!

Bad version坏版本

For those who will find this useful, here a function to implement busy port handling (if the port is busy, it will try with the next port, until it find a no busy port)对于那些会觉得这很有用的人,这里有一个函数来实现繁忙的端口处理(如果端口繁忙,它将尝试下一个端口,直到找到一个不繁忙的端口)

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);

The function listen is recursively calling itself.函数 listen 递归调用自身。 In case of port busy error.如果出现端口繁忙错误。 Incrementing the port number each time.每次递增端口号。

update Completely re-done更新完全重做

Callback full version回调完整版

First of all this version is the one that follow the same signature as nodejs http.Server.listen() method!首先,这个版本是遵循与 nodejs http.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;
}

Signature:签名:

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

just as per就像按照

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

The callback signature is changed to回调签名更改为

(port) => void (端口)=>无效

usage:用法:

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);
});

Explanation解释

In contrary to the old example!与旧例相反! This method doesn't call itself!此方法不会调用自身!

Key elements:关键要素:

  • app.listen() first call will return a net.Server instance app.listen() 第一次调用将返回一个 net.Server 实例
  • After binding an event once, calling listen again into the same net.Server instance will attempt reconnecting!绑定一次事件后,再次调用 listen 到同一个 net.Server 实例将尝试重新连接!
  • The error event listener is always there!错误事件侦听器始终存在!
  • each time an error happen we re-attempt again.每次发生错误时,我们都会重新尝试。
  • the port variable play on the closure to the callback.端口变量在回调的闭包上播放。 when the callback will be called the right value will be passed.当调用回调时,将传递正确的值。

Importantly重要的

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

Why we are skipping the callback here??为什么我们在这里跳过回调?

The callback once added.添加后的回调。 It's hold in the server instance internally on an array!它保存在数组内部的服务器实例中! If we add another!如果我们再加一个! We will have multiple triggers!我们将有多个触发器! On the number of (attempts + 1).关于(尝试+ 1)的次数。 So we only include it in the first attempt!所以我们只在第一次尝试中包含它!

That way we can have the server instance directly returned!这样我们就可以直接返回服务器实例! And keep using it to attempt!并继续使用它来尝试! And it's done cleanly!而且做得很干净!

Simple version port only仅限简单版本端口

That's too can help to understand better at a glimpse这也有助于一目了然地更好地理解

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;
}

Here the parameter port variable play on the closure!这里把参数端口变量玩上了闭包!

ES6 full version 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;
}

Why the old version is bad为什么旧版本不好

To say right it's not really!说对了还真不是! But with the first version!但是对于第一个版本! We call the function itself at every failure!我们在每次失败时调用函数本身! And each time it create a new instance!每次它都会创建一个新实例! The garbage collector will budge some muscles!垃圾收集器会让一些肌肉动起来!

It doesn't matter because this function only execute once and at start!没关系,因为这个函数只在开始时执行一次!

The old version didn't return the server instance!旧版本没有返回服务器实例!

Extra (for @sakib11)额外的(@sakib11)

You can look at @sakib11 comment to see what problem he fall in!你可以看看@sakib11 的评论,看看他遇到了什么问题! It can be thoughtful!可以考虑周到!

Also in the comment i mentioned promise version and closure getter pattern!在评论中我还提到了 promise 版本和闭包 getter 模式! I don't deem them interesting!我不认为他们有趣! The way above just respect the same signature as nodejs!上面的方式只是尊重与 nodejs 相同的签名! And too callback just do fine!而且回调就好了! And we are getting our server reference write away!我们正在将我们的服务器引用写掉! With a promise version!有承诺的版本! A promise get returned and at resolution we pass all the elements!返回一个承诺,并且在决议中我们通过了所有元素! serverInstance + port!服务器实例+端口!

And if you wonder for the closure getter pattern!如果您想知道闭包吸气剂模式! (It's bad here) (这里不好)

Within our method we create a ref that reference the server instance!在我们的方法中,我们创建了一个引用服务器实例的 ref! If we couldn't return the server instance as we are doing (imaging it was impossible! So each time a new instance is created! The pattern consist of creating a closure (method at that scope) And return it!如果我们不能像我们正在做的那样返回服务器实例(想象这是不可能的!所以每次创建一个新实例!该模式包括创建一个闭包(该范围内的方法)并返回它!

so for usage所以为了使用

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

But it's just overhead specially we need to wait for the server to be done!但这只是开销,特别是我们需要等待服务器完成! Unless we set it in a way that it use a callback!除非我们以使用回调的方式设置它! or return a promise!或回报承诺!

And it's just over complicating!而且这太复杂了! And not good at all!而且一点都不好!

It's just because i mentioned it!只是因为我提到了它!

And the method above can be tweaked!上面的方法可以调整! To add number of attempts limit!添加尝试次数限制! And add some events or hooks!并添加一些事件或钩子! But well!但是好吧! Generally we only need a simple function that just attempt and make it!一般我们只需要一个简单的函数就可以尝试实现了! For me the above is more then sufficient!对我来说,以上就足够了!

Good links好的链接

From the doc从文档

The app.listen() method returns an http.Server object and (for HTTP) is a convenience method for the following: app.listen() 方法返回一个 http.Server 对象,并且(对于 HTTP)是以下内容的便捷方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM