Handling cancelled request with Express/Node.js and Angular
Asked Answered
L

4

39

When a pending HTTP request is cancelled by a client/browser it seems that Node with Express continues to process the request. For intensive requests, the CPU is still being kept busy with unnecessary requests.

Is there a way to ask Node.js/Express to kill/stop these pending requests that are requested to be cancelled?

It becomes particularly useful given that since AngularJS 1.5 HTTP request are easily cancellable by calling $cancelRequest() on $http/$resource objects.

Such cancellations could occur when exposing an API method providing results for auto-completion or search fields: when typing in the field to be autocompleted or type-aheaded, previous request(s) can be cancelled.

A global server.timeout does not solve the problem: 1) it is a priori a global setting for all exposed API methods 2) ongoing processing in the canceled request is not killed.

Lanneret answered 4/2, 2016 at 10:15 Comment(0)
L
37

Injected reqobject is shipped with listeners .on().

Listening to close event allows to handle when client close the connection (request cancelled by Angular or, e.g., user closed the querying tab).

Here are 2 simple examples how to use the closeevent to stop request processing.

Example 1: Cancellable synchronous block

var clientCancelledRequest = 'clientCancelledRequest';

function cancellableAPIMethodA(req, res, next) {
    var cancelRequest = false;

    req.on('close', function (err){
       cancelRequest = true;
    });

    var superLargeArray = [/* ... */];

    try {
        // Long processing loop
        superLargeArray.forEach(function (item) {
                if (cancelRequest) {
                    throw {type: clientCancelledRequest};
                }
                /* Work on item */
        });

        // Job done before client cancelled the request, send result to client
        res.send(/* results */);
    } catch (e) {
        // Re-throw (or call next(e)) on non-cancellation exception
        if (e.type !== clientCancelledRequest) {
            throw e;
        }
    }

    // Job done before client cancelled the request, send result to client
    res.send(/* results */);
}

Example 2: Cancellable asynchronous block with promises (analog to a reduce)

function cancellableAPIMethodA(req, res, next) {
    var cancelRequest = false;

    req.on('close', function (err){
       cancelRequest = true;
    });

    var superLargeArray = [/* ... */];

    var promise = Q.when();
    superLargeArray.forEach(function (item) {
            promise = promise.then(function() {
                if (cancelRequest) {
                    throw {type: clientCancelledRequest};
                } 
                /* Work on item */ 
            });
    });

    promise.then(function() {
        // Job done before client cancelled the request, send result to client
        res.send(/* results */);
    })
    .catch(function(err) {
        // Re-throw (or call next(err)) on non-cancellation exception
        if (err.type !== clientCancelledRequest) {
            throw err;
        }
    })
    .done();
}
Lanneret answered 4/2, 2016 at 15:56 Comment(3)
You might want to listen for "aborted" instead of "close". nodejs.org/api/http.html#http_event_abortedAiredale
'aborted' is now deprecated in 17.0; not sure what the right event is, then...Meridithmeriel
Docs say check destroyed instead of aborted - nodejs.org/api/http.html#requestdestroyedMalnutrition
P
10

With express, you can try:

req.connection.on('close',function(){    
  // code to handle connection abort
  console.log('user cancelled');
});
Perce answered 10/12, 2016 at 3:42 Comment(1)
There's a big difference between connection/socket and request. Listening to connection close could be a (big) mistake (although everything will prbly work properly in tests) - most of browsers keep connection open, although the request itself finished (see "Connection: keep-alive" header or HTTP persistent connection term), so other requests can use the same underlying connection.Heterogamete
P
0

A small improvement upon the accepted answer, you can use the express request object's closed flag itself instead of listening for the event and handling the flag in your controller as it becomes problematic if you use middlewares in your application.

Prognosticate answered 9/7 at 16:51 Comment(0)
M
-5

You can set a timeout for requests on your server:

var server = app.listen(app.get('port'), function() {
  debug('Express server listening on port ' + server.address().port);
});
// Set the timeout for a request to 1sec
server.timeout = 1000;
Mcmillan answered 4/2, 2016 at 11:54 Comment(3)
I would like to cancel a request as soon as the client asks to cancel it. Useful with queries for auto-completion.Lanneret
I've added some comments in the question about this situation: A global server.timeout does not solve the problem: 1) it is a priori a global setting for all exposed API methods 2) ongoing processing in the canceled request is not killed.Lanneret
Thank you. I'll look into it.Mcmillan

© 2022 - 2024 — McMap. All rights reserved.