如何在 Node 中设置 http.request()的超时?

我试图在一个使用 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();

有线索吗?

193078 次浏览

Curious, what happens if you use straight net.sockets instead? Here's some sample code I put together for testing purposes:

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

2019 Update

There are various ways to handle this more elegantly now. Please see some other answers on this thread. Tech moves fast so answers can often become out of date fairly quickly. My answer will still work but it's worth looking at alternatives as well.

2012 Answer

Using your code, the issue is that you haven't waited for a socket to be assigned to the request before attempting to set stuff on the socket object. It's all async so:

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

The 'socket' event is fired when the request is assigned a socket object.

At this moment there is a method to do this directly on the request object:

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

This is a shortcut method that binds to the socket event and then creates the timeout.

Reference: Node.js v0.8.8 Manual & Documentation

The Rob Evans anwser works correctly for me but when I use request.abort(), it occurs to throw a socket hang up error which stays unhandled.

I had to add an error handler for the request object :

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

You should pass the reference to request like below

var options = { ... }
var req = http.request(options, function(res) {
// Usual stuff: on(data), on(end), chunks, etc...
});


req.setTimeout(60000, function(){
this.abort();
});
req.write('something');
req.end();

Request error event will get triggered

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

For me - here is a less confusing way of doing the 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 ↑
});
});

There is simpler method.

Instead of using setTimeout or working with socket directly,
We can use 'timeout' in the 'options' in client uses

Below is code of both server and client, in 3 parts.

Module and options part:

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

Server part:

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

Client part:

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

If you put all the above 3 parts in one file, "a.js", and then run:

node a.js

then, output will be:

startServer
Server listening on http://127.0.0.1:3000


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

Hope that helps.

Elaborating on the answer @douwe here is where you would put a timeout on a http request.

// 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() is also fine.

Just to clarify the answer above:

Now it is possible to use timeout option and the corresponding request event:

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

See the docs: enter image description here