From 7c7230a85c9e9911b2f5c53e73641d9a6f83643c Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Wed, 5 Jul 2023 20:59:39 -0400 Subject: [PATCH] http2: send RST code 8 on AbortController signal Fixes: https://github.com/nodejs/node/issues/47321 Refs: https://www.rfc-editor.org/rfc/rfc7540#section-7 PR-URL: https://github.com/nodejs/node/pull/48573 Reviewed-By: Rafael Gonzaga Reviewed-By: Yagiz Nizipli Reviewed-By: Minwoo Jung --- lib/internal/http2/core.js | 15 ++++++++--- test/parallel/test-http2-client-destroy.js | 29 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 0657e23c6d2792..b5b5c17897b1c3 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -2318,10 +2318,17 @@ class Http2Stream extends Duplex { // this stream's close and destroy operations. // Previously, this always overrode a successful close operation code // NGHTTP2_NO_ERROR (0) with sessionCode because the use of the || operator. - const code = (err != null ? - (sessionCode || NGHTTP2_INTERNAL_ERROR) : - (this.closed ? this.rstCode : sessionCode) - ); + let code = this.closed ? this.rstCode : sessionCode; + if (err != null) { + if (sessionCode) { + code = sessionCode; + } else if (err instanceof AbortError) { + // Enables using AbortController to cancel requests with RST code 8. + code = NGHTTP2_CANCEL; + } else { + code = NGHTTP2_INTERNAL_ERROR; + } + } const hasHandle = handle !== undefined; if (!this.closed) diff --git a/test/parallel/test-http2-client-destroy.js b/test/parallel/test-http2-client-destroy.js index 3dfd46abdd55a4..6973cfa1df1e61 100644 --- a/test/parallel/test-http2-client-destroy.js +++ b/test/parallel/test-http2-client-destroy.js @@ -285,3 +285,32 @@ const { getEventListeners } = require('events'); testH2ConnectAbort(false); testH2ConnectAbort(true); } + +// Destroy ClientHttp2Stream with AbortSignal +{ + const server = h2.createServer(); + const controller = new AbortController(); + + server.on('stream', common.mustCall((stream) => { + stream.on('error', common.mustNotCall()); + stream.on('close', common.mustCall(() => { + assert.strictEqual(stream.rstCode, h2.constants.NGHTTP2_CANCEL); + server.close(); + })); + controller.abort(); + })); + server.listen(0, common.mustCall(() => { + const client = h2.connect(`http://localhost:${server.address().port}`); + client.on('close', common.mustCall()); + + const { signal } = controller; + const req = client.request({}, { signal }); + assert.strictEqual(getEventListeners(signal, 'abort').length, 1); + req.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ABORT_ERR'); + assert.strictEqual(err.name, 'AbortError'); + client.close(); + })); + req.on('close', common.mustCall()); + })); +}