Skip to content

Commit

Permalink
http: More useful setTimeout API on server
Browse files Browse the repository at this point in the history
This adds the following to HTTP:

* server.setTimeout(msecs, callback)
  Sets all new connections to time out after the specified time, at
  which point it emits 'timeout' on the server, passing the socket as an
  argument.
  In this way, timeouts can be handled in one place consistently.
* req.setTimeout(), res.setTimeout()
  Essentially an alias to req/res.socket.setTimeout(), but without
  having to delve into a "buried" object.  Adds a listener on the
  req/res object, but not on the socket.
* server.timeout
  Number of milliseconds before incoming connections time out.
  (Default=1000*60*2, as before.)

Furthermore, if the user sets up their own timeout listener on either
the server, the request, or the response, then the default behavior
(destroying the socket) is suppressed.

Fix #3460
  • Loading branch information
isaacs committed Mar 6, 2013
1 parent db5d58e commit d258fb0
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 3 deletions.
51 changes: 51 additions & 0 deletions doc/api/http.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,36 @@ Stops the server from accepting new connections. See [net.Server.close()][].
Limits maximum incoming headers count, equal to 1000 by default. If set to 0 -
no limit will be applied.

### server.setTimeout(msecs, callback)

* `msecs` {Number}
* `callback` {Function}

Sets the timeout value for sockets, and emits a `'timeout'` event on
the Server object, passing the socket as an argument, if a timeout
occurs.

If there is a `'timeout'` event listener on the Server object, then it
will be called with the timed-out socket as an argument.

By default, the Server's timeout value is 2 minutes, and sockets are
destroyed automatically if they time out. However, if you assign a
callback to the Server's `'timeout'` event, then you are responsible
for handling socket timeouts.

### server.timeout

* {Number} Default = 120000 (2 minutes)

The number of milliseconds of inactivity before a socket is presumed
to have timed out.

Note that the socket timeout logic is set up on connection, so
changing this value only affects *new* connections to the server, not
any existing connections.

Set to 0 to disable any kind of automatic timeout behavior on incoming
connections.

## Class: http.ServerResponse

Expand Down Expand Up @@ -236,6 +265,21 @@ should be used to determine the number of bytes in a given encoding.
And Node does not check whether Content-Length and the length of the body
which has been transmitted are equal or not.

### response.setTimeout(msecs, callback)

* `msecs` {Number}
* `callback` {Function}

Sets the Socket's timeout value to `msecs`. If a callback is
provided, then it is added as a listener on the `'timeout'` event on
the response object.

If no `'timeout'` listener is added to the request, the response, or
the server, then sockets are destroyed when they time out. If you
assign a handler on the request, the response, or the server's
`'timeout'` events, then it is your responsibility to handle timed out
sockets.

### response.statusCode

When using implicit headers (not calling `response.writeHead()` explicitly), this property
Expand Down Expand Up @@ -789,6 +833,13 @@ Example:

The request/response trailers object. Only populated after the 'end' event.

### message.setTimeout(msecs, callback)

* `msecs` {Number}
* `callback` {Function}

Calls `message.connection.setTimeout(msecs, callback)`.

### message.setEncoding([encoding])

Set the encoding for data emitted by the `'data'` event. See [stream.setEncoding()][] for more
Expand Down
40 changes: 37 additions & 3 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,13 @@ util.inherits(IncomingMessage, Stream.Readable);
exports.IncomingMessage = IncomingMessage;


IncomingMessage.prototype.setTimeout = function(msecs, callback) {
if (callback)
this.on('timeout', callback);
this.socket.setTimeout(msecs);
};


IncomingMessage.prototype.read = function(n) {
this._consuming = true;
this.read = Stream.Readable.prototype.read;
Expand Down Expand Up @@ -445,6 +452,13 @@ util.inherits(OutgoingMessage, Stream);
exports.OutgoingMessage = OutgoingMessage;


OutgoingMessage.prototype.setTimeout = function(msecs, callback) {
if (callback)
this.on('timeout', callback);
this.socket.setTimeout(msecs);
};


OutgoingMessage.prototype.destroy = function(error) {
this.socket.destroy(error);
};
Expand Down Expand Up @@ -1795,10 +1809,19 @@ function Server(requestListener) {
this.addListener('clientError', function(err, conn) {
conn.destroy(err);
});

this.timeout = 2 * 60 * 1000;
}
util.inherits(Server, net.Server);


Server.prototype.setTimeout = function(msecs, callback) {
this.timeout = msecs;
if (callback)
this.on('timeout', callback);
};


exports.Server = Server;


Expand Down Expand Up @@ -1834,9 +1857,20 @@ function connectionListener(socket) {

httpSocketSetup(socket);

socket.setTimeout(2 * 60 * 1000); // 2 minute timeout
socket.once('timeout', function() {
socket.destroy();
// If the user has added a listener to the server,
// request, or response, then it's their responsibility.
// otherwise, destroy on timeout by default
if (self.timeout)
socket.setTimeout(self.timeout);
socket.on('timeout', function() {
var req = socket.parser && socket.parser.incoming;
var reqTimeout = req && req.emit('timeout', socket);
var res = socket._httpMessage;
var resTimeout = res && res.emit('timeout', socket);
var serverTimeout = self.emit('timeout', socket);

if (!reqTimeout && !resTimeout && !serverTimeout)
socket.destroy();
});

var parser = parsers.alloc();
Expand Down
95 changes: 95 additions & 0 deletions test/simple/test-http-set-timeout-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var common = require('../common.js');
var assert = require('assert');
var http = require('http');

var tests = [];

function test(fn) {
if (!tests.length)
process.nextTick(run);
tests.push(fn);
}

function run() {
var fn = tests.shift();
if (fn) {
console.log('# %s', fn.name);
fn(run);
} else
console.log('ok');
}

test(function serverTimeout(cb) {
var caughtTimeout = false;
process.on('exit', function() {
assert(caughtTimeout);
});
var server = http.createServer(function(req, res) {
// just do nothing, we should get a timeout event.
});
server.listen(common.PORT);
server.setTimeout(50, function(socket) {
caughtTimeout = true;
socket.destroy();
server.close();
cb();
});
http.get({ port: common.PORT }).on('error', function() {});
});

test(function serverRequestTimeout(cb) {
var caughtTimeout = false;
process.on('exit', function() {
assert(caughtTimeout);
});
var server = http.createServer(function(req, res) {
// just do nothing, we should get a timeout event.
req.setTimeout(50, function() {
caughtTimeout = true;
req.socket.destroy();
server.close();
cb();
});
});
server.listen(common.PORT);
http.get({ port: common.PORT }).on('error', function() {});
});

test(function serverResponseTimeout(cb) {
var caughtTimeout = false;
process.on('exit', function() {
assert(caughtTimeout);
});
var server = http.createServer(function(req, res) {
// just do nothing, we should get a timeout event.
res.setTimeout(50, function() {
caughtTimeout = true;
res.socket.destroy();
server.close();
cb();
});
});
server.listen(common.PORT);
http.get({ port: common.PORT }).on('error', function() {});
});

0 comments on commit d258fb0

Please sign in to comment.