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
|
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
|
The number of milliseconds of inactivity before a socket is presumed
|
||||||
to have timed out.
|
to have timed out.
|
||||||
|
|
||||||
Note that the socket timeout logic is set up on connection, so
|
A value of 0 will disable the timeout behavior on incoming connections.
|
||||||
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
|
*Note*: The socket timeout logic is set up on connection, so changing this
|
||||||
connections.
|
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
|
## Class: http.ServerResponse
|
||||||
<!-- YAML
|
<!-- 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.write(data, encoding)`]: #http_response_write_chunk_encoding_callback
|
||||||
[`response.writeContinue()`]: #http_response_writecontinue
|
[`response.writeContinue()`]: #http_response_writecontinue
|
||||||
[`response.writeHead()`]: #http_response_writehead_statuscode_statusmessage_headers
|
[`response.writeHead()`]: #http_response_writehead_statuscode_statusmessage_headers
|
||||||
|
[`server.timeout`]: #http_server_timeout
|
||||||
[`socket.setKeepAlive()`]: net.html#net_socket_setkeepalive_enable_initialdelay
|
[`socket.setKeepAlive()`]: net.html#net_socket_setkeepalive_enable_initialdelay
|
||||||
[`socket.setNoDelay()`]: net.html#net_socket_setnodelay_nodelay
|
[`socket.setNoDelay()`]: net.html#net_socket_setnodelay_nodelay
|
||||||
[`socket.setTimeout()`]: net.html#net_socket_settimeout_timeout_callback
|
[`socket.setTimeout()`]: net.html#net_socket_settimeout_timeout_callback
|
||||||
|
@ -38,6 +38,14 @@ added: v0.11.2
|
|||||||
|
|
||||||
See [`http.Server#timeout`][].
|
See [`http.Server#timeout`][].
|
||||||
|
|
||||||
|
### server.keepAliveTimeout
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
- {number} Defaults to 5000 (5 seconds).
|
||||||
|
|
||||||
|
See [`http.Server#keepAliveTimeout`][].
|
||||||
|
|
||||||
## https.createServer(options[, requestListener])
|
## https.createServer(options[, requestListener])
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.3.4
|
added: v0.3.4
|
||||||
@ -229,6 +237,7 @@ const req = https.request(options, (res) => {
|
|||||||
|
|
||||||
[`Agent`]: #https_class_https_agent
|
[`Agent`]: #https_class_https_agent
|
||||||
[`http.Agent`]: http.html#http_class_http_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#setTimeout()`]: http.html#http_server_settimeout_msecs_callback
|
||||||
[`http.Server#timeout`]: http.html#http_server_timeout
|
[`http.Server#timeout`]: http.html#http_server_timeout
|
||||||
[`http.Server`]: http.html#http_class_http_server
|
[`http.Server`]: http.html#http_class_http_server
|
||||||
|
@ -271,7 +271,7 @@ function Server(requestListener) {
|
|||||||
this.on('connection', connectionListener);
|
this.on('connection', connectionListener);
|
||||||
|
|
||||||
this.timeout = 2 * 60 * 1000;
|
this.timeout = 2 * 60 * 1000;
|
||||||
|
this.keepAliveTimeout = 5000;
|
||||||
this._pendingResponseData = 0;
|
this._pendingResponseData = 0;
|
||||||
this.maxHeadersCount = null;
|
this.maxHeadersCount = null;
|
||||||
}
|
}
|
||||||
@ -323,7 +323,8 @@ function connectionListener(socket) {
|
|||||||
// inactive responses. If more data than the high watermark is queued - we
|
// 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
|
// need to pause TCP socket/HTTP parser, and wait until the data will be
|
||||||
// sent to the client.
|
// sent to the client.
|
||||||
outgoingData: 0
|
outgoingData: 0,
|
||||||
|
keepAliveTimeoutSet: false
|
||||||
};
|
};
|
||||||
state.onData = socketOnData.bind(undefined, this, socket, parser, state);
|
state.onData = socketOnData.bind(undefined, this, socket, parser, state);
|
||||||
state.onEnd = socketOnEnd.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) {
|
function socketOnData(server, socket, parser, state, d) {
|
||||||
assert(!socket._paused);
|
assert(!socket._paused);
|
||||||
debug('SERVER socketOnData %d', d.length);
|
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);
|
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
|
// Usually the first incoming element should be our request. it may
|
||||||
// be that in the case abortIncoming() was called that the incoming
|
// be that in the case abortIncoming() was called that the incoming
|
||||||
// array will be empty.
|
// array will be empty.
|
||||||
@ -514,6 +523,12 @@ function resOnFinish(req, res, socket, state) {
|
|||||||
|
|
||||||
if (res._last) {
|
if (res._last) {
|
||||||
socket.destroySoon();
|
socket.destroySoon();
|
||||||
|
} else if (state.outgoing.length === 0) {
|
||||||
|
if (server.keepAliveTimeout) {
|
||||||
|
socket.setTimeout(0);
|
||||||
|
socket.setTimeout(server.keepAliveTimeout);
|
||||||
|
state.keepAliveTimeoutSet = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// start sending the next message
|
// start sending the next message
|
||||||
var m = state.outgoing.shift();
|
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
|
// When we're finished writing the response, check if this is the last
|
||||||
// response, if so destroy the socket.
|
// 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 &&
|
if (req.headers.expect !== undefined &&
|
||||||
(req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
|
(req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
|
||||||
|
@ -59,6 +59,7 @@ function Server(opts, requestListener) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.timeout = 2 * 60 * 1000;
|
this.timeout = 2 * 60 * 1000;
|
||||||
|
this.keepAliveTimeout = 5000;
|
||||||
}
|
}
|
||||||
inherits(Server, tls.Server);
|
inherits(Server, tls.Server);
|
||||||
exports.Server = 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…
x
Reference in New Issue
Block a user