使用Express/Node.js和Angular处理取消的请求

38

当客户端/浏览器取消挂起的HTTP请求时,似乎Node和Express仍在继续处理该请求。对于密集型请求,CPU仍然在处理不必要的请求。

有没有一种方法可以要求Node.js/Express终止这些被请求取消的挂起请求?

这变得特别有用,因为自AngularJS 1.5以来,通过在$http/$resource对象上调用$cancelRequest()很容易取消HTTP请求。

这样的取消可能发生在公开API方法提供自动完成或搜索字段结果时:在输入要自动完成或键入头部的字段时,之前的请求可能会被取消。

全局server.timeout无法解决问题:1)它是所有公开API方法的全局设置;2)被取消的请求中正在进行的处理未被终止。

3个回答

36

注入的req对象附带有监听器.on()

监听close事件可用于处理客户端关闭连接时的情况(例如,Angular取消请求或用户关闭查询标签)。

以下是两个简单示例,演示如何使用close事件停止请求处理。

示例1:可取消同步块

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 */);
}

示例2:使用Promise实现可取消的异步块(类似于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();
}

5
建议监听 "aborted" 事件而不是 "close" 事件。参考链接:https://nodejs.org/api/http.html#http_event_aborted - ZachB
“'aborted'”在17.0版本中已被弃用;如果不确定正确的事件是什么,那就... - Rashack
1
文档中说要检查 destroyed 而不是 aborted - https://nodejs.org/api/http.html#requestdestroyed - Andrew

10

使用 Express,你可以尝试:

req.connection.on('close',function(){    
  // code to handle connection abort
  console.log('user cancelled');
});

3
连接/套接字和请求之间有很大的区别。监听连接关闭可能是一个(大)错误(尽管在测试中一切都可能正常工作)- 大多数浏览器保持连接打开,即使请求本身已经完成(请参见“Connection: keep-alive”头或HTTP持久连接术语),因此其他请求可以使用相同的底层连接。 - Roy Miloh

-4

您可以为服务器上的请求设置超时

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;

我希望在客户要求取消请求时立即取消该请求。这对于自动完成查询非常有用。 - Derek
我在问题中添加了一些关于这种情况的注释:全局 server.timeout 不能解决问题:1)它是所有公开的 API 方法的先验全局设置;2)被取消请求中正在进行的处理不会被终止。 - Derek
谢谢。我会研究一下的。 - gnerkus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接