Как установить тайм-аут для http.request () в Node?

94

Я пытаюсь установить тайм-аут на 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/…
Клаудио

Ответы:

28

Просто чтобы прояснить ответ выше :

Теперь можно использовать 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
92

Обновление 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();

Событие «сокет» запускается, когда запросу назначается объект сокета.

Роб Эванс
источник
Это действительно имеет смысл. Проблема в том, что сейчас я не могу протестировать именно эту проблему (время идет ...). Так что пока я могу только проголосовать за ответ :) Спасибо.
Claudio
Без проблем. Имейте в виду, что, насколько мне известно, это работает только с последней версией узла. Я тестировал предыдущую версию (5.0.3-pre), я думаю, и она не запускала событие сокета.
Роб Эванс,
1
Другой способ справиться с этим - использовать стандартный вызов setTimeout. Вам нужно сохранить идентификатор setTimeout с помощью: var id = setTimeout (...); так что вы можете отменить его, если вы получили данные и т. д. Хороший способ - сохранить его в самом объекте запроса, а затем clearTimeout, если вы получите некоторые данные.
Роб Эванс,
1
Ты пропускаешь ); в конце req.on. Поскольку это не 6 символов, я не могу редактировать его для вас.
JR Smith
Это работает (спасибо!), Но имейте в виду, что ответ все равно придет. req.abort()на данный момент не кажется стандартной функциональностью. Следовательно, socket.onвы можете захотеть установить такую ​​переменную, timeout = trueа затем соответствующим образом обработать тайм-аут в обработчике ответа
Джонатан Бенн
40

На данный момент есть способ сделать это непосредственно с объектом запроса:

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

Это метод ярлыка, который связывается с событием сокета и затем создает тайм-аут.

Ссылка: Node.js v0.8.8 Руководство и документация

douwe
источник
3
request.setTimeout «устанавливает время ожидания сокета после истечения времени ожидания в миллисекундах бездействия на сокете». Я думаю, что этот вопрос касается тайм-аута запроса независимо от активности.
ostergaard
Обратите внимание, что, как и в ответах ниже, которые напрямую используют задействованный сокет, req.abort () вызывает событие ошибки, которое должно обрабатываться on ('error') и т. Д.
KLoozen
4
request.setTimeout не прервет запрос, нам нужно вызвать прерывание вручную в обратном вызове тайм-аута.
Udhaya
18

Anwser Роба Эванса работает у меня правильно, но когда я использую 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событие
Кирилл
9

Есть способ попроще.

Вместо использования setTimeout или непосредственной работы с сокетом
мы можем использовать «тайм-аут» в «опциях» в клиентских приложениях.

Ниже приведен код сервера и клиента, разделенный на 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 () тоже подойдет.

SpiRail
источник
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) для меня ничего не изменило. Что делает в этом случае привязка?
SpiRail 03
-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();
onteria_
источник
Если я использую тайм-аут сокета и отправляю два запроса один за другим (не дожидаясь завершения первого), у второго запроса сокет не определен (по крайней мере, в тот момент, когда я пытаюсь установить тайм-аут) ... возможно, должно быть что-то вроде на ("готово") на розетке ... не знаю.
Claudio
@Claudio Можете ли вы обновить свой код, чтобы показать, что выполняется несколько запросов?
onteria_ 02
1
Конечно ... это немного длинновато, и я использовал paste2.org, если это не проблема: paste2.org/p/1448487
Claudio
@Claudio Хм, ладно, настройка тестовой среды и написание некоторого тестового кода займет около, так что мой ответ может прийти завтра (по тихоокеанскому времени) в качестве FYI
onteria_
@Claudio на самом деле смотрит, как ваш код не соответствует вашей ошибке. Он говорит, что setTimeout вызывается для неопределенного значения, но способ, которым вы его вызываете, осуществляется через глобальную версию, поэтому не может быть undefined, что меня довольно смущает.
onteria_ 03