mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
http: added connection closing methods
Fixes: https://github.com/nodejs/node/issues/41578 PR-URL: https://github.com/nodejs/node/pull/42812 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
This commit is contained in:
@@ -1455,6 +1455,23 @@ added: v0.1.90
|
||||
|
||||
Stops the server from accepting new connections. See [`net.Server.close()`][].
|
||||
|
||||
### `server.closeAllConnections()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Closes all connections connected to this server.
|
||||
|
||||
### `server.closeIdleConnections()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Closes all connections connected to this server which are not sending a request
|
||||
or waiting for a response.
|
||||
|
||||
### `server.headersTimeout`
|
||||
|
||||
<!-- YAML
|
||||
|
||||
@@ -133,7 +133,23 @@ added: v0.1.90
|
||||
* `callback` {Function}
|
||||
* Returns: {https.Server}
|
||||
|
||||
See [`server.close()`][`http.close()`] from the HTTP module for details.
|
||||
See [`http.Server.close()`][].
|
||||
|
||||
### `server.closeAllConnections()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
See [`http.Server.closeAllConnections()`][].
|
||||
|
||||
### `server.closeIdleConnections()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
See [`http.Server.closeIdleConnections()`][].
|
||||
|
||||
### `server.headersTimeout`
|
||||
|
||||
@@ -529,8 +545,10 @@ headers: max-age=0; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; p
|
||||
[`http.Server#requestTimeout`]: http.md#serverrequesttimeout
|
||||
[`http.Server#setTimeout()`]: http.md#serversettimeoutmsecs-callback
|
||||
[`http.Server#timeout`]: http.md#servertimeout
|
||||
[`http.Server.close()`]: http.md#serverclosecallback
|
||||
[`http.Server.closeAllConnections()`]: http.md#servercloseallconnections
|
||||
[`http.Server.closeIdleConnections()`]: http.md#servercloseidleconnections
|
||||
[`http.Server`]: http.md#class-httpserver
|
||||
[`http.close()`]: http.md#serverclosecallback
|
||||
[`http.createServer()`]: http.md#httpcreateserveroptions-requestlistener
|
||||
[`http.get()`]: http.md#httpgetoptions-callback
|
||||
[`http.request()`]: http.md#httprequestoptions-callback
|
||||
|
||||
@@ -409,10 +409,11 @@ function storeHTTPOptions(options) {
|
||||
function setupConnectionsTracking(server) {
|
||||
// Start connection handling
|
||||
server[kConnections] = new ConnectionsList();
|
||||
if (server.headersTimeout > 0 || server.requestTimeout > 0) {
|
||||
server[kConnectionsCheckingInterval] =
|
||||
setInterval(checkConnections.bind(server), server.connectionsCheckingInterval).unref();
|
||||
}
|
||||
|
||||
// This checker is started without checking whether any headersTimeout or requestTimeout is non zero
|
||||
// otherwise it would not be started if such timeouts are modified after createServer.
|
||||
server[kConnectionsCheckingInterval] =
|
||||
setInterval(checkConnections.bind(server), server.connectionsCheckingInterval).unref();
|
||||
}
|
||||
|
||||
function Server(options, requestListener) {
|
||||
@@ -458,6 +459,22 @@ Server.prototype.close = function() {
|
||||
ReflectApply(net.Server.prototype.close, this, arguments);
|
||||
};
|
||||
|
||||
Server.prototype.closeAllConnections = function() {
|
||||
const connections = this[kConnections].all();
|
||||
|
||||
for (let i = 0, l = connections.length; i < l; i++) {
|
||||
connections[i].socket.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype.closeIdleConnections = function() {
|
||||
const connections = this[kConnections].idle();
|
||||
|
||||
for (let i = 0, l = connections.length; i < l; i++) {
|
||||
connections[i].socket.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype.setTimeout = function setTimeout(msecs, callback) {
|
||||
this.timeout = msecs;
|
||||
if (callback)
|
||||
@@ -489,6 +506,10 @@ Server.prototype[EE.captureRejectionSymbol] = function(err, event, ...args) {
|
||||
};
|
||||
|
||||
function checkConnections() {
|
||||
if (this.headersTimeout === 0 && this.requestTimeout === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expired = this[kConnections].expired(this.headersTimeout, this.requestTimeout);
|
||||
|
||||
for (let i = 0; i < expired.length; i++) {
|
||||
|
||||
@@ -87,6 +87,10 @@ function Server(opts, requestListener) {
|
||||
ObjectSetPrototypeOf(Server.prototype, tls.Server.prototype);
|
||||
ObjectSetPrototypeOf(Server, tls.Server);
|
||||
|
||||
Server.prototype.closeAllConnections = HttpServer.prototype.closeAllConnections;
|
||||
|
||||
Server.prototype.closeIdleConnections = HttpServer.prototype.closeIdleConnections;
|
||||
|
||||
Server.prototype.setTimeout = HttpServer.prototype.setTimeout;
|
||||
|
||||
/**
|
||||
|
||||
@@ -257,9 +257,10 @@ class Parser : public AsyncWrap, public StreamListener {
|
||||
SET_SELF_SIZE(Parser)
|
||||
|
||||
int on_message_begin() {
|
||||
// Important: Pop from the list BEFORE resetting the last_message_start_
|
||||
// Important: Pop from the lists BEFORE resetting the last_message_start_
|
||||
// otherwise std::set.erase will fail.
|
||||
if (connectionsList_ != nullptr) {
|
||||
connectionsList_->Pop(this);
|
||||
connectionsList_->PopActive(this);
|
||||
}
|
||||
|
||||
@@ -270,6 +271,7 @@ class Parser : public AsyncWrap, public StreamListener {
|
||||
status_message_.Reset();
|
||||
|
||||
if (connectionsList_ != nullptr) {
|
||||
connectionsList_->Push(this);
|
||||
connectionsList_->PushActive(this);
|
||||
}
|
||||
|
||||
@@ -492,14 +494,19 @@ class Parser : public AsyncWrap, public StreamListener {
|
||||
int on_message_complete() {
|
||||
HandleScope scope(env()->isolate());
|
||||
|
||||
// Important: Pop from the list BEFORE resetting the last_message_start_
|
||||
// Important: Pop from the lists BEFORE resetting the last_message_start_
|
||||
// otherwise std::set.erase will fail.
|
||||
if (connectionsList_ != nullptr) {
|
||||
connectionsList_->Pop(this);
|
||||
connectionsList_->PopActive(this);
|
||||
}
|
||||
|
||||
last_message_start_ = 0;
|
||||
|
||||
if (connectionsList_ != nullptr) {
|
||||
connectionsList_->Push(this);
|
||||
}
|
||||
|
||||
if (num_fields_)
|
||||
Flush(); // Flush trailing HTTP headers.
|
||||
|
||||
@@ -666,12 +673,14 @@ class Parser : public AsyncWrap, public StreamListener {
|
||||
if (connectionsList != nullptr) {
|
||||
parser->connectionsList_ = connectionsList;
|
||||
|
||||
parser->connectionsList_->Push(parser);
|
||||
|
||||
// This protects from a DoS attack where an attacker establishes
|
||||
// the connection without sending any data on applications where
|
||||
// server.timeout is left to the default value of zero.
|
||||
parser->last_message_start_ = uv_hrtime();
|
||||
|
||||
// Important: Push into the lists AFTER setting the last_message_start_
|
||||
// otherwise std::set.erase will fail later.
|
||||
parser->connectionsList_->Push(parser);
|
||||
parser->connectionsList_->PushActive(parser);
|
||||
} else {
|
||||
parser->connectionsList_ = nullptr;
|
||||
@@ -1044,10 +1053,14 @@ class Parser : public AsyncWrap, public StreamListener {
|
||||
};
|
||||
|
||||
bool ParserComparator::operator()(const Parser* lhs, const Parser* rhs) const {
|
||||
if (lhs->last_message_start_ == 0) {
|
||||
return false;
|
||||
} else if (rhs->last_message_start_ == 0) {
|
||||
if (lhs->last_message_start_ == 0 && rhs->last_message_start_ == 0) {
|
||||
// When both parsers are idle, guarantee strict order by
|
||||
// comparing pointers as ints.
|
||||
return lhs < rhs;
|
||||
} else if (lhs->last_message_start_ == 0) {
|
||||
return true;
|
||||
} else if (rhs->last_message_start_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return lhs->last_message_start_ < rhs->last_message_start_;
|
||||
|
||||
57
test/parallel/test-http-server-close-all.js
Normal file
57
test/parallel/test-http-server-close-all.js
Normal file
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const { createServer } = require('http');
|
||||
const { connect } = require('net');
|
||||
|
||||
let connections = 0;
|
||||
|
||||
const server = createServer(common.mustCall(function(req, res) {
|
||||
res.writeHead(200, { Connection: 'keep-alive' });
|
||||
res.end();
|
||||
}), {
|
||||
headersTimeout: 0,
|
||||
keepAliveTimeout: 0,
|
||||
requestTimeout: common.platformTimeout(60000),
|
||||
});
|
||||
|
||||
server.on('connection', function() {
|
||||
connections++;
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
const port = server.address().port;
|
||||
|
||||
// Create a first request but never finish it
|
||||
const client1 = connect(port);
|
||||
|
||||
client1.on('close', common.mustCall());
|
||||
|
||||
client1.on('error', () => {});
|
||||
|
||||
client1.write('GET / HTTP/1.1');
|
||||
|
||||
// Create a second request, let it finish but leave the connection opened using HTTP keep-alive
|
||||
const client2 = connect(port);
|
||||
let response = '';
|
||||
|
||||
client2.on('data', common.mustCall((chunk) => {
|
||||
response += chunk.toString('utf-8');
|
||||
|
||||
if (response.endsWith('0\r\n\r\n')) {
|
||||
assert(response.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive'));
|
||||
assert.strictEqual(connections, 2);
|
||||
|
||||
server.closeAllConnections();
|
||||
server.close(common.mustCall());
|
||||
|
||||
// This timer should never go off as the server.close should shut everything down
|
||||
setTimeout(common.mustNotCall(), common.platformTimeout(1500)).unref();
|
||||
}
|
||||
}));
|
||||
|
||||
client2.on('close', common.mustCall());
|
||||
|
||||
client2.write('GET / HTTP/1.1\r\n\r\n');
|
||||
});
|
||||
69
test/parallel/test-http-server-close-idle.js
Normal file
69
test/parallel/test-http-server-close-idle.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const { createServer } = require('http');
|
||||
const { connect } = require('net');
|
||||
|
||||
let connections = 0;
|
||||
|
||||
const server = createServer(common.mustCall(function(req, res) {
|
||||
res.writeHead(200, { Connection: 'keep-alive' });
|
||||
res.end();
|
||||
}), {
|
||||
headersTimeout: 0,
|
||||
keepAliveTimeout: 0,
|
||||
requestTimeout: common.platformTimeout(60000),
|
||||
});
|
||||
|
||||
server.on('connection', function() {
|
||||
connections++;
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
const port = server.address().port;
|
||||
let client1Closed = false;
|
||||
let client2Closed = false;
|
||||
|
||||
// Create a first request but never finish it
|
||||
const client1 = connect(port);
|
||||
|
||||
client1.on('close', common.mustCall(() => {
|
||||
client1Closed = true;
|
||||
}));
|
||||
|
||||
client1.on('error', () => {});
|
||||
|
||||
client1.write('GET / HTTP/1.1');
|
||||
|
||||
// Create a second request, let it finish but leave the connection opened using HTTP keep-alive
|
||||
const client2 = connect(port);
|
||||
let response = '';
|
||||
|
||||
client2.on('data', common.mustCall((chunk) => {
|
||||
response += chunk.toString('utf-8');
|
||||
|
||||
if (response.endsWith('0\r\n\r\n')) {
|
||||
assert(response.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive'));
|
||||
assert.strictEqual(connections, 2);
|
||||
|
||||
server.closeIdleConnections();
|
||||
server.close(common.mustCall());
|
||||
|
||||
// Check that only the idle connection got closed
|
||||
setTimeout(common.mustCall(() => {
|
||||
assert(!client1Closed);
|
||||
assert(client2Closed);
|
||||
|
||||
server.closeAllConnections();
|
||||
server.close(common.mustCall());
|
||||
}), common.platformTimeout(500)).unref();
|
||||
}
|
||||
}));
|
||||
|
||||
client2.on('close', common.mustCall(() => {
|
||||
client2Closed = true;
|
||||
}));
|
||||
|
||||
client2.write('GET / HTTP/1.1\r\n\r\n');
|
||||
});
|
||||
68
test/parallel/test-https-server-close-all.js
Normal file
68
test/parallel/test-https-server-close-all.js
Normal file
@@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
}
|
||||
|
||||
const { createServer } = require('https');
|
||||
const { connect } = require('tls');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const options = {
|
||||
key: fixtures.readKey('agent1-key.pem'),
|
||||
cert: fixtures.readKey('agent1-cert.pem')
|
||||
};
|
||||
|
||||
let connections = 0;
|
||||
|
||||
const server = createServer(options, common.mustCall(function(req, res) {
|
||||
res.writeHead(200, { Connection: 'keep-alive' });
|
||||
res.end();
|
||||
}), {
|
||||
headersTimeout: 0,
|
||||
keepAliveTimeout: 0,
|
||||
requestTimeout: common.platformTimeout(60000),
|
||||
});
|
||||
|
||||
server.on('connection', function() {
|
||||
connections++;
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
const port = server.address().port;
|
||||
|
||||
// Create a first request but never finish it
|
||||
const client1 = connect({ port, rejectUnauthorized: false });
|
||||
|
||||
client1.on('close', common.mustCall());
|
||||
|
||||
client1.on('error', () => {});
|
||||
|
||||
client1.write('GET / HTTP/1.1');
|
||||
|
||||
// Create a second request, let it finish but leave the connection opened using HTTP keep-alive
|
||||
const client2 = connect({ port, rejectUnauthorized: false });
|
||||
let response = '';
|
||||
|
||||
client2.on('data', common.mustCall((chunk) => {
|
||||
response += chunk.toString('utf-8');
|
||||
|
||||
if (response.endsWith('0\r\n\r\n')) {
|
||||
assert(response.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive'));
|
||||
assert.strictEqual(connections, 2);
|
||||
|
||||
server.closeAllConnections();
|
||||
server.close(common.mustCall());
|
||||
|
||||
// This timer should never go off as the server.close should shut everything down
|
||||
setTimeout(common.mustNotCall(), common.platformTimeout(1500)).unref();
|
||||
}
|
||||
}));
|
||||
|
||||
client2.on('close', common.mustCall());
|
||||
|
||||
client2.write('GET / HTTP/1.1\r\n\r\n');
|
||||
});
|
||||
80
test/parallel/test-https-server-close-idle.js
Normal file
80
test/parallel/test-https-server-close-idle.js
Normal file
@@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
}
|
||||
|
||||
const { createServer } = require('https');
|
||||
const { connect } = require('tls');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const options = {
|
||||
key: fixtures.readKey('agent1-key.pem'),
|
||||
cert: fixtures.readKey('agent1-cert.pem')
|
||||
};
|
||||
|
||||
let connections = 0;
|
||||
|
||||
const server = createServer(options, common.mustCall(function(req, res) {
|
||||
res.writeHead(200, { Connection: 'keep-alive' });
|
||||
res.end();
|
||||
}), {
|
||||
headersTimeout: 0,
|
||||
keepAliveTimeout: 0,
|
||||
requestTimeout: common.platformTimeout(60000),
|
||||
});
|
||||
|
||||
server.on('connection', function() {
|
||||
connections++;
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
const port = server.address().port;
|
||||
let client1Closed = false;
|
||||
let client2Closed = false;
|
||||
|
||||
// Create a first request but never finish it
|
||||
const client1 = connect({ port, rejectUnauthorized: false });
|
||||
|
||||
client1.on('close', common.mustCall(() => {
|
||||
client1Closed = true;
|
||||
}));
|
||||
|
||||
client1.on('error', () => {});
|
||||
|
||||
client1.write('GET / HTTP/1.1');
|
||||
|
||||
// Create a second request, let it finish but leave the connection opened using HTTP keep-alive
|
||||
const client2 = connect({ port, rejectUnauthorized: false });
|
||||
let response = '';
|
||||
|
||||
client2.on('data', common.mustCall((chunk) => {
|
||||
response += chunk.toString('utf-8');
|
||||
|
||||
if (response.endsWith('0\r\n\r\n')) {
|
||||
assert(response.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive'));
|
||||
assert.strictEqual(connections, 2);
|
||||
|
||||
server.closeIdleConnections();
|
||||
server.close(common.mustCall());
|
||||
|
||||
// Check that only the idle connection got closed
|
||||
setTimeout(common.mustCall(() => {
|
||||
assert(!client1Closed);
|
||||
assert(client2Closed);
|
||||
|
||||
server.closeAllConnections();
|
||||
server.close(common.mustCall());
|
||||
}), common.platformTimeout(500)).unref();
|
||||
}
|
||||
}));
|
||||
|
||||
client2.on('close', common.mustCall(() => {
|
||||
client2Closed = true;
|
||||
}));
|
||||
|
||||
client2.write('GET / HTTP/1.1\r\n\r\n');
|
||||
});
|
||||
Reference in New Issue
Block a user