ノードのhttp.request()にタイムアウトを設定するにはどうすればよいですか?


92

運が悪いhttp.requestを使用するHTTPクライアントにタイムアウトを設定しようとしています。これまでのところ、これは次のとおりです。

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/...
クラウディオ・

回答:


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);
Alexander Mills

@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.destroyabort廃止予定です)。また、これは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」イベントが発生します。


実際、これは完全に理にかなっています。問題は、この特定の問題をテストできないことです(時間の経過...)。だから私は今のところ答えを賛成することしかできません:)ありがとう。
クラウディオ

心配ない。これは、私の知る限り、最新バージョンのノードでのみ機能することに注意してください。以前のバージョン(5.0.3-pre)でテストしましたが、ソケットイベントは発生しませんでした。
Rob Evans

1
これを処理するもう1つの方法は、沼標準のsetTimeout呼び出しを使用することです。次のようにしてsetTimeout idを保持する必要があります。var id = setTimeout(...); onデータなどを受け取った場合にキャンセルできるようにします。データを取得した場合は、それをリクエストオブジェクト自体に格納し、clearTimeoutを実行することをお勧めします。
Rob Evans

1
あなたは間違っている ); req.onの最後に。6文字ではないため、編集できません。
JRスミス

これは機能します(ありがとう!)が、最終的には応答が到着することを覚えておいてください。req.abort()現時点では標準機能ではないようです。したがって、のsocket.onような変数を設定timeout = trueし、応答ハンドラーでタイムアウトを適切に処理する必要がある場合があります
Jonathan Benn

40

現時点では、リクエストオブジェクトで直接これを行うメソッドがあります。

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

これは、ソケットイベントにバインドしてからタイムアウトを作成するショートカットメソッドです。

リファレンス:Node.js v0.8.8のマニュアルとドキュメント


3
request.setTimeout "ソケットの非アクティブ状態がタイムアウトミリ秒後にタイムアウトするようにソケットを設定します。" この質問は、アクティビティに関係なくリクエストをタイムアウトにすることに関するものだと私は思っています。
ostergaard 2013年

してください以下の回答と同じでは直接関与したソケットを使用し、そのノート、req.abort()(「エラー」)上で扱われるべきエラーイベントを引き起こすなど
KLoozen

4
request.setTimeoutはリクエストを中止しません。タイムアウトコールバックで手動で中止を呼び出す必要があります。
ウダヤ共和国2016

18

Rob Evans 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/…へのバインドと同じ)
Ben Muircroft

ECONNRESET両方のケースで発生する可能性があることに注意してください:クライアントがソケットを閉じ、サーバーが接続を閉じます。呼び出しによってクライアントによって行われたかどうかを確認するabort()には、特別なabortイベントがある
Kirill

8

より簡単な方法があります。

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つの部分すべてを1つのファイル "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 2018

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

ソケットタイムアウトを使用し、最初の完了を待たずに2つの要求を次々に発行する場合、2番目の要求にはソケットが未定義です(少なくともタイムアウトを設定しようとしたとき)。ソケットのon( "ready")のようなもの...わかりません。
Claudio

@Claudio複数のリクエストが行われていることを示すようにコードを更新できますか?
onteria_

1
もちろん...少し長いので、問題がない場合はpaste2.orgを使用しました。paste2.org/ p
Claudio

@Claudioうーん、テスト環境をセットアップしてテストコードを書くのに時間がかかるので、明日(太平洋標準時)に返事が来るかもしれません
onteria_

@Claudioは実際にあなたのコードを見て、あなたのエラーと一致しないようです。それは、setTimeoutが未定義の値で呼び出されていることを示していますが、それを呼び出す方法はグローバルバージョンを介しているため、未定義になる可能性はなく、混乱を招きます。
onteria_
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.