Comment définir un délai d'expiration sur une http.request () dans Node?

92

J'essaie de définir un délai d'attente sur un client HTTP qui utilise http.request sans succès. Jusqu'à présent, j'ai fait ceci:

var options = { ... }
var req = http.request(options, function(res) {
  // Usual stuff: on(data), on(end), chunks, etc...
}

/* This does not work TOO MUCH... sometimes the socket is not ready (undefined) expecially on rapid sequences of requests */
req.socket.setTimeout(myTimeout);  
req.socket.on('timeout', function() {
  req.abort();
});

req.write('something');
req.end();

Des indices?

Claudio
la source
1
J'ai trouvé cette réponse (ça marche aussi) mais je me demande s'il y a quelque chose de différent pour http.request () stackoverflow.com/questions/6129240/…
Claudio

Réponses:

26

Juste pour clarifier la réponse ci - dessus :

Il est maintenant possible d'utiliser l' timeoutoption et l'événement de requête correspondant:

// set the desired timeout in options
const options = {
    //...
    timeout: 3000,
};

// create a request
const request = http.request(options, response => {
    // your callback here
});

// use its "timeout" event to abort the request
request.on('timeout', () => {
    request.abort();
});
Sergueï Kovalenko
la source
1
Je me demande si c'est différent que justesetTimeout(req.abort.bind(req), 3000);
Alexander Mills
@AlexanderMills, vous voudrez probablement effacer le délai manuellement, lorsque la demande a fonctionné correctement.
Sergei Kovalenko
4
Notez que c'est strictement le connectdélai d'expiration, une fois la socket établie, elle n'a aucun effet. Donc, cela n'aidera pas avec un serveur qui maintient le socket ouvert trop longtemps (vous devrez toujours lancer le vôtre avec setTimeout). timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.
UpTheCreek
C'est ce que je recherche lors d'une tentative de connexion bloquée. Les connexions bloquées peuvent se produire un peu lorsque vous essayez d'accéder à un port sur un serveur qui n'écoute pas. BTW, l'API a changé pour request.destroy( abortest obsolète.) Aussi, c'est différent desetTimeout
dturvene
91

Mise à jour 2019

Il existe maintenant plusieurs façons de gérer cela plus élégamment. Veuillez voir quelques autres réponses sur ce fil. La technologie évolue rapidement, de sorte que les réponses peuvent souvent devenir obsolètes assez rapidement. Ma réponse fonctionnera toujours, mais cela vaut également la peine de rechercher des alternatives.

Réponse 2012

En utilisant votre code, le problème est que vous n'avez pas attendu qu'un socket soit affecté à la demande avant d'essayer de définir des éléments sur l'objet socket. Tout est asynchrone donc:

var options = { ... }
var req = http.request(options, function(res) {
  // Usual stuff: on(data), on(end), chunks, etc...
});

req.on('socket', function (socket) {
    socket.setTimeout(myTimeout);  
    socket.on('timeout', function() {
        req.abort();
    });
});

req.on('error', function(err) {
    if (err.code === "ECONNRESET") {
        console.log("Timeout occurs");
        //specific error treatment
    }
    //other error treatment
});

req.write('something');
req.end();

L'événement 'socket' est déclenché lorsque la requête reçoit un objet socket.

Rob Evans
la source
Cela a du sens, en effet. Le problème est que maintenant je ne peux pas tester ce problème particulier (le temps passe ...). Je ne peux donc que voter pour la réponse pour l'instant :) Merci.
Claudio
Pas de soucis. Gardez à l'esprit que cela ne fonctionne que sur la dernière version de node pour autant que je sache. J'ai testé sur une version précédente (5.0.3-pre) je pense et cela n'a pas déclenché l'événement socket.
Rob Evans
1
L'autre façon de gérer cela consiste à utiliser un appel setTimeout standard bog. Vous devrez conserver l'id setTimeout avec: var id = setTimeout (...); afin que vous puissiez l'annuler si vous recevez un on data etc. Un bon moyen est de le stocker dans l'objet de requête lui-même puis de clearTimeout si vous obtenez des données.
Rob Evans
1
Tu manques ); à la fin de req.on. Comme il ne s'agit pas de 6 caractères, je ne peux pas le modifier pour vous.
JR Smith
Cela fonctionne (merci!), Mais gardez à l'esprit que la réponse finira par arriver. req.abort()ne semble pas être une fonctionnalité standard pour le moment. Par conséquent, socket.onvous voudrez peut-être définir une variable comme timeout = true, puis gérer le délai d'attente de manière appropriée dans le gestionnaire de réponse
Jonathan Benn
40

