¿Cómo establecer un tiempo de espera en un http.request () en Node?

92

Intento establecer un tiempo de espera en un cliente HTTP que usa http.request sin suerte. Hasta ahora lo que hice es esto:

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

¿Alguna pista?

Claudio
fuente
1
Encontré esta respuesta (también funciona) pero me pregunto si hay algo diferente para http.request () stackoverflow.com/questions/6129240/…
Claudio

Respuestas:

26

Solo para aclarar la respuesta anterior :

Ahora es posible utilizar la timeoutopción y el evento de solicitud correspondiente:

// 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
fuente
1
Me pregunto si esto es diferente a simplementesetTimeout(req.abort.bind(req), 3000);
Alexander Mills
@AlexanderMills, entonces probablemente desee borrar el tiempo de espera manualmente, cuando la solicitud funcionó bien.
Sergei Kovalenko
4
Tenga en cuenta que este es estrictamente el connecttiempo de espera, una vez que se establece el socket, no tiene ningún efecto. Por lo tanto, esto no ayudará con un servidor que mantiene el socket abierto durante demasiado tiempo (aún necesitará lanzar el suyo con setTimeout). timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.
UpTheCreek
Esto es lo que estoy buscando en un intento de conexión colgada. Las conexiones colgadas pueden ocurrir bastante cuando se intenta acceder a un puerto en un servidor que no está escuchando. Por cierto, la API ha cambiado a request.destroy( abortestá en desuso). Además, esto es diferente desetTimeout
dturvene
91

Actualización 2019

Hay varias formas de manejar esto de manera más elegante ahora. Consulte algunas otras respuestas en este hilo. La tecnología se mueve rápido, por lo que las respuestas a menudo pueden quedar desactualizadas con bastante rapidez. Mi respuesta seguirá funcionando, pero también vale la pena buscar alternativas.

2012 respuesta

Usando su código, el problema es que no ha esperado a que se asigne un socket a la solicitud antes de intentar establecer cosas en el objeto socket. Todo es asincrónico, así que:

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

El evento 'socket' se activa cuando a la solicitud se le asigna un objeto socket.

Rob Evans
fuente
De hecho, esto tiene mucho sentido. El problema es que ahora no puedo probar este problema en particular (pasa el tiempo ...). Así que solo puedo votar la respuesta por ahora :) Gracias.
Claudio
Sin preocupaciones. Tenga en cuenta que esto solo funciona en la última versión del nodo que yo sepa. Probé en una versión anterior (5.0.3-pre) creo que no disparó el evento de socket.
Rob Evans
1
La otra forma de manejar esto es usar una llamada setTimeout estándar de bog. Deberá mantener el id de setTimeout con: var id = setTimeout (...); para que puedas cancelarlo si recibes un on data, etc. Una buena forma es almacenarlo en el objeto de solicitud y luego borrarTimeout si obtienes algunos datos.
Rob Evans
1
Estás perdido ); al final de req.on. Como no tiene 6 caracteres, no puedo editarlo por ti.
JR Smith
Esto funciona (¡gracias!), Pero tenga en cuenta que la respuesta aún llegará eventualmente. req.abort()no parece ser una funcionalidad estándar en este momento. Por lo tanto, socket.ones posible que desee establecer una variable como timeout = truey luego manejar el tiempo de espera de manera adecuada en el controlador de respuesta
Jonathan Benn
40

En este momento, existe un método para hacer esto directamente en el objeto de solicitud:

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

Este es un método de acceso directo que se une al evento de socket y luego crea el tiempo de espera.

Referencia: Node.js v0.8.8 Manual y documentación

Douwe
fuente
3
request.setTimeout "establece el tiempo de espera del socket después de milisegundos de inactividad en el socket". Pienso que esta pregunta trata sobre el tiempo de espera de la solicitud independientemente de la actividad.
Ostergaard
Tenga en cuenta que, al igual que en las respuestas a continuación, que usan el socket involucrado directamente, req.abort () causa un evento de error, que debe ser manejado por on ('error') etc.
KLoozen
4
request.setTimeout no abortará la solicitud, necesitamos llamar a abortar manualmente en la devolución de llamada del tiempo de espera.
Udhaya
18

El anwser de Rob Evans funciona correctamente para mí, pero cuando uso request.abort (), se produce un error de colgar el socket que no se controla.

Tuve que agregar un controlador de errores para el objeto de solicitud:

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
fuente
3
donde esta la myTimeoutfuncion? (editar: los documentos dicen: Igual que vincularse al evento de tiempo de espera nodejs.org/api/… )
Ben Muircroft
Tenga en cuenta que ECONNRESETpuede suceder en ambos casos: el cliente cierra el socket y el servidor cierra la conexión. Para determinar si lo hizo el cliente llamando, abort()hay un abortevento especial
Kirill
8

Hay un método más sencillo.

En lugar de usar setTimeout o trabajar con socket directamente,
podemos usar 'timeout' en las 'opciones' en los usos del cliente

A continuación se muestra el código tanto del servidor como del cliente, en 3 partes.

Módulo y parte de opciones:

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

Si pones las 3 partes anteriores en un archivo, "a.js", y luego ejecutas:

node a.js

entonces, la salida 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 ayude.

Manohar Reddy Poreddy
fuente
2

Para mí, aquí hay una forma menos confusa de hacer el 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
fuente
2

Desarrollando la respuesta @douwe aquí es donde pondría un tiempo de espera en una solicitud 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 () también está bien.

SpiRail
fuente
1

Debe pasar la referencia para solicitar como a continuación

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

Se activará el evento de error de solicitud

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

Udhaya
fuente
Agregar bind (req) no cambió nada para mí. ¿Qué hace bind en este caso?
SpiRail
-1

Curioso, ¿qué pasa si usa recto en su net.socketslugar? Aquí hay un código de muestra que reuní con fines de prueba:

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_
fuente
Si uso el tiempo de espera del socket y emito dos solicitudes una tras otra (sin esperar a que termine la primera), la segunda solicitud tiene el socket indefinido (al menos en el momento en que intento establecer el tiempo de espera) ... tal vez debería haber algo como en ("listo") en el enchufe ... no lo sé.
Claudio
@Claudio ¿Puede actualizar su código para mostrar que se realizan varias solicitudes?
onteria_
1
Por supuesto ... es un poco largo y usé paste2.org si esto no es un problema: paste2.org/p/1448487
Claudio
@Claudio Hmm está bien, configurar un entorno de prueba y escribir un código de prueba va a tomar alrededor, por lo que mi respuesta podría llegar mañana (hora del Pacífico) como un FYI
onteria_
@Claudio en realidad, al ver su código, no parece coincidir con su error. Está diciendo que se está llamando a setTimeout en un valor indefinido, pero la forma en que lo está llamando es a través de la versión global, por lo que no hay forma de que pueda ser indefinido, dejándome bastante confundido.
onteria_