Як встановити час очікування для http.request () у Node?


92

Я намагаюся встановити тайм-аут для клієнта HTTP, який використовує http.request без удачі. Поки що я зробив це:

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

Будь-які підказки?


1
Знайдено ця відповідь (це теж працює) , але мені цікаво , якщо є що - то інше для http.request () stackoverflow.com/questions/6129240 / ...
Claudio

Відповіді:


26

Тільки для уточнення відповіді вище :

Тепер можна використовувати timeoutопцію та відповідну подію запиту:

// 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
Цікаво, якщо це щось інше, ніж простоsetTimeout(req.abort.bind(req), 3000);
Олександр Міллс

@AlexanderMills, тоді ви, ймовірно, хочете очистити час очікування вручну, коли запит спрацював нормально.
Сергій Коваленко

4
Зверніть увагу, що це строго час connectочікування, після встановлення сокета це не впливає. Отже, це не допоможе серверу, який занадто довго тримає сокет відкритим (вам все одно доведеться прокрутити свій власний за допомогою setTimeout). timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.
UpTheCreek

Це те, що я шукаю при спробі підключення. Підвішені з'єднання можуть трапитись при спробі отримати доступ до порту на сервері, який не слухає. До речі, API змінено на request.destroy( abortзастаріло.) Крім того, це відрізняється відsetTimeout
dturvene

91

2019 Оновлення

Зараз є різні способи вирішити це питання більш елегантно. Будь ласка, перегляньте деякі інші відповіді на цю тему. Технологія рухається швидко, тому відповіді часто можуть досить швидко застаріти. Моя відповідь все одно буде працювати, але варто також розглянути альтернативні варіанти.

Відповідь 2012 року

Використовуючи ваш код, проблема полягає в тому, що ви не чекали призначення сокету запиту, перш ніж намагатися встановити речі на об’єкт сокета. Це все асинхронно, тому:

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

Подія 'socket' запускається, коли запиту присвоюється об'єкт socket.


Це справді має цілковитий сенс. Проблема в тому, що зараз я не можу перевірити цю конкретну проблему (час минає ...). Тож поки що можу лише проголосувати відповідь :) Дякую.
Клаудіо

Без турбот. Майте на увазі, що це працює лише на останній версії node, наскільки мені відомо. Я тестував на попередній версії (5.0.3-pre), на мою думку, і це не запустило подію socket.
Роб Еванс,

1
Інший спосіб вирішити це - використовувати стандартний виклик setTimeout. Вам потрібно буде тримати ідентифікатор setTimeout за допомогою: var id = setTimeout (...); так що ви можете скасувати його, якщо отримаєте дані про дані і т. д. Хороший спосіб - зберегти їх у самому об'єкті запиту, а потім clearTimeout, якщо ви отримаєте деякі дані.
Роб Еванс,

1
Ви пропали безвісти); в кінці вимог. Оскільки це не 6 символів, я не можу редагувати його для вас.
JR Smith

Це працює (дякую!), Але майте на увазі, що відповідь все-таки зрештою надійде. req.abort()на даний момент це не є стандартною функціональністю. Отже, socket.onвам може знадобитися встановити змінну типу, timeout = trueа потім належним чином обробити час очікування в обробнику відповідей
Джонатан Бенн,

40

На даний момент існує метод зробити це безпосередньо в об’єкті запиту:

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

Це метод ярлика, який прив’язується до події сокета, а потім створює час очікування.

Довідково: Node.js v0.8.8 Посібник та документація


3
request.setTimeout "встановлює час очікування сокета після таймауту в мілісекундах бездіяльності на сокеті." Я вважаю, що це питання стосується часу очікування запиту незалежно від діяльності.
ostergaard

Зверніть увагу, що, як і у відповідях нижче, де безпосередньо використовується задіяний сокет, req.abort () спричиняє подію помилки, яку слід обробляти on ('error') тощо
KLoozen

4
request.setTimeout не скасовує запит, нам потрібно викликати abort вручну під час зворотного виклику timeout.
Удхая

18

Анвансер Роб Еванса працює для мене коректно, але коли я використовую request.abort (), виникає помилка зависання сокета, яка залишається необробленою.

Мені довелося додати обробник помилок для об'єкта запиту:

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
де myTimeoutфункція? (редагувати: у документах сказано: Те саме, що і прив'язка до події тайм-ауту nodejs.org/api/… )
Бен Меркрофт,

Зверніть увагу, що це ECONNRESETможе статися в обох випадках: клієнт закриває сокет, а сервер - з'єднання. Щоб визначити, чи було це зроблено клієнтом, зателефонувавши, abort()існує додаткова abortподія
Кирило

8

Існує простіший метод.

Замість використання setTimeout або безпосередньої роботи з сокетом,
ми можемо використовувати 'timeout' у 'options' у клієнтських використаннях

Нижче наведено код як сервера, так і клієнта, що складається з 3 частин.

Частина модуля та опцій:

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

Серверна частина:

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

Клієнтська частина:

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

Якщо ви покладете всі вищезазначені 3 частини в один файл, "a.js", а потім запустіть:

node a.js

тоді результат буде:

startServer
Server listening on http://127.0.0.1:3000

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

Сподіваюся, це допомагає.


2

Для мене - ось менш заплутаний спосіб зробити це 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

Опрацьовуючи відповідь @douwe, тут ви можете встановити час очікування на запит 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 () також чудово.


1

Вам слід передати посилання на запит, як показано нижче

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

Запит на помилку буде ініційовано

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


Додавання bind (req) для мене нічого не змінило. Що робить bind у цьому випадку?
SpiRail

-1

Цікаво, а що станеться, якщо net.socketsзамість цього використовувати прямий ? Ось зразок коду, який я зібрав для тестування:

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

Якщо я використовую час очікування сокета, і я видаю два запити один за одним (не чекаючи завершення першого), у другому запиті сокет невизначений (принаймні на даний момент я намагаюся встановити час очікування) .. можливо, це повинно бути щось на зразок on ("готовий") на розетці ... я не знаю.
Клаудіо

@Claudio Чи можете ви оновити свій код, щоб показати, як зроблено кілька запитів?
onteria_

1
Звичайно ... це трохи довго, і я використовував paste2.org, якщо це не проблема: paste2.org/p/1448487
Клаудіо

@Claudio Hmm гаразд, налаштування тестового середовища та написання тестового коду вимагатимуть, тому моя відповідь може прийти колись завтра (за тихоокеанським часом) як FYI
onteria_

@Claudio насправді подивившись, ваш код, здається, не збігається з вашою помилкою. Це говорить про те, що setTimeout викликається з невизначеним значенням, але спосіб його виклику - через глобальну версію, тому немає способу, який міг би бути невизначеним, що змусило мене досить розгубитися.
onteria_
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.