user826955
user826955

Reputation: 3206

How to make requests timeout after x seconds node.js/express

My middleware layout looks approx like this:

let express = require('express');
let app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

app.use(myBigMiddleware);   // this will call 3rd party service and can time-out
app.use(myAfterwardsMiddleware);
[maybe more post-processing middleware]

What I want to achieve is that any request which is not answered within XX seconds should be canceled + some HTTP error should be send to the browser/client.

I found https://github.com/expressjs/timeout which seems to do this, but somehow I am too stupid to use it. I fail to understand how I could cancel the middleware-pipe and instead do something like res.status(503).end();.

Adjusting the above:

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

app.use(timeout(5000));
app.use(haltOnTimedout);

function haltOnTimedout(req, res, next){
   if (!req.timedout) next();
   else console.log('REQUEST TIMEOUT');
}

app.use(myBigMiddleware);   // this will call 3rd party service and can time-out
app.use(myAfterwardsMiddleware);
[maybe more post-processing middleware]

will give me the following output:

events.js:176
    domain.enter();
       ^

TypeError: domain.enter is not a function
    at IncomingMessage.emit (events.js:176:12)
    at Timeout._onTimeout     (/home/user/dev/node_modules/connect-    timeout/index.js:43:11)
    at tryOnTimeout (timers.js:232:11)
    at Timer.listOnTimeout (timers.js:202:5)
*app died*

I will get the same result when I put app.use(haltOnTimedout); at the very end of the middleware-chain.

What am I doing wrong? Or where/how should I plug the timeout-middleware to catch the timeout and send my HTTP response, canceling all following middlewares? :/


[edit] I found that app.use(myBigMiddleware); might cause this, since when I replace this with a plain app.use(()=>{}); I will get the following in both console & browser (with the app still running):

ServiceUnavailableError: Response timeout
    at IncomingMessage.<anonymous> (/home/user/dev/node_modules/connect-timeout/index.js:70:8)
    at emitOne (events.js:96:13)
    at IncomingMessage.emit (events.js:188:7)
    at Timeout._onTimeout (/home/user/dev/node_modules/connect-timeout/index.js:43:11)
    at tryOnTimeout (timers.js:224:11)
    at Timer.listOnTimeout (timers.js:198:5)

This is somewhat closer to what I need, and indeed I can now add the following middleware to the end of the stack to achieve what I want:

app.use(function(err, req, res, next) {
    if (req.timedout) {
        res.status(503).end();
    } else {
        res.status(err.status || 500).end();
    }
});

But this leaves me unable to use my middleware. Anyone know what could be causing this TypeError: domain.enter is not a function above? :(


Solution

It turned out that I had several additional middlewares after the timeout-middleware, which was causing this error. I did not find the root cause, however I ended up adding this middleware:

    function timeout(ms) {
        let id = null;
        return function(req, res, next) {
            id = setTimeout(() => { if (!res.headersSent) res.status(503).end(); }, ms);

            res.on('finish', () => { clearTimeout(id); });
            next();
        };
    };

This does in fact what I originally wanted, no matter how many middlewares are chained up.

Upvotes: 0

Views: 1187

Answers (1)

DrakaSAN
DrakaSAN

Reputation: 7853

You can use async.race:

app.get('/whatever', (req, res) => {
    async.race([
        (callback) => {
            yourRealFunction(() => {
                callback(null, 'OK');
            });
        },
        (callback) => {
            setTimeout(() => {
                callback(null, 'Timed out');
            }, 5000);
        }
    ], (error, result) => {
        if(result === 'Timed out') {
            res.status(503).end();
        } else {
            res.status(200).end();
        }
    });
});

Of course, you can put that in a middleware, or in its own function to prevent code repetition.

Upvotes: 1

Related Questions