Jak ustawić limit czasu dla http.request () w Node?


94

Próbuję ustawić limit czasu na kliencie HTTP, który używa http.request bez powodzenia. Do tej pory to, co zrobiłem, to:

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

Jakieś wskazówki?


1
Znalazłem tę odpowiedź (to też działa), ale zastanawiam się, czy jest coś innego dla http.request () stackoverflow.com/questions/6129240/ ...
Claudio

Odpowiedzi:


28

Aby wyjaśnić powyższą odpowiedź :

Teraz można użyć timeoutopcji i odpowiedniego zdarzenia żądania:

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

1
Ciekawe, czy to coś innego niż zwykłesetTimeout(req.abort.bind(req), 3000);
Alexander Mills

@AlexanderMills, prawdopodobnie chcesz ręcznie wyczyścić limit czasu, gdy żądanie zadziałało poprawnie.
Siergiej Kovalenko

4
Zauważ, że jest to ściśle określony connectlimit czasu, po ustanowieniu gniazda nie ma żadnego wpływu. Więc to nie pomoże w przypadku serwera, który utrzymuje gniazdo otwarte zbyt długo (nadal będziesz musiał zmienić swój własny za pomocą setTimeout). timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.
UpTheCreek

Tego właśnie szukam przy próbie zawieszenia połączenia. Zawieszone połączenia mogą się zdarzyć, gdy próbujesz uzyskać dostęp do portu na serwerze, który nie nasłuchuje. Przy okazji, API zmieniło się na request.destroy( abortjest przestarzałe). Poza tym różni się odsetTimeout
dturvene

92

Aktualizacja 2019

Obecnie istnieje wiele sposobów, aby poradzić sobie z tym bardziej elegancko. Zobacz inne odpowiedzi w tym wątku. Technologia szybko się rozwija, więc odpowiedzi często dość szybko stają się nieaktualne. Moja odpowiedź będzie nadal działać, ale warto też przyjrzeć się alternatywom.

Odpowiedź z 2012 roku

Używając twojego kodu, problem polega na tym, że nie czekałeś na przypisanie gniazda do żądania przed próbą ustawienia rzeczy na obiekcie gniazda. Wszystko jest asynchroniczne, więc:

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

Zdarzenie „gniazdo” jest wyzwalane, gdy żądanie jest przypisane do obiektu gniazda.


To rzeczywiście ma sens. Problem w tym, że teraz nie mogę przetestować tego konkretnego problemu (czas płynie ...). Więc na razie mogę tylko zagłosować za odpowiedzią :) Dziękuję.
Claudio,

Bez obaw. Pamiętaj, że działa to tylko w najnowszej wersji node, o ile wiem. Wydaje mi się, że testowałem na poprzedniej wersji (5.0.3-pre) i to nie uruchomiło zdarzenia gniazda.
Rob Evans,

1
Innym sposobem rozwiązania tego problemu jest użycie standardowego wywołania setTimeout bog. Będziesz musiał zachować identyfikator setTimeout za pomocą: var id = setTimeout (...); abyś mógł to anulować, jeśli otrzymasz dane itp. Dobrym sposobem jest przechowywanie ich w samym obiekcie żądania, a następnie clearTimeout, jeśli otrzymasz jakieś dane.
Rob Evans,

1
Brakuje); na końcu żądania wł. Ponieważ nie ma 6 znaków, nie mogę go dla Ciebie edytować.
JR Smith

To działa (dzięki!), Ale pamiętaj, że odpowiedź w końcu nadejdzie. req.abort()nie wydaje się być w tej chwili standardową funkcjonalnością. Dlatego socket.onmożesz chcieć ustawić zmienną, taką jak, timeout = truea następnie odpowiednio obsłużyć limit czasu w programie obsługi odpowiedzi
Jonathan Benn

40

W tej chwili istnieje metoda, aby to zrobić bezpośrednio na obiekcie żądania:

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

Jest to metoda skrótowa, która wiąże się ze zdarzeniem gniazda, a następnie tworzy limit czasu.

