diff --git a/doc/api/http.markdown b/doc/api/http.markdown index e01ab5c91b..c17c6b2ea2 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -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 @@ -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 @@ -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 diff --git a/lib/http.js b/lib/http.js index 50571bf313..aafbe5f60f 100644 --- a/lib/http.js +++ b/lib/http.js @@ -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; @@ -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); }; @@ -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; @@ -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(); diff --git a/test/simple/test-http-set-timeout-server.js b/test/simple/test-http-set-timeout-server.js new file mode 100644 index 0000000000..21be505b70 --- /dev/null +++ b/test/simple/test-http-set-timeout-server.js @@ -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() {}); +});