web-dev-qa-db-fra.com

Module de requête Node.js obtenant ETIMEDOUT et ESOCKETTIMEDOUT

Je parcours beaucoup de liens avec le module request en parallèle avec la combinaison du module asynchrone .
Je remarque beaucoup d’erreurs ETIMEDOUT et ESOCKETTIMEDOUT bien que les liens soient accessibles et répondent assez rapidement avec chrome.

J'ai limité le maxSockets à 2 et le timeout à 10 000 dans les options de la requête. J'utilise async.filterLimit() avec une limite de 2 pour même réduire le parallélisme à 2 requêtes à chaque fois. J'ai donc 2 sockets, 2 requêtes et un délai d'attente de 10 secondes pour attendre la réponse des en-têtes du serveur, mais j'obtiens ces erreurs.

Voici la configuration de la demande que j'utilise:

{
    ...
    pool: {
        maxSockets: 2
    },
    timeout: 10000
    ,
    time: true
    ...
}

Voici l'extrait de code que j'utilise pour fecth links:

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

J'ai écouté l'événement d'erreur et je vois à chaque fois que le code d'erreur est ETIMEDOUT l'objet de connexion est soit vrai/faux, donc parfois c'est un délai de connexion et parfois ce n'est pas le cas (selon les documents de la demande)

UPDATE: J'ai décidé de relancer le maxSockets à Infinity afin qu'aucune connexion ne soit coupée en raison du manque de disponibilité prises de courant:

pool: {
    maxSockets: Infinity
}

Afin de contrôler la bande passante, j'ai implémenté une méthode requestLoop qui gère la requête avec les paramètres maxAttemps et retryDelay pour contrôler les requêtes:

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

Implémentation de 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);
            }
        });
    }
};

Donc ceci pour résumer: - Boosté maxSockets à Infinity pour essayer de surmonter l'erreur de timeout de la connexion aux sockets - Méthode implémentée requestLoop pour contrôler les demandes en échec et maxAttemps ainsi que retryDelay de telles demandes - Il existe également un nombre maximal de demandes simultanées définies par le nombre passé à async.filterLimit

Je tiens à noter que j'ai également joué avec les paramètres de tout ici afin d'obtenir une exploration libre des erreurs, mais jusqu'à présent, les tentatives ont également échoué.

Je cherche toujours de l'aide pour résoudre ce problème.

UPDATE2: J'ai décidé de supprimer async.filterLimit et de créer mon propre mécanisme de limite. J'ai juste 3 variables pour m'aider à atteindre ceci:
pendingRequests - un tableau de demandes qui contiendra toutes les demandes (sera expliqué plus tard) activeRequests - nombre de demandes actives maxConcurrentRequests - nombre de demandes simultanées maximum autorisées

dans le tableau waitingRequests, i Poussez un objet complexe contenant une référence à la fonction requestLoop ainsi qu'un tableau d'arguments contenant les arguments à transmettre à la fonction de boucle:

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();

Vous '' notez l'appel à runRequest() à la fin. Ce travail de fonction consiste à gérer les demandes et à exécuter les demandes quand il le peut, tout en maintenant le maximum activeRequests sous la limite de 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();
});

Cela devrait résoudre toutes les erreurs de délai d'attente, lors de mes tests, cependant, j'ai encore remarqué des délais d'attente dans des sites Web spécifiques sur lesquels j'ai testé cela. Je ne peux pas être tout à fait sûr à ce sujet, mais je pense que cela est dû à la nature du site Web supportant le serveur http, limitant au maximum les demandes des utilisateurs en effectuant une vérification d'IP et en renvoyant des messages HTTP 400. pour empêcher une éventuelle "attaque" sur le serveur.

40
Jorayen

Edit: duplicate of https://stackoverflow.com/a/37946324/744276

Par défaut, Node a 4 travailleurs pour résoudre les requêtes DNS . Si votre requête DNS prend du temps, les demandes se bloquent pendant la phase DNS et le symptôme est exactement ESOCKETTIMEDOUT ou ETIMEDOUT.

Essayez d'augmenter la taille de votre pool de threads UV:

export UV_THREADPOOL_SIZE=128
node ...

ou dans index.js (ou votre lieu d’entrée):

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

function main() {
   ...
}

Edit: voici un article de blog à ce sujet.

31
Motiejus Jakštys

J'ai constaté que s'il y avait trop de demandes asynchrones, l'exception ESOCKETTIMEDOUT se produit sous Linux. La solution que j'ai trouvée est la suivante:

définir cette option à request (): agent: false, pool: {maxSockets: 100} Remarquez qu'après cela, le délai d'attente peut être en train de mentir, il peut donc être nécessaire de l'augmenter.

1
cancerbero