Odniesienie: Node.js v0.8.8 Podręcznik i dokumentacja


3
request.setTimeout "ustawia limit czasu gniazda po upływie milisekund braku aktywności w gnieździe." Myślę, że to pytanie dotyczy przekroczenia limitu czasu żądania niezależnie od aktywności.
ostergaard

Proszę zauważyć, że tak samo jak w odpowiedziach poniżej, które używają bezpośrednio danego gniazda, req.abort () powoduje zdarzenie błędu, które powinno być obsłużone przez on ('błąd') itd.
KLoozen

4
request.setTimeout nie przerwie żądania, musimy ręcznie wywołać abort w wywołaniu zwrotnym limitu czasu.
Udhaya

18

Odpowiedź Roba Evansa działa poprawnie dla mnie, ale kiedy używam request.abort (), pojawia się błąd zawieszenia gniazda, który pozostaje nieobsługiwany.

Musiałem dodać procedurę obsługi błędów dla obiektu żądania:

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

3
gdzie jest myTimeoutfunkcja? (edit: docs powiedzieć: Tak samo jak wiązania do zdarzenia limitu czasu nodejs.org/api/... )
Ben Muircroft

Zauważ, że ECONNRESETmoże się to zdarzyć w obu przypadkach: klient zamyka gniazdo, a serwer zamyka połączenie. Aby ustalić, czy zrobił to klient dzwoniąc abort(), jest specjalne abortwydarzenie
Kirill

9

Jest prostsza metoda.

Zamiast używać setTimeout lub pracować bezpośrednio z gniazdem,
możemy użyć 'timeout' w 'opcjach' w kliencie używa

Poniżej znajduje się kod serwera i klienta, w 3 częściach.

Moduł i część opcji:

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

Część serwerowa:

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

Część klienta:

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

Jeśli umieścisz wszystkie powyższe 3 części w jednym pliku „a.js”, a następnie uruchomisz:

node a.js

wtedy wynik będzie:

startServer
Server listening on http://127.0.0.1:3000

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

Mam nadzieję, że to pomoże.


2

Dla mnie - tutaj jest mniej zagmatwany sposób wykonania 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 ↑
        });
    });

2

Omówienie odpowiedzi @douwe to miejsce, w którym można ustawić limit czasu na żądanie 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 () też jest w porządku.


1

Aby zgłosić żądanie, należy podać numer referencyjny, jak poniżej

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

Zostanie wyzwolone zdarzenie błędu żądania

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


Dodanie bind (req) nic mnie nie zmieniło. Co robi bind w tym przypadku?
SpiRail

-1

Ciekawe, co się stanie, jeśli net.socketszamiast tego użyjesz prostego ? Oto przykładowy kod, który utworzyłem do celów testowych:

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

Jeśli używam limitu czasu gniazda i wydaję dwa żądania jedno po drugim (nie czekając na zakończenie pierwszego), drugie żądanie ma niezdefiniowane gniazdo (przynajmniej w tej chwili próbuję ustawić limit czasu) .. może powinno być coś jak włączone ("gotowe") na gnieździe ... nie wiem.
Claudio

@Claudio Czy możesz zaktualizować swój kod, aby wyświetlić wiele żądań?
onteria_

1
Oczywiście ... jest trochę za długi i użyłem paste2.org, jeśli to nie jest problem: paste2.org/p/1448487
Claudio

@Claudio Hmm ok, skonfigurowanie środowiska testowego i napisanie kodu testowego zajmie trochę czasu, więc moja odpowiedź może przyjść jutro (czas pacyficzny) jako
powiadomienie

@Claudio faktycznie przygląda się Twojemu kodowi, który nie pasuje do Twojego błędu. Mówi się, że funkcja setTimeout jest wywoływana na podstawie niezdefiniowanej wartości, ale sposób, w jaki ją wywołujesz, odbywa się za pośrednictwem wersji globalnej, więc nie ma możliwości, aby było to niezdefiniowane, pozostawiając mnie raczej zdezorientowanego.
onteria_
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.