Wie setze ich ein Timeout für eine http.request () in Node?

92

Ich versuche, ein Timeout auf einem HTTP-Client festzulegen, der http.request ohne Glück verwendet. Bisher habe ich Folgendes getan:

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

Irgendwelche Hinweise?

Claudio
quelle
1
Ich habe diese Antwort gefunden (es funktioniert auch), aber ich frage mich, ob es etwas anderes für http.request () stackoverflow.com/questions/6129240/… gibt
Claudio

Antworten:

24

Nur um die Antwort oben zu verdeutlichen :

Jetzt ist es möglich, die timeoutOption und das entsprechende Anforderungsereignis zu verwenden:

// 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();
});
Sergei Kovalenko
quelle
1
Ich frage mich, ob dies anders ist als nursetTimeout(req.abort.bind(req), 3000);
Alexander Mills
@AlexanderMills, dann möchten Sie das Timeout wahrscheinlich manuell löschen, wenn die Anforderung einwandfrei funktioniert hat.
Sergei Kovalenko
4
Beachten Sie, dass dies ausschließlich das connectZeitlimit ist. Sobald der Socket eingerichtet ist, hat er keine Auswirkung mehr. Dies hilft also nicht bei einem Server, der den Socket zu lange offen hält (Sie müssen immer noch Ihren eigenen mit setTimeout rollen). timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.
UpTheCreek
Dies ist, wonach ich bei einem Verbindungsversuch suche. Hung-Verbindungen können häufig auftreten, wenn Sie versuchen, auf einen Port auf einem Server zuzugreifen, der nicht empfangsbereit ist. Übrigens, die API wurde geändert in request.destroy( abortist veraltet). Dies unterscheidet sich auch vonsetTimeout
dturvene
91

Update 2019

Es gibt verschiedene Möglichkeiten, dies jetzt eleganter zu handhaben. Bitte sehen Sie einige andere Antworten auf diesen Thread. Tech bewegt sich schnell, so dass Antworten oft ziemlich schnell veraltet sind. Meine Antwort wird immer noch funktionieren, aber es lohnt sich auch, nach Alternativen zu suchen.

2012 Antwort

Bei Verwendung Ihres Codes besteht das Problem darin, dass Sie nicht darauf gewartet haben, dass der Anforderung ein Socket zugewiesen wird, bevor Sie versuchen, Inhalte für das Socket-Objekt festzulegen. Es ist alles asynchron, also:

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

Das 'Socket'-Ereignis wird ausgelöst, wenn der Anforderung ein Socket-Objekt zugewiesen wird.

Rob Evans
quelle
Dies ist in der Tat absolut sinnvoll. Das Problem ist, dass ich dieses spezielle Problem jetzt nicht testen kann (die Zeit vergeht ...). Daher kann ich die Antwort vorerst nur positiv bewerten :) Danke.
Claudio
Keine Sorge. Beachten Sie, dass dies meines Wissens nur mit der neuesten Version von Node funktioniert. Ich habe auf einer früheren Version (5.0.3-pre) getestet, die das Socket-Ereignis nicht ausgelöst hat.
Rob Evans
1
Die andere Möglichkeit, dies zu handhaben, besteht darin, einen setTimeout-Aufruf nach Moorstandard zu verwenden. Sie müssen die setTimeout-ID behalten mit: var id = setTimeout (...); Damit Sie es abbrechen können, wenn Sie Daten usw. erhalten. Eine gute Möglichkeit besteht darin, sie im Anforderungsobjekt selbst zu speichern und dann clearTimeout zu löschen, wenn Sie Daten erhalten.
Rob Evans
1
Du fehlst ); am Ende von req.on. Da es nicht 6 Zeichen sind, kann ich es nicht für Sie bearbeiten.
JR Smith
Dies funktioniert (danke!), Aber denken Sie daran, dass die Antwort irgendwann noch eintreffen wird. req.abort()scheint im Moment keine Standardfunktionalität zu sein. Daher socket.onmöchten Sie möglicherweise eine Variable wie festlegen timeout = trueund dann das Zeitlimit im Antworthandler entsprechend behandeln
Jonathan Benn
40

In diesem Moment gibt es eine Methode, um dies direkt für das Anforderungsobjekt zu tun:

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

Dies ist eine Verknüpfungsmethode, die an das Socket-Ereignis bindet und dann das Zeitlimit erstellt.

