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?


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

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.


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


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

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.


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

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.


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


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

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_
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.