En ce moment, il existe une méthode pour le faire directement sur l'objet de requête:

request.setTimeout(timeout, function() {
    request.abort();
});

Il s'agit d'une méthode de raccourci qui se lie à l'événement socket, puis crée le délai d'expiration.

Référence: Manuel et documentation Node.js v0.8.8

douwe
la source
3
request.setTimeout "définit le socket sur timeout après le timeout en millisecondes d'inactivité sur le socket." Je pense que cette question concerne le délai d'expiration de la demande, quelle que soit l'activité.
ostergaard
Veuillez noter que, comme dans les réponses ci-dessous qui utilisent directement le socket impliqué, le req.abort () provoque un événement d'erreur, qui devrait être géré par le ('error') etc.
KLoozen
4
request.setTimeout n'annulera pas la demande, nous devons appeler abort manuellement dans le rappel de timeout.
Udhaya
18

La réponse de Rob Evans fonctionne correctement pour moi, mais lorsque j'utilise request.abort (), il se produit une erreur de raccrochage de socket qui reste non gérée.

J'ai dû ajouter un gestionnaire d'erreurs pour l'objet de requête:

var options = { ... }
var req = http.request(options, function(res) {
  // Usual stuff: on(data), on(end), chunks, etc...
}

req.on('socket', function (socket) {
    socket.setTimeout(myTimeout);  
    socket.on('timeout', function() {
        req.abort();
    });
}

req.on('error', function(err) {
    if (err.code === "ECONNRESET") {
        console.log("Timeout occurs");
        //specific error treatment
    }
    //other error treatment
});

req.write('something');
req.end();
Pierre Maoui
la source
3
où est la myTimeoutfonction? (modifier: la documentation dit: Identique à la liaison à l'événement timeout nodejs.org/api/… )
Ben Muircroft
Notez que ECONNRESETcela peut arriver dans les deux cas: le client ferme le socket et le serveur ferme la connexion. Pour déterminer si cela a été fait par le client en appelant, abort()il y a un abortévénement spécial
Kirill
8

Il existe une méthode plus simple.

Au lieu d'utiliser setTimeout ou de travailler directement avec socket,
nous pouvons utiliser 'timeout' dans les 'options' des utilisations client

Vous trouverez ci-dessous le code du serveur et du client, en 3 parties.

Partie module et options:

'use strict';

// Source: https://github.com/nodejs/node/blob/master/test/parallel/test-http-client-timeout-option.js

const assert = require('assert');
const http = require('http');

const options = {
    host: '127.0.0.1', // server uses this
    port: 3000, // server uses this

    method: 'GET', // client uses this
    path: '/', // client uses this
    timeout: 2000 // client uses this, timesout in 2 seconds if server does not respond in time
};

Partie serveur:

function startServer() {
    console.log('startServer');

    const server = http.createServer();
    server
            .listen(options.port, options.host, function () {
                console.log('Server listening on http://' + options.host + ':' + options.port);
                console.log('');

                // server is listening now
                // so, let's start the client

                startClient();
            });
}

Partie client:

function startClient() {
    console.log('startClient');

    const req = http.request(options);

    req.on('close', function () {
        console.log("got closed!");
    });

    req.on('timeout', function () {
        console.log("timeout! " + (options.timeout / 1000) + " seconds expired");

        // Source: https://github.com/nodejs/node/blob/master/test/parallel/test-http-client-timeout-option.js#L27
        req.destroy();
    });

    req.on('error', function (e) {
        // Source: https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L248
        if (req.connection.destroyed) {
            console.log("got error, req.destroy() was called!");
            return;
        }

        console.log("got error! ", e);
    });

    // Finish sending the request
    req.end();
}


startServer();

Si vous mettez toutes les 3 parties ci-dessus dans un fichier, "a.js", puis exécutez:

node a.js

alors, la sortie sera:

startServer
Server listening on http://127.0.0.1:3000

startClient
timeout! 2 seconds expired
got closed!
got error, req.destroy() was called!

J'espère que cela pourra aider.

Manohar Reddy Poreddy
la source
2

Pour moi, voici une façon moins déroutante de faire socket.setTimeout

var request=require('https').get(
    url
   ,function(response){
        var r='';
        response.on('data',function(chunk){
            r+=chunk;
            });
        response.on('end',function(){
            console.dir(r);            //end up here if everything is good!
            });
        }).on('error',function(e){
            console.dir(e.message);    //end up here if the result returns an error
            });
request.on('error',function(e){
    console.dir(e);                    //end up here if a timeout
    });
request.on('socket',function(socket){
    socket.setTimeout(1000,function(){
        request.abort();                //causes error event ↑
        });
    });
Ben Muircroft
la source
2

L'élaboration de la réponse @douwe ici est l'endroit où vous mettriez un timeout sur une requête http.

// TYPICAL REQUEST
var req = https.get(http_options, function (res) {                                                                                                             
    var data = '';                                                                                                                                             

    res.on('data', function (chunk) { data += chunk; });                                                                                                                                                                
    res.on('end', function () {
        if (res.statusCode === 200) { /* do stuff with your data */}
        else { /* Do other codes */}
    });
});       
req.on('error', function (err) { /* More serious connection problems. */ }); 

// TIMEOUT PART
req.setTimeout(1000, function() {                                                                                                                              
    console.log("Server connection timeout (after 1 second)");                                                                                                                  
    req.abort();                                                                                                                                               
});

this.abort () est également très bien.

SpiRail
la source
1

Vous devez passer la référence à la demande comme ci-dessous

var options = { ... }
var req = http.request(options, function(res) {
  // Usual stuff: on(data), on(end), chunks, etc...
});

req.setTimeout(60000, function(){
    this.abort();
}).bind(req);
req.write('something');
req.end();

L'événement d'erreur de demande sera déclenché

req.on("error", function(e){
       console.log("Request Error : "+JSON.stringify(e));
  });

Udhaya
la source
L'ajout de bind (req) n'a rien changé pour moi. Que fait bind dans ce cas?
SpiRail
-1

Curieux, que se passe-t-il si vous utilisez net.socketsplutôt directement ? Voici un exemple de code que j'ai assemblé à des fins de test:

var net = require('net');

function HttpRequest(host, port, path, method) {
  return {
    headers: [],
    port: 80,
    path: "/",
    method: "GET",
    socket: null,
    _setDefaultHeaders: function() {

      this.headers.push(this.method + " " + this.path + " HTTP/1.1");
      this.headers.push("Host: " + this.host);
    },
    SetHeaders: function(headers) {
      for (var i = 0; i < headers.length; i++) {
        this.headers.push(headers[i]);
      }
    },
    WriteHeaders: function() {
      if(this.socket) {
        this.socket.write(this.headers.join("\r\n"));
        this.socket.write("\r\n\r\n"); // to signal headers are complete
      }
    },
    MakeRequest: function(data) {
      if(data) {
        this.socket.write(data);
      }

      this.socket.end();
    },
    SetupRequest: function() {
      this.host = host;

      if(path) {
        this.path = path;
      }
      if(port) {
        this.port = port;
      }
      if(method) {
        this.method = method;
      }

      this._setDefaultHeaders();

      this.socket = net.createConnection(this.port, this.host);
    }
  }
};

var request = HttpRequest("www.somesite.com");
request.SetupRequest();

request.socket.setTimeout(30000, function(){
  console.error("Connection timed out.");
});

request.socket.on("data", function(data) {
  console.log(data.toString('utf8'));
});

request.WriteHeaders();
request.MakeRequest();
onteria_
la source
Si j'utilise le délai d'expiration du socket, et que j'émets deux demandes l'une après l'autre (sans attendre que la première se termine), la deuxième demande a le socket indéfini (au moins au moment où j'essaye de définir le délai d'expiration) .. peut-être qu'il devrait y avoir quelque chose comme sur ("prêt") sur la prise ... je ne sais pas.
Claudio
@Claudio Pouvez-vous mettre à jour votre code pour afficher plusieurs demandes en cours?
onteria_
1
Bien sûr ... c'est un peu long et j'ai utilisé paste2.org si ce n'est pas un problème: paste2.org/p/1448487
Claudio
@Claudio Hmm d'accord, la mise en place d'un environnement de test et l'écriture d'un code de test vont prendre environ, donc ma réponse pourrait venir demain (heure du Pacifique) en tant que FYI
onteria_
@Claudio en jetant un œil, votre code ne semble pas correspondre à votre erreur. Cela dit que setTimeout est appelé sur une valeur non définie, mais la façon dont vous l'appelez se fait via la version globale, donc il n'y a aucun moyen que cela puisse être indéfini, ce qui me laisse plutôt confus.
onteria_