Referenz: Node.js v0.8.8 Handbuch und Dokumentation

Douwe
quelle
3
request.setTimeout "setzt den Socket nach einer Zeitüberschreitung von Millisekunden Inaktivität auf dem Socket auf Timeout." Ich denke, bei dieser Frage geht es darum, die Anfrage unabhängig von der Aktivität zu beenden.
Ostgaard
Bitte beachten Sie, dass req.abort () genau wie in den Antworten unten, in denen der betreffende Socket direkt verwendet wird, ein Fehlerereignis verursacht, das von on ('error') usw. behandelt werden sollte.
KLoozen
4
request.setTimeout bricht die Anforderung nicht ab. Wir müssen abort im Timeout-Rückruf manuell aufrufen.
Udhaya
18

Die Rob Evans-Antwort funktioniert für mich korrekt, aber wenn ich request.abort () verwende, tritt ein Socket-Aufhängefehler auf, der unbehandelt bleibt.

Ich musste einen Fehlerhandler für das Anforderungsobjekt hinzufügen:

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
quelle
3
wo ist die myTimeoutfunktion (Bearbeiten: Die Dokumente sagen: Entspricht der Bindung an das Timeout-Ereignis nodejs.org/api/… )
Ben Muircroft
Beachten Sie, dass ECONNRESETdies in beiden Fällen passieren kann: Der Client schließt den Socket und der Server schließt die Verbindung. Um festzustellen, ob es vom Kunden durch einen Anruf gemacht wurde, abort()gibt es ein besonderes abortEreignis
Kirill
7

Es gibt eine einfachere Methode.

Anstatt setTimeout zu verwenden oder direkt mit Socket zu arbeiten, können
wir 'Timeout' in den 'Optionen' in Client-Anwendungen verwenden

Unten finden Sie den Code von Server und Client in drei Teilen.

Modul- und Optionsteil:

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

Serverteil:

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

Client-Teil:

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

Wenn Sie alle oben genannten 3 Teile in eine Datei einfügen, "a.js", und dann ausführen:

node a.js

dann wird die Ausgabe sein:

startServer
Server listening on http://127.0.0.1:3000

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

Hoffentlich hilft das.

Manohar Reddy Poreddy
quelle
2

Für mich - hier ist eine weniger verwirrende Art, das zu tun 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
quelle
2

Wenn Sie hier auf die Antwort @douwe eingehen, können Sie eine http-Anfrage zeitlich festlegen.

// 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 () ist auch in Ordnung.

SpiRail
quelle
1

Sie sollten die Referenz wie unten angegeben anfordern

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

Das Anforderungsfehlerereignis wird ausgelöst

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

Udhaya
quelle
Das Hinzufügen von bind (req) hat für mich nichts geändert. Was macht bind in diesem Fall?
SpiRail
-1

Neugierig, was passiert, wenn Sie net.socketsstattdessen gerade verwenden? Hier ist ein Beispielcode, den ich zu Testzwecken zusammengestellt habe:

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_
quelle
Wenn ich das Socket-Timeout verwende und zwei Anforderungen nacheinander ausstelle (ohne darauf zu warten, dass die erste abgeschlossen wird), ist bei der zweiten Anforderung der Socket undefiniert (zumindest in dem Moment, in dem ich versuche, das Timeout festzulegen). Vielleicht sollte es eine geben so etwas wie auf ("bereit") auf der Steckdose ... Ich weiß es nicht.
Claudio
@Claudio Können Sie Ihren Code aktualisieren, um anzuzeigen, dass mehrere Anfragen gestellt werden?
onteria_
1
Natürlich ... es ist ein bisschen lang und ich habe paste2.org verwendet, wenn dies kein Problem ist: paste2.org/p/1448487
Claudio
@Claudio Hmm okay, das Einrichten einer Testumgebung und das Schreiben von Testcode wird ungefähr dauern, daher könnte meine Antwort irgendwann morgen (pazifische Zeit) als FYI
onteria_
@Claudio Wenn Sie tatsächlich einen Blick darauf werfen, scheint Ihr Code nicht mit Ihrem Fehler übereinzustimmen. Es heißt, dass setTimeout für einen undefinierten Wert aufgerufen wird, aber die Art und Weise, wie Sie ihn aufrufen, erfolgt über die globale Version. Es gibt also keine Möglichkeit, die undefiniert sein könnte, was mich ziemlich verwirrt.
onteria_