node/test/parallel/test-http2-client-rststream-before-connect.js
Kushagra Pandey 2acc8bc6a9
http2: fix graceful session close
Fix issue where session.close() prematurely destroys the session
when response.end() was called with an empty payload while active
http2 streams still existed. This change ensures that sessions are
closed gracefully only after all http2 streams complete and clients
properly receive the GOAWAY frame as per the HTTP/2 spec.

Refs: https://nodejs.org/api/http2.html\#http2sessionclosecallback
PR-URL: https://github.com/nodejs/node/pull/57808
Fixes: https://github.com/nodejs/node/issues/57809
Refs: https://nodejs.org/api/http2.html%5C#http2sessionclosecallback
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2025-04-19 16:36:03 +00:00

80 lines
2.1 KiB
JavaScript

'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');
let client;
const server = h2.createServer();
server.on('stream', (stream) => {
stream.on('close', common.mustCall(() => {
client.close();
server.close();
}));
stream.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
name: 'Error',
message: 'Stream closed with error code NGHTTP2_PROTOCOL_ERROR'
}));
});
server.listen(0, common.mustCall(() => {
client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
const closeCode = 1;
assert.throws(
() => req.close(2 ** 32),
{
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE',
message: 'The value of "code" is out of range. It must be ' +
'>= 0 && <= 4294967295. Received 4294967296'
}
);
assert.strictEqual(req.closed, false);
[true, 1, {}, [], null, 'test'].forEach((notFunction) => {
assert.throws(
() => req.close(closeCode, notFunction),
{
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
}
);
assert.strictEqual(req.closed, false);
});
req.close(closeCode, common.mustCall());
assert.strictEqual(req.closed, true);
// Make sure that destroy is called.
req._destroy = common.mustCall(req._destroy.bind(req));
// Second call doesn't do anything.
req.close(closeCode + 1);
req.on('close', common.mustCall(() => {
assert.strictEqual(req.destroyed, true);
assert.strictEqual(req.rstCode, closeCode);
}));
req.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
name: 'Error',
message: 'Stream closed with error code NGHTTP2_PROTOCOL_ERROR'
}));
// The `response` event should not fire as the server should receive the
// RST_STREAM frame before it ever has a chance to reply.
req.on('response', common.mustNotCall());
// The `end` event should still fire as we close the readable stream by
// pushing a `null` chunk.
req.on('end', common.mustCall());
req.resume();
req.end();
}));