Skip to content

HTTP/2 server.close() is broken with "allowHTTP1" mode #51493

Closed
@JoshuaWise

Description

@JoshuaWise

Version

v21.6.0

Platform

Darwin Kernel Version 19.6.0

Subsystem

none

What steps will reproduce the bug?

Run the script below:

const fs = require('fs');
const https = require('https');
const http2 = require('http2');

const USE_COMPATIBILITY_MODE = true;

(async function main() {
	const sockets = new Set();
	let server;

	if (USE_COMPATIBILITY_MODE) {
		server = http2.createSecureServer({
			allowHTTP1: true,
			cert: fs.readFileSync('test-cert.pem'),
			key: fs.readFileSync('test-key.pem'),
		});
	} else {
		server = https.createServer({
			cert: fs.readFileSync('test-cert.pem'),
			key: fs.readFileSync('test-key.pem'),
		});
	}

	server.on('connection', (socket) => {
		sockets.add(socket);
		socket.on('close', () => sockets.delete(socket));
	});
	server.on('request', (req, res) => {
		console.log('request received');
		const message = 'hello world.';
		res.setHeader('Content-Length', String(Buffer.byteLength(message)));
		res.setHeader('Content-Type', 'text/plain; charset=utf-8');
		res.writeHead(200);
		res.end(message);
	});

	await new Promise(resolve => server.listen(443, resolve));
	console.log('Listening at https://localhost:443');

	await openKeepAliveSocket();
	console.log(`closing ${sockets.size} socket(s)...`);
	server.close(() => console.log('done'));
})();

function openKeepAliveSocket() {
	return new Promise((resolve, reject) => {
		const req = https.get('https://localhost:443', {
			rejectUnauthorized: false,
			headers: { connection: 'keep-alive' },
		});
		req.on('error', reject);
		req.on('response', (res) => {
			console.log('response status ' + res.statusCode);
			res.on('error', reject);
			res.on('data', () => {});
			res.on('end', () => {
				console.log('response ended');
				resolve();
			});
		});
	});
}

How often does it reproduce? Is there a required condition?

I can reliably reproduce it 100% of the time.

What is the expected behavior? Why is that the expected behavior?

The expected behavior is for the HTTPS server to close down immediately (as soon as the request ends). This is what happens when using https.createServer(), as demonstrated by changing USE_COMPATIBILITY_MODE to false in the script above.

What do you see instead?

Instead, the HTTPS server stays open for 5 seconds (which is the default keep-alive duration).

In other words, calling server.close() properly shuts down idle "keep-alive" sockets when using the http or https modules, but not when using the http2 module with allowHTTP1: true.

Additional information

You'll need a TLS certificate to run the script above. I created one by following this guide in the Node.js documentation to create a self-signed certificate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions