简体   繁体   中英

Node.js - Serving error 500 pages on unexpected errors

I'm trying to serve 500 pages (some generic HTML that says "500 - internal server error") from my Node.js server to requests that failed to resolve due to developer bugs, but can't find an elegant way to do this.

Lets say we have the following index.js, where a developer innocently made a mistake:

const http = require('http');
const port = 12345;

http.createServer(onHttpRequest).listen(port);

function onHttpRequest(req, res) {
    var a = null;
    var b = a.c; // this is the mistake

    res.end('status: 200');
}

Trying to access property "c" of null throws an error, so "res.end" will never be reached. As a result, the requesting client will eventually get a timeout. Ideally, I my server to have code that can catch errors like this, and return 500 pages to the requesting client (as well as email an administrator and so on).

Using "try catch" in every single block is out of the question. Most Node.js code is async, and a lot of the code relies on external libraries with questionable error handling. Even if I use try-catch everywhere, there's a chance that an error would happen in an external library that didn't have a try-catch block inside of it, in a function that happens asynchronously, and thus my server will crash and the client would never get a response.

Shortest example I can provide:

/* my server's index.js */

const http = require('http');
const poorlyTestedNpmModule = require('some-npm-module');
const port = 12345;

http.createServer(onHttpRequest).listen(port);

function onHttpRequest(req, res) {
    try {
        poorlyTestedNpmModule(null, onResult);
    }
    catch(err) {
        res.end('status: 500');
    }

    function onResult(err, expectedResult) {
        if(err) {
            res.end('status: 400');
        }
        else {
            res.end('status: 200');
        }
    }
}

/* some-npm-module.js */

module.exports = function poorlyTestedNpmModule(options, callback) {
    setTimeout(afterSomething, 100);

    function afterSomething() {
        var someValue = options.key; // here's the problem
        callback(null, someValue);
    }
}

Here, the server crashes, due to a function call that led to code that asynchronously throws an error. This code is not code that I control or wish to modify; I want my server to be able to handle all those errors on its own.

Now, I could, for instance, just use the global uncaughtException event, ie: process.on('uncaughtException', doSomething);

but then I have no access to the (req, res) arguments, making it impossible to call res.end for the correct res instance; the only way to have access to them, is to store them in a higher-scope object for each incoming request, and then prune them on successful request resolutions, then mark existing [req, res] stored pairs as "potentially errored" whenever an uncaughtException triggers, and serve 500 pages to those requests whenever the count of currently-active requests matches the count of currently-unresolved-errors (and re-test that count per thrown uncaught expection and per successful res.end call).

Doing that works, but... it's ugly as hell. It means that request objects have to be leaked to the global scope, and it also means that my router module now has a dependency on the uncaughtException global event, and if any other code overwrites that event, everything breaks, or if I ever want to handle other uncaught exceptions for whatever reason, I'll run into cross dependency hell.

The root cause of this problem is that an unexpected error can happen anywhere, but I want to specifically catch whether an unexpected error originated from a stack trace that began from an incoming http request (and not, for example, from some interval I have running in the background, because then I get an unexpected error but obviously don't want to serve a 500 page to anyone, only email an admin with an error log), and on top of needing to know whether the error originated from an http request, I need to have access to the request+response objects that node server objects provide.

Is there no better way?

[Edit] The topic of this question is role distribution in modules.

ie, one guy is making base code for a server, lets say a "router module". Other people will add new code to the server in the future, handling branches that are routed to.

The guy that writes the base server code has to write it in a way that it will serve 500 pages if any future code is written incorrectly and throws errors. Help him accomplish his goal.

Answers of the format "make sure all future people that add code never make mistakes and always write code that won't throw uncaught errors" will not be accepted.

At first, using uncaughtException in Nodejs is not safe. If you feel that there is no other option in your application, make sure that you exit the process in the handler of 'uncaughtException' and restart the process using pm2 or forever or someother modules. Below link can provide you its reference.

Catch all uncaughtException for Node js app

Coming to the process of error handling, as mentioned, you may always miss to handle errors with callback. To avoid, these we can use an exceptional advantage of promises in nodejs.

/* my server's index.js */

const http = require('http');
const poorlyTestedNpmModule = require('some-npm-module');
const port = 12345;

http.createServer(onHttpRequest).listen(port);

function onHttpRequest(req, res) { 

   try {
            poorlyTestedNpmModule(null)
            .then(result => {
                res.end('status: 200');
            })
            .catch(err =>{
              console.log('err is', err);
              res.end('status: 400');
            })
       }
    catch(err) {
                res.end('status: 500');
    }

}


/* some-npm-module.js */

module.exports = function poorlyTestedNpmModule(options, callback) {
    setTimeout(afterSomething, 100);

   afterSomthing = new Promise((resolve, reject)=> {
       var someValue = options.key; // here's the problem
       resolve(someValue);

   })
}

If you see that some of the npm nodemodules are not present with promise, try to write wrappers to convert callback to promise model and use them in your application.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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