Come impostare un timeout su http.request () in Node?


92

Sto cercando di impostare un timeout su un client HTTP che utilizza http.request senza fortuna. Finora quello che ho fatto è questo:

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

Eventuali suggerimenti?


1
Trovato questa risposta (funziona troppo), ma mi chiedo se c'è qualcosa di diverso per http.request () stackoverflow.com/questions/6129240/...
Claudio

Risposte:


26

Giusto per chiarire la risposta sopra :

Ora è possibile utilizzare l' timeoutopzione e il corrispondente evento di richiesta:

// 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
Mi chiedo se questo sia diverso dal semplicesetTimeout(req.abort.bind(req), 3000);
Alexander Mills

@AlexanderMills, allora probabilmente vorrai cancellare il timeout manualmente, quando la richiesta ha funzionato bene.
Sergei Kovalenko

4
Nota che questo è strettamente il connecttimeout, una volta stabilito il socket non ha alcun effetto. Quindi questo non aiuterà con un server che mantiene il socket aperto troppo a lungo (sarà comunque necessario eseguire il rollio con setTimeout). timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.
UpTheCreek

Questo è ciò che cerco in un tentativo di connessione in sospeso. Le connessioni bloccate possono verificarsi un bel po 'quando si tenta di accedere a una porta su un server che non è in ascolto. A proposito, l'API è cambiata in request.destroy( abortè deprecata). Inoltre, questo è diverso dasetTimeout
dturvene,

91

Aggiornamento 2019

Ci sono vari modi per gestirlo in modo più elegante ora. Si prega di vedere alcune altre risposte su questo thread. La tecnologia si muove velocemente, quindi le risposte possono spesso diventare obsolete abbastanza rapidamente. La mia risposta funzionerà ancora, ma vale anche la pena cercare delle alternative.

Risposta del 2012

Usando il tuo codice, il problema è che non hai aspettato che un socket fosse assegnato alla richiesta prima di tentare di impostare qualcosa sull'oggetto socket. È tutto asincrono quindi:

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'evento "socket" viene generato quando alla richiesta viene assegnato un oggetto socket.


Questo ha assolutamente senso, davvero. Il problema è che ora non posso testare questo particolare problema (il tempo passa ...). Quindi posso solo votare la risposta per ora :) Grazie.
Claudio

Nessun problema. Tieni presente che funziona solo sull'ultima versione di node, per quanto ne so. Ho provato su una versione precedente (5.0.3-pre) penso e non ha generato l'evento socket.
Rob Evans

1
L'altro modo per gestire questo è usare una chiamata setTimeout standard di bog. Dovrai mantenere il setTimeout id con: var id = setTimeout (...); in modo che tu possa cancellarlo se ricevi dei dati ecc. Un buon modo è memorizzarlo nell'oggetto di richiesta stesso, quindi cancellareTimeout se ottieni dei dati.
Rob Evans

1
Ti manca); alla fine della richiesta. Dato che non sono 6 caratteri, non posso modificarlo per te.
JR Smith

Funziona (grazie!), Ma tieni presente che la risposta alla fine arriverà comunque. req.abort()non sembra essere una funzionalità standard al momento. Quindi, socket.onpotresti voler impostare una variabile come timeout = truee quindi gestire il timeout in modo appropriato nel gestore delle risposte
Jonathan Benn,

40

In questo momento esiste un metodo per farlo direttamente sull'oggetto richiesta:

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

Questo è un metodo di scelta rapida che si associa all'evento socket e quindi crea il timeout.

Riferimento: Manuale e documentazione di Node.js v0.8.8


3
request.setTimeout "imposta il socket su timeout dopo il timeout di millisecondi di inattività sul socket." Penso che questa domanda riguardi il timeout della richiesta indipendentemente dall'attività.
ostergaard

Si noti che, come nelle risposte seguenti che utilizzano direttamente il socket coinvolto, req.abort () causa un evento di errore, che dovrebbe essere gestito da on ('error') ecc.
KLoozen

4
request.setTimeout non interromperà la richiesta, dobbiamo chiamare abort manualmente nella richiamata timeout.
Udhaya,

18

La risposta di Rob Evans funziona correttamente per me, ma quando uso request.abort (), si verifica un errore di blocco del socket che rimane non gestito.

Ho dovuto aggiungere un gestore degli errori per l'oggetto richiesta:

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
dov'è la myTimeoutfunzione? (modifica: i documenti dicono: come il collegamento all'evento di timeout nodejs.org/api/… )
Ben Muircroft

Si noti che ECONNRESETpuò accadere in entrambi i casi: il client chiude il socket e il server chiude la connessione. Per determinare se è stato fatto dal cliente chiamando abort()c'è un abortevento speciale
Kirill

8

C'è un metodo più semplice.

Invece di usare setTimeout o lavorare direttamente con socket,
possiamo usare 'timeout' nelle 'opzioni' negli usi client

Di seguito è riportato il codice del server e del client, in 3 parti.

Parte modulo e opzioni:

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

Parte server:

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

Parte cliente:

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

Se metti tutte le 3 parti precedenti in un file, "a.js", e poi esegui:

node a.js

quindi, l'output sarà:

startServer
Server listening on http://127.0.0.1:3000

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

Spero che aiuti.


2

Per me, ecco un modo meno confuso di eseguire il file 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

Elaborare la risposta @douwe qui è dove mettere un timeout su una richiesta 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();                                                                                                                                               
});

Anche this.abort () va bene.


1

Dovresti passare il riferimento alla richiesta come di seguito

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

Verrà attivato l'evento di errore della richiesta

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


L'aggiunta di bind (req) non ha cambiato nulla per me. Cosa fa il bind in questo caso?
SpiRail

-1

Curioso, cosa succede se net.socketsinvece usi straight ? Ecco un po 'di codice di esempio che ho messo insieme a scopo di 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();

Se utilizzo il timeout del socket, ed emetto due richieste una dopo l'altra (senza aspettare che la prima finisca), la seconda richiesta ha il socket indefinito (almeno al momento provo a impostare il timeout) .. forse dovrebbe esserci qualcosa come su ("pronto") sulla presa ... non lo so.
Claudio

@Claudio Puoi aggiornare il tuo codice per mostrare più richieste in corso?
onteria_

1
Certo ... è un po 'lungo e ho usato paste2.org se questo non è un problema: paste2.org/p/1448487
Claudio

@Claudio Hmm ok, la creazione di un ambiente di test e la scrittura di un codice di test richiederanno circa, quindi la mia risposta potrebbe arrivare domani (Pacific Time) come FYI
onteria_

@Claudio in realtà sta dando un'occhiata al tuo codice non sembra corrispondere al tuo errore. Sta dicendo che setTimeout viene chiamato con un valore indefinito, ma il modo in cui lo chiami è attraverso la versione globale, quindi non c'è modo che possa essere indefinito, lasciandomi piuttosto confuso.
onteria_
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.