Express timeout if endpoint is taking too long - javascript

I am trying to timeout a response back to the client if its taking too long on my server. But any solution I have found does not seem to trigger the timeout. This should timeout after 2 seconds, but for some reason my endpoint resolves with no timeout happening. Am I doing this correct?
app.use((req, res, next) => {
res.setTimeout(2000, () => {
console.log('Request has timed out.');
res.status(503).send('Service unavailable. Please retry.');
});
next();
});
// endpoints
...
// resolves after 5 seconds
app.get('/timeoutTest', (req, res) => {
const time = new Date().valueOf();
const futureTime = time + 5000;
while (new Date().valueOf() < futureTime) {
// nothing to do but wait
}
return res.status(200).send('5 seconds have passed since call');
});
res.setTimout does not seem to be triggering if it has the next() at the bottom of the middleware. However. If I comment out next() it does trigger my res.setTimeout after 2 seconds, but of course any http requests coming in wont get reached because there is no next()

There is an express module that will do this already: connect-timeout
Be aware - Node will continue to process your request, so this will not solve CPU usage issues etc.

Related

node.js express: Is there a way to cancel http response in middleware?

I am writing a timeout middleware with express in node.js.
app.use((req, res, next) => {
res.setTimeout(3000, () => {
console.warn("Timeout - response end with 408")
res.status(408).json({ "error": "timeout 408" });
// !!! error will happen with next function when call like `res.send()`:
// Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
next()
})
If there's an endpoint that takes more than 3000 ms, my middleware will repsond with 408. However, the next function will respond again. I don't want to check if the response has been already sent by res.headersSent api every time.
Is there a better way to handle this - like the title said - to cancel the next response in the middleware?
It's your own code in the response handler that is still running (probably waiting for some asynchronous operation to complete). There is no way to tell the interpreter to stop running that code from outside that code. Javascript does not have that feature unless you put that code in a WorkerThread or a separate process (in which case you could kill that thread/process).
If you're just trying to suppress that warning when the code eventually tries to send its response (after the timeout response has already been sent), you could do something like this:
app.use((req, res, next) => {
res.setTimeout(3000, () => {
console.warn("Timeout - response end with 408")
res.status(408).json({ "error": "timeout 408" });
// to avoid warnings after a timeout sent,
// replace the send functions with no-ops
// for the rest of this particular response object's lifetime
res.json = res.send = res.sendFile = res.jsonP = res.end = res.sendStatus = function() {
return this;
}
});
next();
});

Express.js: Do background task after sending response to client

It is a simple controller. Receive requests from users, do task and response to them. My goal is to make the response as soon as possible and do the task then so the user will not notice the lengthy waiting time.
Here is my code:
router.post("/some-task", (req, res, next) => {
res.send("ok!");
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5000); // lengthy task
});
When post to this controller, I need to wait 5000ms before getting the response, which is not I want. I have tried promise but don't work either. What is the correct way to do it?
Improve version for Evyatar Meged answer:
router.post("/some-task", (req, res, next) => {
res.send("ok!");
setTimeout(()=>{
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5000); // lengthy task
}, 0);
});
It is not about res.end, just setTimeout do the thing. But I don't recommend this since it will block the entire app for handling other requests. You may need to think to use child process instead.
You can have a callback after res.end that will execute as soon as the response was sent.
router.get('/', function(req, res, next) {
res.end('123', () => {
setTimeout(() => {console.log('456')}, 5000)
})
});
Notice you're browser will receive 123 and after 5 seconds your server will log 456.

Non-block setTimeout for web server

I have a web server that upon request makes a phone call, waits 3 seconds and then checks if that phone call is still ongoing. I used setTimeout to integrate this but this blocks all other connections to the web server until the timeout has finished.
// example get request
app.get("/", function(req, res) {
// take an action
example.makeCall(function() {
setTimeout(function() {
// check the action
example.checkCall(function() {
res.status(200)
})
}, 3000)
})
})
Is there some other way of adding a timeout to a request without blocking all other incoming requests?
Not sure why your additional API requests are being blocked, new API requests should use a new invocation of your route's callback function on the call stack and shouldn't be dependant on previous callback functions finishing to be added to the call stack.
The issue might not be that the setTimeout is blocking and may be another problem such as your phone call API blocking new calls being made before a previous call has finished.
But something to try could be to wait 3 seconds in a promise to try to get around potential blocking.
function waitThree() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve();
}, 3000);
});
}
app.get("/", function(req, res) {
// take an action
example.makeCall(function() {
waitThree.then(function() {
example.checkCall(function() {
res.status(200)
});
});
})
});

Why does settimeout break Expressjs

I have the following code in express.js
// POST api/posts/:id
exports.post = function(req, res){
req.body[0].id = posts.length + 1;
posts.push(req.body[0]);
console.log(posts);
fs.writeFileSync("json/posts.json", JSON.stringify(posts));
setTimeout(function(){
res.set.apply(res, utils.contentType);
res.json(req.body[0]);
}, utils.randomNumberBetween(1000, 3000));
};
When I post a simple json object I receive the following error.
org.apache.http.NoHttpResponseException: localhost:9000 failed to respond
If I remove the settimeout everything works as expected. I have used settimeout in other places like so:
// GET api/posts
exports.get = function(req, res){
setTimeout(function(){
res.set.apply(res, utils.contentType);
res.json(posts);
}, utils.randomNumberBetween(1000, 3000));
};
So I'm not understanding why it would break when posting. Any thoughts??
This is because the application making the request expects a response from express within a certain amount of time, say 5 seconds. If it does not receive anything back it thinks something went wrong. One thing you can do to resolve this is change the amount of time your "sending/requesting" application waits/listens for a response.

Stop the execution of a cancelled request in Express

I am creating an application which makes a lot of HTTP requests to another server and it may take up to 1 minute to complete one of such requests. Some of the users cancel the request, however my app still executes the cancelled requests.
Here is my code :
var app = express();
app.get('/something', function (req, res) {
function timeout(i) {
setTimeout(function () {
// lets assume there is a http request.
console.log(i);
timeout(++i);
}, 100);
}
req.connection.on('close', function () {
console.log('I should do something');
});
timeout(1);
});
app.listen(5000);
Basically, What I want is stop console.log(i) calls after client closes connection. Also, If possible the client omit "close-{id}" event and when backend recevies close-{id} event, it terminates {id} request.
Note : I used setTimeout to show callback mechanizim. It is not the real code.
Thank you for your help.
From the documentation, "http.request() returns an instance of the http.ClientRequest class." You could call the abort() method of the returned object. (Warning, untested code).
var http = require('http'); // This needs to go at the top of the file.
app.get('/something', function (req, res) {
var remote_request = http.request("www.something.com", function(data){
res.writeHeader(200, {"Content-type": "text/plain"});
res.end(data);
});
req.on("close", function() {
remote_request.abort();
});
});
Assign your setTimeout to a var, and then use clearTimeout in the close handler. If might take some clever restructuring based on your method structure but something like:
var myTimeout = setTimeout(...)
...
req.connection.on('close', function() {
clearTimeout(myTimeout);
});

Categories

Resources