From cc9c45767cf606f9c5743f37d21bc75a72afd081 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Mon, 26 Jun 2023 15:53:58 -0400 Subject: [PATCH] http2: send RST code 8 on AbortController signal Fixes: #47321 --- 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 6d0b6a1b72d3775..3c8b51f30f3e1ef 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 3dfd46abdd55a49..6973cfa1df1e613 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()); + })); +}