Como definir um tempo limite em um http.request () no Node?

92

Estou tentando definir um tempo limite em um cliente HTTP que usa http.request sem sorte. Até agora, o que fiz foi o seguinte:

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

Alguma dica?

Claudio
fonte
1
Encontrei esta resposta (também funciona), mas gostaria de saber se há algo diferente para http.request () stackoverflow.com/questions/6129240/…
Claudio

Respostas:

26

Só para esclarecer a resposta acima :

Agora é possível usar a timeoutopção e o evento de solicitação correspondente:

// 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
fonte
1
Gostaria de saber se isso é diferente de apenassetTimeout(req.abort.bind(req), 3000);
Alexander Mills
@AlexanderMills, então você provavelmente deseja limpar o tempo limite manualmente, quando a solicitação funcionou bem.
Sergei Kovalenko de
4
Observe que este é estritamente o connecttempo limite, uma vez que o soquete é estabelecido, ele não tem efeito. Portanto, isso não vai ajudar com um servidor que mantém o soquete aberto por muito tempo (você ainda precisará fazer o seu próprio com setTimeout). timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.
UpTheCreek
Isso é o que estou procurando em uma tentativa de conexão interrompida. Conexões travadas podem acontecer bastante ao tentar acessar uma porta em um servidor que não está escutando. BTW, a API mudou para request.destroy( abortestá obsoleta.) Além disso, isso é diferente desetTimeout
dturvene
91

Atualização de 2019

Existem várias maneiras de lidar com isso de forma mais elegante agora. Por favor, veja algumas outras respostas neste tópico. A tecnologia avança rapidamente, então as respostas podem ficar desatualizadas com bastante rapidez. Minha resposta ainda funcionará, mas também vale a pena procurar alternativas.

Resposta de 2012

Usando seu código, o problema é que você não esperou que um soquete fosse atribuído à solicitação antes de tentar definir coisas no objeto de soquete. É tudo assíncrono, então:

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

O evento 'socket' é disparado quando o pedido é atribuído a um objeto socket.

Rob Evans
fonte
Isso faz todo o sentido, de fato. O problema é que agora não consigo testar esse problema específico (o tempo passa ...). Portanto, por enquanto, só posso votar positivamente na resposta :) Obrigado.
Claudio
Não se preocupe. Lembre-se de que isso só funciona na versão mais recente do nó, pelo que eu sei. Eu testei em uma versão anterior (5.0.3-pre) e acho que não disparou o evento socket.
Rob Evans
1
A outra maneira de lidar com isso é usar uma chamada setTimeout padrão bog. Você precisará manter o id setTimeout com: var id = setTimeout (...); para que você possa cancelá-lo se receber um dado ativo, etc. Uma boa maneira é armazená-lo no próprio objeto de solicitação e, em seguida, limparTimeout se você receber alguns dados.
Rob Evans
1
Você está faltando ); no final do req.on. Uma vez que não tem 6 caracteres, não posso editá-lo para você.
JR Smith
Isso funciona (obrigado!), Mas lembre-se de que a resposta ainda chegará eventualmente. req.abort()não parece ser uma funcionalidade padrão no momento. Portanto, em socket.onvocê pode querer definir uma variável como timeout = truee, em seguida, manipular o tempo limite apropriadamente no manipulador de resposta
Jonathan Benn
40

No momento, existe um método para fazer isso diretamente no objeto de solicitação:

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

Este é um método de atalho que se liga ao evento de soquete e, em seguida, cria o tempo limite.

Referência: Manual e documentação do Node.js v0.8.8

douwe
fonte
3
request.setTimeout "define o tempo limite do soquete após o tempo limite em milissegundos de inatividade no soquete." Eu acho que essa pergunta é sobre o tempo limite da solicitação, independentemente da atividade.
Ostergaard de
Observe que, da mesma forma que nas respostas abaixo que usam o soquete envolvido diretamente, o req.abort () causa um evento de erro, que deve ser tratado por em ('erro') etc.
KLoozen
4
request.setTimeout não aborta a solicitação, precisamos chamar abort manualmente no retorno de chamada de tempo limite.
Udhaya
18

O anwser Rob Evans funciona corretamente para mim, mas quando eu uso request.abort (), ocorre um erro de desligamento de soquete que permanece sem tratamento.

Tive que adicionar um manipulador de erros para o objeto de solicitação:

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
fonte
3
onde está a myTimeoutfunção? (editar: os documentos dizem: O mesmo que vincular ao evento de tempo limite nodejs.org/api/… )
Ben Muircroft
Observe que ECONNRESETpode acontecer em ambos os casos: o cliente fecha o soquete e o servidor fecha a conexão. Para determinar se foi feito pelo cliente ligando, abort()há um abortevento especial
Kirill
8

Existe um método mais simples.

Em vez de usar setTimeout ou trabalhar com soquete diretamente,
podemos usar 'timeout' nas 'opções' nos usos do cliente

Abaixo está o código do servidor e do cliente, em 3 partes.

Módulo e parte de opções:

'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 do servidor:

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 do 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 você colocar todas as 3 partes acima em um arquivo, "a.js", e depois executar:

node a.js

então, a saída será:

startServer
Server listening on http://127.0.0.1:3000

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

Espero que ajude.

Manohar Reddy Poreddy
fonte
2

Para mim - aqui está uma maneira menos confusa de fazer o 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
fonte
2

Elaborando a resposta @douwe aqui é onde você colocaria um tempo limite em uma solicitação 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 () também está bom.

SpiRail
fonte
1

Você deve passar a referência para solicitar como abaixo

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

O evento de erro de solicitação será acionado

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

Udhaya
fonte
Adicionar bind (req) não mudou nada para mim. O que o bind faz neste caso?
SpiRail
-1

Curioso, o que acontece se você usar puro em net.socketsvez disso? Aqui está um exemplo de código que criei para fins de teste:

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_
fonte
Se eu usar o tempo limite do soquete e emitir dois pedidos um após o outro (sem esperar que o primeiro termine), o segundo pedido tem o soquete indefinido (pelo menos no momento em que tento definir o tempo limite) .. talvez deva haver algo como on ("pronto") no soquete ... não sei.
Claudio
@Claudio Você pode atualizar seu código para mostrar várias solicitações sendo feitas?
onteria_
1
Claro ... é um pouco longo e usei paste2.org se isso não for um problema: paste2.org/p/1448487
Claudio
@Claudio Hmm ok, configurar um ambiente de teste e escrever algum código de teste vai demorar, então minha resposta pode chegar amanhã (horário do Pacífico) como um FYI
onteria_
@Claudio, na verdade, dando uma olhada, seu código não parece corresponder ao seu erro. Ele está dizendo que setTimeout está sendo chamado em um valor indefinido, mas a forma como você está chamando é por meio da versão global, então não há como isso ser indefinido, me deixando um tanto confuso.
onteria_