Node.js请求模块获得ETIMEDOUT和ESOCKETTIMEDOUT

Jor*_*yen 42 sockets asynchronous request httprequest node.js

我正在使用异步模块的组合并行地抓取与请求模块的许多链接. 我注意到很多和错误,虽然链接可以访问并使用chrome快速响应.
ETIMEDOUTESOCKETTIMEDOUT

我在请求选项中限制maxSockets为2和timeout10000.我使用async.filterLimit()的是2的限制,甚至每次都将并行性降低到2个请求.所以我有2个套接字,2个请求,以及10秒的超时等待来自服务器的标头响应但我得到了这些错误.

这里;我使用的请求配置:

{
    ...
    pool: {
        maxSockets: 2
    },
    timeout: 10000
    ,
    time: true
    ...
}
Run Code Online (Sandbox Code Playgroud)

这是我用来链接的代码片段:

var self = this;
async.filterLimit(resources, 2, function(resource, callback) {
    request({
        uri: resource.uri
    }, function (error, response, body) {
        if (!error && response.statusCode === 200) {
            ...
        } else {
            self.emit('error', resource, error);
        }
        callback(...);
    })
}, function(result) {
    callback(null, result);
});
Run Code Online (Sandbox Code Playgroud)

我听了错误事件,我看到每当错误代码是ETIMEDOUT连接对象是真/假,所以有时它是连接超时,有时它不是(根据请求文档)

更新: 我决定提升maxSockets至,Infinity所以没有连接将由于缺乏可用的套接字挂起:

pool: {
    maxSockets: Infinity
}
Run Code Online (Sandbox Code Playgroud)

为了控制带宽我实现了一个requestLoop处理请求的方法,带有maxAttempsretryDelay参数来控制请求:

async.filterLimit(resources, 10, function(resource, callback) {
    self.requestLoop({
        uri: resource.uri
    }, 100, 5000, function (error, response, body) {
            var fetched = false;
            if (!error) {
                ...
            } else {
                ....
            }
            callback(...);
        });
}, function(result) {
    callback(null, result);
});
Run Code Online (Sandbox Code Playgroud)

requestLoop的实现:

requestLoop = function(options, attemptsLeft, retryDelay, callback, lastError) {
    var self = this;
    if (attemptsLeft <= 0) {
        callback((lastError != null ? lastError : new Error('...')));
    } else {
        request(options, function (error, response, body) {
            var recoverableErrors = ['ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED'];
            var e;
            if ((error && _.contains(recoverableErrors, error.code)) || (response && (500 <= response.statusCode && response.statusCode < 600))) {
                e = error ? new Error('...');
                e.code = error ? error.code : response.statusCode;
                setTimeout((function () {
                    self.requestLoop(options, --attemptsLeft, retryDelay, callback, e);
                }), retryDelay);
            } else if (!error && (200 <= response.statusCode && response.statusCode < 300)) {
                callback(null, response, body);
            } else if (error) {
                e = new Error('...');
                e.code = error.code;
                callback(e);
            } else {
                e = new Error('...');
                e.code = response.statusCode;
                callback(e);
            }
        });
    }
};
Run Code Online (Sandbox Code Playgroud)

因此,这总结一下: -提振maxSockets,以Infinity尽量克服插座的连接超时错误- Implemnted requestLoop方法来控制失败的请求和maxAttemps以及retryDelay这种请求的-也有并发请求的最大信号数量由传递给数设置async.filterLimit

我想要注意的是,我还使用了这里的所有设置,以便免费获取错误,但到目前为止尝试失败了.

仍在寻找解决这个问题的帮助.

UPDATE2: 我决定放弃async.filterLimit并制作自己的限制机制.我只有3个变量来帮助我实现这个目标:
pendingRequests- 一个请求数组,它将保存所有请求(稍后会解释) activeRequests- 活动请求 maxConcurrentRequests数 - 允许的最大并发请求数

进入pendingRequests数组,我推送一个包含对requestLoop函数的引用的复杂对象,以及包含要传递给循环函数的参数的arguments数组:

self.pendingRequests.push({
    "arguments": [{
        uri: resource.uri.toString()
    }, self.maxAttempts, function (error, response, body) {
        if (!error) {
            if (self.policyChecker.isMimeTypeAllowed((response.headers['content-type'] || '').split(';')[0]) &&
                self.policyChecker.isFileSizeAllowed(body)) {
                self.totalBytesFetched += body.length;
                resource.content = self.decodeBuffer(body, response.headers["content-type"] || '', resource);
                callback(null, resource);
            } else {
                self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1);
                callback(new Error('Fetch failed because a mime-type is not allowed or file size is bigger than permited'));
            }
        } else {
            self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1);
            callback(error);
        }
        self.activeRequests--;
        self.runRequest();
    }],
    "function": self.requestLoop
});
self.runRequest();
Run Code Online (Sandbox Code Playgroud)

你最后注意到了这个电话runRequest().此功能作业是在可能的情况下管理请求和触发请求,同时将最大值保持在activeRequests以下限制maxConcurrentRequests:

var self = this;
process.nextTick(function() {
    var next;
    if (!self.pendingRequests.length || self.activeRequests >= self.maxConcurrentRequests) {
        return;
    }
    self.activeRequests++;
    next = self.pendingRequests.shift();
    next["function"].apply(self, next["arguments"]);
    self.runRequest();
});
Run Code Online (Sandbox Code Playgroud)

这应该可以解决任何超时错误,通过我的测试,我仍然注意到在我测试过的特定网站上有一些超时.我无法100%确定这一点,但我认为这是由于支持http-server的网站的性质通过执行ip检查将用户请求限制为最大值,因此返回一些HTTP 400消息防止服务器上可能出现"攻击".

Mot*_*tys 33

编辑:https ://stackoverflow.com/a/37946324/744276的副本

默认情况下,Node有4个工作人员来解析DNS查询.如果您的DNS查询需要很长时间,请求将阻止DNS阶段,并且症状正好ESOCKETTIMEDOUTETIMEDOUT.

尝试增加你的uv线程池大小:

export UV_THREADPOOL_SIZE=128
node ...
Run Code Online (Sandbox Code Playgroud)

或在index.js(或在你的切入点的任何地方):

#!/usr/bin/env node
process.env.UV_THREADPOOL_SIZE = 128;

function main() {
   ...
}
Run Code Online (Sandbox Code Playgroud)

编辑:这是一篇关于它的博客文章.

  • 这是正确的,除了 DNS 解析也被阻塞,因为 `getaddrinfo(3)` 是如何工作的。 (4认同)