Reputation: 3206
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? :(
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
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