117

I'm trying to set a timeout on an HTTP client that uses http.request with no luck. So far what I did is this:

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

Any hints?

Claudio
  • 5,740
  • 5
  • 33
  • 40
  • 1
    Found this answer (it works too) but I wonder if there is something different for http.request() http://stackoverflow.com/questions/6129240/how-to-set-timeout-for-http-createclient-in-node-js – Claudio Jun 02 '11 at 13:22

9 Answers9

99

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.

Rob Evans
  • 6,750
  • 4
  • 39
  • 56
  • This make total sense, indeed. The problem is that now I can't test this particular issue (time passes...). So I can only upvote the answer for now :) Thank you. – Claudio Mar 29 '12 at 13:07
  • No worries. Keep in mind this only works on the latest version of node as far as I know. I tested on a previous version (5.0.3-pre) I think and it didn't fire the socket event. – Rob Evans Mar 29 '12 at 16:21
  • 1
    The other way to handle this is to use a bog-standard setTimeout call. You'll need to keep hold of the setTimeout id with: var id = setTimeout(...); so that you can cancel it if you recieve an on data etc. A good way is to store it in the request object itself then clearTimeout if you get some data. – Rob Evans Mar 29 '12 at 16:23
  • 1
    You're missing ); at the end of req.on. Since it's not 6 characters, I can't edit it for you. – JR Smith May 07 '15 at 15:57
  • This works (thanks!), but keep in mind that the response will still eventually arrive. `req.abort()` doesn't seem to be standard functionality at the moment. Hence, in `socket.on` you might want to set a variable like `timeout = true` and then handle the timeout appropriately in the response handler – Jonathan Benn Jun 08 '17 at 14:45
  • 2
    Could you mention one more elegant solution? Saying there's more elegant solutions isn't super helpful without more info – B T Jun 12 '20 at 17:39
64

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

jakub.g
  • 38,512
  • 12
  • 92
  • 130
Sergei Kovalenko
  • 1,402
  • 12
  • 17
  • 1
    Wonder if this is any different than just `setTimeout(req.abort.bind(req), 3000);` – Alexander Mills May 31 '19 at 00:53
  • @AlexanderMills, then you probably want to clear the timeout manually, when the request worked fine. – Sergei Kovalenko Jun 14 '19 at 13:48
  • 13
    Note that this is strictly the `connect` timeout, once the socket is established it has no effect. So this will not help with a server that keeps the socket open for too long (you will still need to roll your own with setTimeout). `timeout : A number specifying the socket timeout in milliseconds. This will set the timeout before the socket is connected.` – UpTheCreek Oct 31 '19 at 16:53
  • 2
    This is what I'm looking for on a hung connection attempt. Hung connections can happen a good bit when trying to access a port on a server that isn't listening. BTW, the API has changed to `request.destroy` (`abort` is deprecated.) Also, this is different from `setTimeout` – dturvene Jun 27 '20 at 03:21
  • 1
    As @dturvene said, use `request.destroy` which will trigger the request's "error" handler with an `ECONNRESET` event. You can pass your own custom error to `request.destroy` as well. – jowo Jan 29 '22 at 14:51
  • Official docs: https://nodejs.org/api/http.html#httprequestoptions-callback – jakub.g Mar 11 '22 at 16:26
  • I think UpTheCreek's answer is the best: connect timeout and response timeout should be both taken care. – jiajianrong Dec 26 '22 at 08:20
44

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

Andrey Hohutkin
  • 2,703
  • 1
  • 16
  • 17
douwe
  • 1,305
  • 10
  • 12
  • 5
    request.setTimeout "sets the socket to timeout after timeout milliseconds of inactivity on the socket." Me thinks this question is about timing out the request regardless of activity. – ostergaard Jan 20 '13 at 14:00
  • 1
    Please note that, the same as in the answers below which use the involved socket directly, the req.abort() causes an error event, which should be handled by on('error' ) etc. – KLoozen Jan 10 '16 at 17:31
  • 6
    request.setTimeout won't abort the request, we need to call abort manually in the timeout callback. – Nadhas Feb 23 '16 at 05:52
20

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();
Ron Klein
  • 9,178
  • 9
  • 55
  • 88
Pierre Maoui
  • 5,976
  • 2
  • 27
  • 28
  • 3
    where is the `myTimeout` function? (edit: the docs say: Same as binding to the timeout event https://nodejs.org/api/http.html#http_request_settimeout_timeout_callback) – Ben Muircroft Sep 20 '16 at 21:08
  • Notice that `ECONNRESET` can happen in both cases: client closes socket and server closes connection. To determine if it was done by client by calling `abort()` there is spceial `abort` event – Kirill Nov 08 '16 at 09:16
12

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.

Manohar Reddy Poreddy
  • 25,399
  • 9
  • 157
  • 140
  • Since request.abort() is deprecated, this is the approach I use in production. Note that if you pass your own Error to request.destroy(), it will be sent to the 'error' handler. Otherwise, the 'error' handler will be sent an 'ECONNRESET' event. – jowo Jan 29 '22 at 14:48
2

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 ↑
        });
    });
Ben Muircroft
  • 2,936
  • 8
  • 39
  • 66
2

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.

SpiRail
  • 1,377
  • 19
  • 28
0

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));
  });
Nadhas
  • 5,421
  • 2
  • 28
  • 42
  • Adding bind(req) didn't change anything for me. What does bind do in this case? – SpiRail Apr 03 '18 at 10:10
  • IMHO i think this makes things a lot more confusing. Using `req` instead of `this` makes things a little less complex. – Elmer Jul 06 '21 at 07:58
-2

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();
onteria_
  • 68,181
  • 7
  • 71
  • 64
  • If I use the socket timeout, and I issue two requests one after another (without waiting the first to finish), the second request has the socket undefined (at least at the moment I try to set the timeout).. maybe there should be something like on("ready") on the socket... I don't know. – Claudio Jun 02 '11 at 17:18
  • @Claudio Can you update your code to show multiple request being made? – onteria_ Jun 02 '11 at 17:25
  • 1
    Of course... it's a bit long and I used paste2.org if this is not a problem: http://paste2.org/p/1448487 – Claudio Jun 02 '11 at 17:42
  • @Claudio Hmm okay, setting up a test environment and writing some test code is going to take about, so my reply might come sometime tomorrow (Pacific Time) as an FYI – onteria_ Jun 02 '11 at 17:53
  • @Claudio actually taking a look your code doesn't seem to match up with your error. It's saying that setTimeout is being called on an undefined value, but the way you're calling it is through the global version, so there's no way that could be undefined, leaving me rather confused. – onteria_ Jun 03 '11 at 03:13