diff --git a/doc/api/http.md b/doc/api/http.md index 543a25dfdf80c2a..21378cfc52df35b 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -1443,6 +1443,31 @@ This event is guaranteed to be passed an instance of the {net.Socket} class, a subclass of {stream.Duplex}, unless the user specifies a socket type other than {net.Socket}. +### `server.activeConnections()` + + + +* Returns: {net.Socket\[]} + +Returns an array of all the sockets connected to the server that have an +active request. + +A request is considered active if it has not fully received by the server and +it has not expired yet according to `server.headersTimeout` (for the headers) or +`server.requestTimeout` (for the entire request). + +### `server.connections()` + + + +* Returns: {net.Socket\[]} + +Returns an array of all the sockets connected to the server. + ### `server.close([callback])` + +* Returns: {net.Socket\[]} + +Returns an array of all the sockets connected to the server that have an +expired request according to `server.headersTimeout` (for the headers) or +`server.requestTimeout` (for the entire request). + ### `server.headersTimeout` + +* Returns: {net.Socket\[]} + +Returns an array of all the sockets connected to the server that have no +active requests. + +The list of sockets returned in this list are usually still connected because +they are using HTTP Keep-Alive. + ### `server.listen()` Starts the HTTP server listening for connections. diff --git a/doc/api/https.md b/doc/api/https.md index c022bc7ee30a1b2..a466c487f33d8d3 100644 --- a/doc/api/https.md +++ b/doc/api/https.md @@ -124,6 +124,26 @@ added: v0.3.4 See [`http.Server`][] for more information. +### `server.activeConnections()` + + + +* Returns: {net.Socket\[]} + +See [`http.Server.activeConnections()`][]. + +### `server.connections()` + + + +* Returns: {net.Socket\[]} + +See [`http.Server.connections()`][]. + ### `server.close([callback])` + +* Returns: {net.Socket\[]} + +See [`http.Server.expiredConnections()`][]. ### `server.headersTimeout` @@ -143,7 +173,17 @@ added: v11.3.0 * {number} **Default:** `60000` -See [`http.Server#headersTimeout`][]. +See [`http.Server.headersTimeout`][]. + +### `server.idleConnections()` + + + +* Returns: {net.Socket\[]} + +See [`http.Server.idleConnections()`][]. ### `server.listen()` @@ -154,7 +194,7 @@ This method is identical to [`server.listen()`][] from [`net.Server`][]. * {number} **Default:** `2000` -See [`http.Server#maxHeadersCount`][]. +See [`http.Server.maxHeadersCount`][]. ### `server.requestTimeout` @@ -164,7 +204,7 @@ added: v14.11.0 * {number} **Default:** `0` -See [`http.Server#requestTimeout`][]. +See [`http.Server.requestTimeout`][]. ### `server.setTimeout([msecs][, callback])` @@ -176,7 +216,7 @@ added: v0.11.2 * `callback` {Function} * Returns: {https.Server} -See [`http.Server#setTimeout()`][]. +See [`http.Server.setTimeout()`][]. ### `server.timeout` @@ -190,7 +230,7 @@ changes: * {number} **Default:** 0 (no timeout) -See [`http.Server#timeout`][]. +See [`http.Server.timeout`][]. ### `server.keepAliveTimeout` @@ -200,7 +240,7 @@ added: v8.0.0 * {number} **Default:** `5000` (5 seconds) -See [`http.Server#keepAliveTimeout`][]. +See [`http.Server.keepAliveTimeout`][]. ## `https.createServer([options][, requestListener])` @@ -523,14 +563,18 @@ headers: max-age=0; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; p [`http.Agent(options)`]: http.md#new-agentoptions [`http.Agent`]: http.md#class-httpagent [`http.ClientRequest`]: http.md#class-httpclientrequest -[`http.Server#headersTimeout`]: http.md#serverheaderstimeout -[`http.Server#keepAliveTimeout`]: http.md#serverkeepalivetimeout -[`http.Server#maxHeadersCount`]: http.md#servermaxheaderscount -[`http.Server#requestTimeout`]: http.md#serverrequesttimeout -[`http.Server#setTimeout()`]: http.md#serversettimeoutmsecs-callback -[`http.Server#timeout`]: http.md#servertimeout +[`http.Server.activeConnections()`]: http.md#serveractiveconnections +[`http.Server.close()`]: http.md#serverclosecallback +[`http.Server.connections()`]: http.md#serverconnections +[`http.Server.expiredconnections()`]: http.md#serverexpiredconnections +[`http.Server.headersTimeout`]: http.md#serverheaderstimeout +[`http.Server.idleconnections()`]: http.md#serveridleconnections +[`http.Server.keepAliveTimeout`]: http.md#serverkeepalivetimeout +[`http.Server.maxHeadersCount`]: http.md#servermaxheaderscount +[`http.Server.requestTimeout`]: http.md#serverrequesttimeout +[`http.Server.setTimeout()`]: http.md#serversettimeoutmsecs-callback +[`http.Server.timeout`]: http.md#servertimeout [`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 diff --git a/lib/_http_server.js b/lib/_http_server.js index 84be32c78c4075c..c8126db0295151c 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -22,6 +22,7 @@ 'use strict'; const { + Array, ArrayIsArray, Error, ObjectKeys, @@ -465,6 +466,22 @@ Server.prototype.setTimeout = function setTimeout(msecs, callback) { return this; }; +Server.prototype.connections = function connections() { + return connectionsToResource(this[kConnections].all()); +}; + +Server.prototype.activeConnections = function activeConnections() { + return connectionsToResource(this[kConnections].active()); +}; + +Server.prototype.idleConnections = function idleConnections() { + return connectionsToResource(this[kConnections].idle()); +}; + +Server.prototype.expiredConnections = function expiredConnections() { + return connectionsToResource(this[kConnections].expired(this.headersTimeout, this.requestTimeout)); +}; + Server.prototype[EE.captureRejectionSymbol] = function(err, event, ...args) { switch (event) { case 'request': { @@ -488,6 +505,16 @@ Server.prototype[EE.captureRejectionSymbol] = function(err, event, ...args) { } }; +function connectionsToResource(resources) { + const connections = Array(resources.length); + + for (let i = 0, l = resources.length; i < l; i++) { + connections[i] = resources[i].socket; + } + + return connections; +} + function checkConnections() { const expired = this[kConnections].expired(this.headersTimeout, this.requestTimeout); diff --git a/lib/https.js b/lib/https.js index 74ce93baaafe35a..fdc861712167d9d 100644 --- a/lib/https.js +++ b/lib/https.js @@ -87,6 +87,14 @@ function Server(opts, requestListener) { ObjectSetPrototypeOf(Server.prototype, tls.Server.prototype); ObjectSetPrototypeOf(Server, tls.Server); +Server.prototype.activeConnections = HttpServer.prototype.activeConnections; + +Server.prototype.connections = HttpServer.prototype.connections; + +Server.prototype.expiredConnections = HttpServer.prototype.expiredConnections; + +Server.prototype.idleConnections = HttpServer.prototype.idleConnections; + Server.prototype.setTimeout = HttpServer.prototype.setTimeout; /** diff --git a/test/parallel/test-http-server-close-all.js b/test/parallel/test-http-server-close-all.js new file mode 100644 index 000000000000000..e842d011cebf948 --- /dev/null +++ b/test/parallel/test-http-server-close-all.js @@ -0,0 +1,50 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { createServer } = require('http'); +const { connect } = require('net'); + +const server = createServer(common.mustCall(function(req, res) { + res.writeHead(200, { 'Connection': 'keep-alive' }); + res.end(); +}), { requestTimeout: common.platformTimeout(60000), headersTimeout: 0 }); + +server.listen(0, function() { + const port = server.address().port; + + // Create a new request, let it finish but leave the connection opened using HTTP keep-alive + const client1 = connect(port); + let response1 = ''; + + client1.on('data', common.mustCall((chunk) => { + response1 += chunk.toString('utf-8'); + })); + + client1.on('end', common.mustCall(function() { + assert(response1.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive')); + })); + client1.on('close', common.mustCall()); + + client1.resume(); + client1.write('GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n'); + + // Create a second request but never finish it + const client2 = connect(port); + client2.on('close', common.mustCall()); + + client2.resume(); + client2.write('GET / HTTP/1.1\r\n'); + + // Now, after a while, close the server + setTimeout(common.mustCall(() => { + for (const connection of server.connections()) { + connection.destroy(); + } + + server.close(common.mustCall()); + }), common.platformTimeout(1000)); + + // This timer should never go off as the server.close should shut everything down + setTimeout(common.mustNotCall(), common.platformTimeout(1500)).unref(); +}); diff --git a/test/parallel/test-http-server-close-idle.js b/test/parallel/test-http-server-close-idle.js new file mode 100644 index 000000000000000..d0f8f28be371a65 --- /dev/null +++ b/test/parallel/test-http-server-close-idle.js @@ -0,0 +1,65 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { createServer } = require('http'); +const { connect } = require('net'); + +const server = createServer(common.mustCall(function(req, res) { + res.writeHead(200, { 'Connection': 'keep-alive' }); + res.end(); +}), { requestTimeout: common.platformTimeout(60000), headersTimeout: 0 }); + +server.listen(0, function() { + const port = server.address().port; + + // Create a new request, let it finish but leave the connection opened using HTTP keep-alive + const client1 = connect(port); + let response1 = ''; + let client1Closed = false; + + client1.on('data', common.mustCall((chunk) => { + response1 += chunk.toString('utf-8'); + })); + + client1.on('end', common.mustCall(function() { + assert(response1.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive')); + })); + client1.on('close', common.mustCall(() => { + client1Closed = true; + })); + + client1.resume(); + client1.write('GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n'); + + // Create a second request but never finish it + const client2 = connect(port); + let client2Closed = false; + client2.on('close', common.mustCall(() => { + client2Closed = true; + })); + + client2.resume(); + client2.write('GET / HTTP/1.1\r\n'); + + // Now, after a while, close the server + setTimeout(common.mustCall(() => { + for (const connection of server.idleConnections()) { + connection.destroy(); + } + + server.close(common.mustCall()); + }), common.platformTimeout(1000)); + + // Check that only the idle connection got closed + setTimeout(common.mustCall(() => { + assert(client1Closed); + assert(!client2Closed); + + for (const connection of server.connections()) { + connection.destroy(); + } + + server.close(); + }), common.platformTimeout(1500)).unref(); +}); diff --git a/test/parallel/test-http-server-headers-timeout-delayed-headers.js b/test/parallel/test-http-server-headers-timeout-delayed-headers.js index 5c18492384911ee..dc0efc5855ad021 100644 --- a/test/parallel/test-http-server-headers-timeout-delayed-headers.js +++ b/test/parallel/test-http-server-headers-timeout-delayed-headers.js @@ -10,12 +10,12 @@ const { connect } = require('net'); // pauses before start sending the request. let sendDelayedRequestHeaders; -const headersTimeout = common.platformTimeout(1000); +const headersTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout, requestTimeout: 0, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: headersTimeout / 4, }, common.mustNotCall()); server.on('connection', common.mustCall(() => { assert.strictEqual(typeof sendDelayedRequestHeaders, 'function'); diff --git a/test/parallel/test-http-server-headers-timeout-interrupted-headers.js b/test/parallel/test-http-server-headers-timeout-interrupted-headers.js index ea47ae8e19e12e8..ce38123c15b684a 100644 --- a/test/parallel/test-http-server-headers-timeout-interrupted-headers.js +++ b/test/parallel/test-http-server-headers-timeout-interrupted-headers.js @@ -10,12 +10,12 @@ const { connect } = require('net'); // pauses sending in the middle of a header. let sendDelayedRequestHeaders; -const headersTimeout = common.platformTimeout(1000); +const headersTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout, requestTimeout: 0, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: headersTimeout / 4, }, common.mustNotCall()); server.on('connection', common.mustCall(() => { assert.strictEqual(typeof sendDelayedRequestHeaders, 'function'); diff --git a/test/parallel/test-http-server-headers-timeout-pipelining.js b/test/parallel/test-http-server-headers-timeout-pipelining.js index 13f89c60c05cf53..658bdf9a3aaf1ca 100644 --- a/test/parallel/test-http-server-headers-timeout-pipelining.js +++ b/test/parallel/test-http-server-headers-timeout-pipelining.js @@ -9,12 +9,12 @@ const { connect } = require('net'); // after server.requestTimeout if the client // does not complete a request when using pipelining. -const headersTimeout = common.platformTimeout(1000); +const headersTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout, requestTimeout: 0, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: headersTimeout / 4, }, common.mustCallAtLeast((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(); diff --git a/test/parallel/test-http-server-request-timeout-delayed-body.js b/test/parallel/test-http-server-request-timeout-delayed-body.js index c5ecb720e47eaf6..91cb5a851bcfe38 100644 --- a/test/parallel/test-http-server-request-timeout-delayed-body.js +++ b/test/parallel/test-http-server-request-timeout-delayed-body.js @@ -10,12 +10,12 @@ const { connect } = require('net'); // pauses before start sending the body. let sendDelayedRequestBody; -const requestTimeout = common.platformTimeout(1000); +const requestTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout: 0, requestTimeout, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: requestTimeout / 4, }, common.mustCall((req, res) => { let body = ''; req.setEncoding('utf-8'); diff --git a/test/parallel/test-http-server-request-timeout-delayed-headers.js b/test/parallel/test-http-server-request-timeout-delayed-headers.js index 7174afec47fcc04..ae4260ddeefa18a 100644 --- a/test/parallel/test-http-server-request-timeout-delayed-headers.js +++ b/test/parallel/test-http-server-request-timeout-delayed-headers.js @@ -10,12 +10,12 @@ const { connect } = require('net'); // pauses before start sending the request. let sendDelayedRequestHeaders; -const requestTimeout = common.platformTimeout(1000); +const requestTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout: 0, requestTimeout, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: requestTimeout / 4, }, common.mustNotCall()); server.on('connection', common.mustCall(() => { assert.strictEqual(typeof sendDelayedRequestHeaders, 'function'); diff --git a/test/parallel/test-http-server-request-timeout-interrupted-body.js b/test/parallel/test-http-server-request-timeout-interrupted-body.js index ee087719e4ee052..3b147693d97550e 100644 --- a/test/parallel/test-http-server-request-timeout-interrupted-body.js +++ b/test/parallel/test-http-server-request-timeout-interrupted-body.js @@ -10,12 +10,12 @@ const { connect } = require('net'); // pauses sending in the middle of the body. let sendDelayedRequestBody; -const requestTimeout = common.platformTimeout(1000); +const requestTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout: 0, requestTimeout, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: requestTimeout / 4, }, common.mustCall((req, res) => { let body = ''; req.setEncoding('utf-8'); diff --git a/test/parallel/test-http-server-request-timeout-interrupted-headers.js b/test/parallel/test-http-server-request-timeout-interrupted-headers.js index 64511c6b50ce3a3..45c773996be2090 100644 --- a/test/parallel/test-http-server-request-timeout-interrupted-headers.js +++ b/test/parallel/test-http-server-request-timeout-interrupted-headers.js @@ -10,12 +10,12 @@ const { connect } = require('net'); // pauses sending in the middle of a header. let sendDelayedRequestHeaders; -const requestTimeout = common.platformTimeout(1000); +const requestTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout: 0, requestTimeout, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: requestTimeout / 4, }, common.mustNotCall()); server.on('connection', common.mustCall(() => { assert.strictEqual(typeof sendDelayedRequestHeaders, 'function'); diff --git a/test/parallel/test-http-server-request-timeout-keepalive.js b/test/parallel/test-http-server-request-timeout-keepalive.js index 2466e1ee7a953c3..a83ae61021d8139 100644 --- a/test/parallel/test-http-server-request-timeout-keepalive.js +++ b/test/parallel/test-http-server-request-timeout-keepalive.js @@ -67,7 +67,7 @@ server.listen(0, common.mustCall(() => { performRequestWithDelay( client, requestTimeout / 5, - requestTimeout, + requestTimeout * 2, true ); }, defer).unref(); @@ -88,7 +88,7 @@ server.listen(0, common.mustCall(() => { client.on('error', errOrEnd); client.on('end', errOrEnd); - // Perform a second request expected to finish before requestTimeout + // Perform a first request which is completed immediately performRequestWithDelay( client, requestTimeout / 5, diff --git a/test/parallel/test-http-server-request-timeout-pipelining.js b/test/parallel/test-http-server-request-timeout-pipelining.js index 4e6977b3270dab6..2647268b11fc87e 100644 --- a/test/parallel/test-http-server-request-timeout-pipelining.js +++ b/test/parallel/test-http-server-request-timeout-pipelining.js @@ -9,12 +9,12 @@ const { connect } = require('net'); // after server.requestTimeout if the client // does not complete a request when using pipelining. -const requestTimeout = common.platformTimeout(1000); +const requestTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout: 0, requestTimeout, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: requestTimeout / 4 }, common.mustCallAtLeast((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(); @@ -66,5 +66,5 @@ server.listen(0, common.mustCall(() => { // Complete the request setTimeout(() => { client.write('close\r\n\r\n'); - }, requestTimeout * 1.5).unref(); + }, requestTimeout * 2).unref(); })); diff --git a/test/parallel/test-http-server-request-timeout-upgrade.js b/test/parallel/test-http-server-request-timeout-upgrade.js index b1974a128b9df3a..0d95a8b22c2b046 100644 --- a/test/parallel/test-http-server-request-timeout-upgrade.js +++ b/test/parallel/test-http-server-request-timeout-upgrade.js @@ -8,12 +8,12 @@ const { connect } = require('net'); // This test validates that the requestTimeoout // is disabled after the connection is upgraded. let sendDelayedRequestHeaders; -const requestTimeout = common.platformTimeout(1000); +const requestTimeout = common.platformTimeout(2000); const server = createServer({ headersTimeout: 0, requestTimeout, keepAliveTimeout: 0, - connectionsCheckingInterval: common.platformTimeout(250), + connectionsCheckingInterval: requestTimeout / 4 }, common.mustNotCall()); server.on('connection', common.mustCall(() => { assert.strictEqual(typeof sendDelayedRequestHeaders, 'function'); @@ -58,6 +58,6 @@ server.listen(0, common.mustCall(() => { setTimeout(() => { client.write('12345678901234567890'); client.end(); - }, common.platformTimeout(2000)).unref(); + }, requestTimeout * 2).unref(); }); })); diff --git a/test/parallel/test-http-server-request-timeouts-mixed.js b/test/parallel/test-http-server-request-timeouts-mixed.js new file mode 100644 index 000000000000000..18a8565b4aee324 --- /dev/null +++ b/test/parallel/test-http-server-request-timeouts-mixed.js @@ -0,0 +1,133 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { createServer } = require('http'); +const { connect } = require('net'); + +// This test validates that request are correct checked for both requests and headers timeout in various situations. + +const requestBodyPart1 = 'POST / HTTP/1.1\r\nContent-Length: 20\r\n'; +const requestBodyPart2 = 'Connection: close\r\n\r\n1234567890'; +const requestBodyPart3 = '1234567890'; + +const responseOk = 'HTTP/1.1 200 OK\r\n'; +const responseTimeout = 'HTTP/1.1 408 Request Timeout\r\n'; + +const headersTimeout = common.platformTimeout(2000); +const connectionsCheckingInterval = headersTimeout / 4; + +const server = createServer({ + headersTimeout, + requestTimeout: headersTimeout * 2, + keepAliveTimeout: 0, + connectionsCheckingInterval +}, common.mustCall((req, res) => { + req.on('data', () => { + // No-op + }); + + req.on('end', () => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(); + }); +}, 4)); + +assert.strictEqual(server.headersTimeout, headersTimeout); +assert.strictEqual(server.requestTimeout, headersTimeout * 2); + +let i = 0; +function createClient(server) { + const request = { + index: i++, + client: connect(server.address().port), + response: '', + completed: false + }; + + request.client.on('data', common.mustCallAtLeast((chunk) => { + request.response += chunk.toString('utf-8'); + })); + + request.client.on('end', common.mustCall(() => { + request.completed = true; + })); + + request.client.on('error', common.mustNotCall()); + + request.client.resume(); + + return request; +} + +server.listen(0, common.mustCall(() => { + const request1 = createClient(server); + let request2; + let request3; + let request4; + let request5; + + // Send the first request and stop before the body + request1.client.write(requestBodyPart1); + + // After a little while send two new requests + setTimeout(() => { + request2 = createClient(server); + request3 = createClient(server); + + // Send the second request, stop in the middle of the headers + request2.client.write(requestBodyPart1); + // Send the second request, stop in the middle of the headers + request3.client.write(requestBodyPart1); + }, headersTimeout * 0.2); + + // After another little while send the last two new requests + setTimeout(() => { + request4 = createClient(server); + request5 = createClient(server); + + // Send the fourth request, stop in the middle of the headers + request4.client.write(requestBodyPart1); + // Send the fifth request, stop in the middle of the headers + request5.client.write(requestBodyPart1); + }, headersTimeout * 0.6); + + setTimeout(() => { + // Finish the first request + request1.client.write(requestBodyPart2 + requestBodyPart3); + + // Complete headers for all requests but second + request3.client.write(requestBodyPart2); + request4.client.write(requestBodyPart2); + request5.client.write(requestBodyPart2); + }, headersTimeout * 0.8); + + setTimeout(() => { + // After the first timeout, the first request should have been completed and second timedout + assert(request1.completed); + assert(request2.completed); + assert(!request3.completed); + assert(!request4.completed); + assert(!request5.completed); + + assert(request1.response.startsWith(responseOk)); + assert(request2.response.startsWith(responseTimeout)); // It is expired due to headersTimeout + }, headersTimeout * 1.2 + connectionsCheckingInterval); + + setTimeout(() => { + // Complete the body for the fourth request + request4.client.write(requestBodyPart3); + }, headersTimeout * 1.5); + + setTimeout(() => { + // All request should be completed now, either with 200 or 408 + assert(request3.completed); + assert(request4.completed); + assert(request5.completed); + + assert(request3.response.startsWith(responseTimeout)); // It is expired due to requestTimeout + assert(request4.response.startsWith(responseOk)); + assert(request5.response.startsWith(responseTimeout)); // It is expired due to requestTimeout + server.close(); + }, headersTimeout * 3 + connectionsCheckingInterval); +})); diff --git a/test/parallel/test-https-server-close-all.js b/test/parallel/test-https-server-close-all.js new file mode 100644 index 000000000000000..c4d073157e01e46 --- /dev/null +++ b/test/parallel/test-https-server-close-all.js @@ -0,0 +1,57 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +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') +}; + +const server = createServer(options, common.mustCall(function(req, res) { + res.writeHead(200, { 'Connection': 'keep-alive' }); + res.end(); +}), { requestTimeout: common.platformTimeout(60000), headersTimeout: 0 }); + +server.listen(0, function() { + const port = server.address().port; + + // Create a new request, let it finish but leave the connection opened using HTTP keep-alive + const client1 = connect({port, rejectUnauthorized: false}); + let response1 = ''; + + client1.on('data', common.mustCall((chunk) => { + response1 += chunk.toString('utf-8'); + })); + + client1.on('end', common.mustCall(function() { + assert(response1.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive')); + })); + client1.on('close', common.mustCall()); + + client1.resume(); + client1.write('GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n'); + + // Create a second request but never finish it + const client2 = connect({port, rejectUnauthorized: false}); + client2.on('close', common.mustCall()); + + client2.resume(); + client2.write('GET / HTTP/1.1\r\n'); + + // Now, after a while, close the server + setTimeout(common.mustCall(() => { + for (const connection of server.connections()) { + connection.destroy(); + } + + server.close(common.mustCall()); + }), common.platformTimeout(1000)); + + // This timer should never go off as the server.close should shut everything down + setTimeout(common.mustNotCall(), common.platformTimeout(1500)).unref(); +}); diff --git a/test/parallel/test-https-server-close-idle.js b/test/parallel/test-https-server-close-idle.js new file mode 100644 index 000000000000000..094c8d2e5a4692d --- /dev/null +++ b/test/parallel/test-https-server-close-idle.js @@ -0,0 +1,72 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +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') +}; + +const server = createServer(options, common.mustCall(function(req, res) { + res.writeHead(200, { 'Connection': 'keep-alive' }); + res.end(); +}), { requestTimeout: common.platformTimeout(60000), headersTimeout: 0 }); + +server.listen(0, function() { + const port = server.address().port; + + // Create a new request, let it finish but leave the connection opened using HTTP keep-alive + const client1 = connect({port, rejectUnauthorized: false}); + let response1 = ''; + let client1Closed = false; + + client1.on('data', common.mustCall((chunk) => { + response1 += chunk.toString('utf-8'); + })); + + client1.on('end', common.mustCall(function() { + assert(response1.startsWith('HTTP/1.1 200 OK\r\nConnection: keep-alive')); + })); + client1.on('close', common.mustCall(() => { + client1Closed = true; + })); + + client1.resume(); + client1.write('GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n'); + + // Create a second request but never finish it + const client2 = connect({port, rejectUnauthorized: false}); + let client2Closed = false; + client2.on('close', common.mustCall(() => { + client2Closed = true; + })); + + client2.resume(); + client2.write('GET / HTTP/1.1\r\n'); + + // Now, after a while, close the server + setTimeout(common.mustCall(() => { + for (const connection of server.idleConnections()) { + connection.destroy(); + } + + server.close(common.mustCall()); + }), common.platformTimeout(1000)); + + // Check that only the idle connection got closed + setTimeout(common.mustCall(() => { + assert(client1Closed); + assert(!client2Closed); + + for (const connection of server.connections()) { + connection.destroy(); + } + + server.close(); + }), common.platformTimeout(1500)).unref(); +});