mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
http: destroy sockets after keepAliveTimeout
Implement server.keepAliveTimeout in addition to server.timeout to prevent temporary socket/memory leaking in keep-alive mode. PR-URL: https://github.com/nodejs/node/pull/2534 Author: Timur Shemsedinov <timur.shemsedinov@gmail.com> Author: Alexey Orlenko <eaglexrlnk@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com>
This commit is contained in:
parent
21c0c0275a
commit
0aa7ef5950
@ -832,17 +832,33 @@ Returns `server`.
|
||||
added: v0.9.12
|
||||
-->
|
||||
|
||||
* {number} Defaults to 120000 (2 minutes).
|
||||
* {number} Timeout in milliseconds. Defaults to 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.
|
||||
A value of 0 will disable the timeout behavior on incoming connections.
|
||||
|
||||
Set to 0 to disable any kind of automatic timeout behavior on incoming
|
||||
connections.
|
||||
*Note*: The socket timeout logic is set up on connection, so changing this
|
||||
value only affects new connections to the server, not any existing connections.
|
||||
|
||||
### server.keepAliveTimeout
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {number} Timeout in milliseconds. Defaults to 5000 (5 seconds).
|
||||
|
||||
The number of milliseconds of inactivity a server needs to wait for additional
|
||||
incoming data, after it has finished writing the last response, before a socket
|
||||
will be destroyed. If the server receives new data before the keep-alive
|
||||
timeout has fired, it will reset the regular inactivity timeout, i.e.,
|
||||
[`server.timeout`][].
|
||||
|
||||
A value of 0 will disable the keep-alive timeout behavior on incoming connections.
|
||||
|
||||
*Note*: The socket timeout logic is set up on connection, so changing this
|
||||
value only affects new connections to the server, not any existing connections.
|
||||
|
||||
## Class: http.ServerResponse
|
||||
<!-- YAML
|
||||
@ -1764,6 +1780,7 @@ There are a few special headers that should be noted.
|
||||
[`response.write(data, encoding)`]: #http_response_write_chunk_encoding_callback
|
||||
[`response.writeContinue()`]: #http_response_writecontinue
|
||||
[`response.writeHead()`]: #http_response_writehead_statuscode_statusmessage_headers
|
||||
[`server.timeout`]: #http_server_timeout
|
||||
[`socket.setKeepAlive()`]: net.html#net_socket_setkeepalive_enable_initialdelay
|
||||
[`socket.setNoDelay()`]: net.html#net_socket_setnodelay_nodelay
|
||||
[`socket.setTimeout()`]: net.html#net_socket_settimeout_timeout_callback
|
||||
|
@ -38,6 +38,14 @@ added: v0.11.2
|
||||
|
||||
See [`http.Server#timeout`][].
|
||||
|
||||
### server.keepAliveTimeout
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
- {number} Defaults to 5000 (5 seconds).
|
||||
|
||||
See [`http.Server#keepAliveTimeout`][].
|
||||
|
||||
## https.createServer(options[, requestListener])
|
||||
<!-- YAML
|
||||
added: v0.3.4
|
||||
@ -229,6 +237,7 @@ const req = https.request(options, (res) => {
|
||||
|
||||
[`Agent`]: #https_class_https_agent
|
||||
[`http.Agent`]: http.html#http_class_http_agent
|
||||
[`http.Server#keepAliveTimeout`]: http.html#http_server_keepalivetimeout
|
||||
[`http.Server#setTimeout()`]: http.html#http_server_settimeout_msecs_callback
|
||||
[`http.Server#timeout`]: http.html#http_server_timeout
|
||||
[`http.Server`]: http.html#http_class_http_server
|
||||
|
@ -271,7 +271,7 @@ function Server(requestListener) {
|
||||
this.on('connection', connectionListener);
|
||||
|
||||
this.timeout = 2 * 60 * 1000;
|
||||
|
||||
this.keepAliveTimeout = 5000;
|
||||
this._pendingResponseData = 0;
|
||||
this.maxHeadersCount = null;
|
||||
}
|
||||
@ -323,7 +323,8 @@ function connectionListener(socket) {
|
||||
// inactive responses. If more data than the high watermark is queued - we
|
||||
// need to pause TCP socket/HTTP parser, and wait until the data will be
|
||||
// sent to the client.
|
||||
outgoingData: 0
|
||||
outgoingData: 0,
|
||||
keepAliveTimeoutSet: false
|
||||
};
|
||||
state.onData = socketOnData.bind(undefined, this, socket, parser, state);
|
||||
state.onEnd = socketOnEnd.bind(undefined, this, socket, parser, state);
|
||||
@ -431,8 +432,16 @@ function socketOnEnd(server, socket, parser, state) {
|
||||
function socketOnData(server, socket, parser, state, d) {
|
||||
assert(!socket._paused);
|
||||
debug('SERVER socketOnData %d', d.length);
|
||||
var ret = parser.execute(d);
|
||||
|
||||
if (state.keepAliveTimeoutSet) {
|
||||
socket.setTimeout(0);
|
||||
if (server.timeout) {
|
||||
socket.setTimeout(server.timeout);
|
||||
}
|
||||
state.keepAliveTimeoutSet = false;
|
||||
}
|
||||
|
||||
var ret = parser.execute(d);
|
||||
onParserExecuteCommon(server, socket, parser, state, ret, d);
|
||||
}
|
||||
|
||||
@ -496,7 +505,7 @@ function onParserExecuteCommon(server, socket, parser, state, ret, d) {
|
||||
}
|
||||
}
|
||||
|
||||
function resOnFinish(req, res, socket, state) {
|
||||
function resOnFinish(req, res, socket, state, server) {
|
||||
// Usually the first incoming element should be our request. it may
|
||||
// be that in the case abortIncoming() was called that the incoming
|
||||
// array will be empty.
|
||||
@ -514,6 +523,12 @@ function resOnFinish(req, res, socket, state) {
|
||||
|
||||
if (res._last) {
|
||||
socket.destroySoon();
|
||||
} else if (state.outgoing.length === 0) {
|
||||
if (server.keepAliveTimeout) {
|
||||
socket.setTimeout(0);
|
||||
socket.setTimeout(server.keepAliveTimeout);
|
||||
state.keepAliveTimeoutSet = true;
|
||||
}
|
||||
} else {
|
||||
// start sending the next message
|
||||
var m = state.outgoing.shift();
|
||||
@ -560,7 +575,8 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
|
||||
|
||||
// When we're finished writing the response, check if this is the last
|
||||
// response, if so destroy the socket.
|
||||
res.on('finish', resOnFinish.bind(undefined, req, res, socket, state));
|
||||
res.on('finish',
|
||||
resOnFinish.bind(undefined, req, res, socket, state, server));
|
||||
|
||||
if (req.headers.expect !== undefined &&
|
||||
(req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
|
||||
|
@ -59,6 +59,7 @@ function Server(opts, requestListener) {
|
||||
});
|
||||
|
||||
this.timeout = 2 * 60 * 1000;
|
||||
this.keepAliveTimeout = 5000;
|
||||
}
|
||||
inherits(Server, tls.Server);
|
||||
exports.Server = Server;
|
||||
|
95
test/parallel/test-http-server-keep-alive-timeout.js
Normal file
95
test/parallel/test-http-server-keep-alive-timeout.js
Normal file
@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
const tests = [];
|
||||
|
||||
function test(fn) {
|
||||
if (!tests.length) {
|
||||
process.nextTick(run);
|
||||
}
|
||||
tests.push(fn);
|
||||
}
|
||||
|
||||
function run() {
|
||||
const fn = tests.shift();
|
||||
if (fn) fn(run);
|
||||
}
|
||||
|
||||
test(function serverEndKeepAliveTimeoutWithPipeline(cb) {
|
||||
let socket;
|
||||
let destroyedSockets = 0;
|
||||
let timeoutCount = 0;
|
||||
let requestCount = 0;
|
||||
process.on('exit', () => {
|
||||
assert.strictEqual(timeoutCount, 1);
|
||||
assert.strictEqual(requestCount, 3);
|
||||
assert.strictEqual(destroyedSockets, 1);
|
||||
});
|
||||
const server = http.createServer((req, res) => {
|
||||
socket = req.socket;
|
||||
requestCount++;
|
||||
res.end();
|
||||
});
|
||||
server.setTimeout(200, (socket) => {
|
||||
timeoutCount++;
|
||||
socket.destroy();
|
||||
});
|
||||
server.keepAliveTimeout = 50;
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = {
|
||||
port: server.address().port,
|
||||
allowHalfOpen: true
|
||||
};
|
||||
const c = net.connect(options, () => {
|
||||
c.write('GET /1 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /2 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /3 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
});
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
if (socket.destroyed) destroyedSockets++;
|
||||
cb();
|
||||
}, 1000);
|
||||
}));
|
||||
});
|
||||
|
||||
test(function serverNoEndKeepAliveTimeoutWithPipeline(cb) {
|
||||
let socket;
|
||||
let destroyedSockets = 0;
|
||||
let timeoutCount = 0;
|
||||
let requestCount = 0;
|
||||
process.on('exit', () => {
|
||||
assert.strictEqual(timeoutCount, 1);
|
||||
assert.strictEqual(requestCount, 3);
|
||||
assert.strictEqual(destroyedSockets, 1);
|
||||
});
|
||||
const server = http.createServer((req, res) => {
|
||||
socket = req.socket;
|
||||
requestCount++;
|
||||
});
|
||||
server.setTimeout(200, (socket) => {
|
||||
timeoutCount++;
|
||||
socket.destroy();
|
||||
});
|
||||
server.keepAliveTimeout = 50;
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = {
|
||||
port: server.address().port,
|
||||
allowHalfOpen: true
|
||||
};
|
||||
const c = net.connect(options, () => {
|
||||
c.write('GET /1 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /2 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /3 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
});
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
if (socket.destroyed) destroyedSockets++;
|
||||
cb();
|
||||
}, 1000);
|
||||
}));
|
||||
});
|
103
test/parallel/test-https-server-keep-alive-timeout.js
Normal file
103
test/parallel/test-https-server-keep-alive-timeout.js
Normal file
@ -0,0 +1,103 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const https = require('https');
|
||||
const tls = require('tls');
|
||||
const fs = require('fs');
|
||||
|
||||
const tests = [];
|
||||
|
||||
const serverOptions = {
|
||||
key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
|
||||
cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem')
|
||||
};
|
||||
|
||||
function test(fn) {
|
||||
if (!tests.length) {
|
||||
process.nextTick(run);
|
||||
}
|
||||
tests.push(fn);
|
||||
}
|
||||
|
||||
function run() {
|
||||
const fn = tests.shift();
|
||||
if (fn) fn(run);
|
||||
}
|
||||
|
||||
test(function serverKeepAliveTimeoutWithPipeline(cb) {
|
||||
let socket;
|
||||
let destroyedSockets = 0;
|
||||
let timeoutCount = 0;
|
||||
let requestCount = 0;
|
||||
process.on('exit', function() {
|
||||
assert.strictEqual(timeoutCount, 1);
|
||||
assert.strictEqual(requestCount, 3);
|
||||
assert.strictEqual(destroyedSockets, 1);
|
||||
});
|
||||
const server = https.createServer(serverOptions, (req, res) => {
|
||||
socket = req.socket;
|
||||
requestCount++;
|
||||
res.end();
|
||||
});
|
||||
server.setTimeout(200, (socket) => {
|
||||
timeoutCount++;
|
||||
socket.destroy();
|
||||
});
|
||||
server.keepAliveTimeout = 50;
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = {
|
||||
port: server.address().port,
|
||||
allowHalfOpen: true,
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
const c = tls.connect(options, () => {
|
||||
c.write('GET /1 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /2 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /3 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
});
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
if (socket.destroyed) destroyedSockets++;
|
||||
cb();
|
||||
}, 1000);
|
||||
}));
|
||||
});
|
||||
|
||||
test(function serverNoEndKeepAliveTimeoutWithPipeline(cb) {
|
||||
let socket;
|
||||
let destroyedSockets = 0;
|
||||
let timeoutCount = 0;
|
||||
let requestCount = 0;
|
||||
process.on('exit', () => {
|
||||
assert.strictEqual(timeoutCount, 1);
|
||||
assert.strictEqual(requestCount, 3);
|
||||
assert.strictEqual(destroyedSockets, 1);
|
||||
});
|
||||
const server = https.createServer(serverOptions, (req, res) => {
|
||||
socket = req.socket;
|
||||
requestCount++;
|
||||
});
|
||||
server.setTimeout(200, (socket) => {
|
||||
timeoutCount++;
|
||||
socket.destroy();
|
||||
});
|
||||
server.keepAliveTimeout = 50;
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const options = {
|
||||
port: server.address().port,
|
||||
allowHalfOpen: true,
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
const c = tls.connect(options, () => {
|
||||
c.write('GET /1 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /2 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
c.write('GET /3 HTTP/1.1\r\nHost: localhost\r\n\r\n');
|
||||
});
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
if (socket && socket.destroyed) destroyedSockets++;
|
||||
cb();
|
||||
}, 1000);
|
||||
}));
|
||||
});
|
Loading…
Reference in New Issue
Block a user