diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 63f9da5564d6d8..fefb04d03d994d 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -942,6 +942,7 @@ class Http2Session extends EventEmitter { socket[kSession] = this; this[kState] = { + destroyCode: NGHTTP2_NO_ERROR, flags: SESSION_FLAGS_PENDING, goawayCode: null, goawayLastStreamID: null, @@ -1206,6 +1207,7 @@ class Http2Session extends EventEmitter { const state = this[kState]; state.flags |= SESSION_FLAGS_DESTROYED; + state.destroyCode = code; // Clear timeout and remove timeout listeners this.setTimeout(0); @@ -1937,10 +1939,13 @@ class Http2Stream extends Duplex { debug(`Http2Stream ${this[kID] || ''} [Http2Session ` + `${sessionName(session[kType])}]: destroying stream`); + const state = this[kState]; + const sessionCode = session[kState].goawayCode || + session[kState].destroyCode; const code = err != null ? - NGHTTP2_INTERNAL_ERROR : (state.rstCode || NGHTTP2_NO_ERROR); - + sessionCode || NGHTTP2_INTERNAL_ERROR : + state.rstCode || sessionCode; const hasHandle = handle !== undefined; if (!this.closed) diff --git a/test/parallel/test-http2-propagate-session-destroy-code.js b/test/parallel/test-http2-propagate-session-destroy-code.js new file mode 100644 index 00000000000000..a23bcfd07c32f9 --- /dev/null +++ b/test/parallel/test-http2-propagate-session-destroy-code.js @@ -0,0 +1,54 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const http2 = require('http2'); +const server = http2.createServer(); +const errRegEx = /Session closed with error code 7/; +const destroyCode = http2.constants.NGHTTP2_REFUSED_STREAM; + +server.on('error', common.mustNotCall()); + +server.on('session', (session) => { + session.on('close', common.mustCall()); + session.on('error', common.mustCall((err) => { + assert(errRegEx.test(err)); + assert.strictEqual(session.closed, false); + assert.strictEqual(session.destroyed, true); + })); + + session.on('stream', common.mustCall((stream) => { + stream.on('error', common.mustCall((err) => { + assert.strictEqual(session.closed, false); + assert.strictEqual(session.destroyed, true); + assert(errRegEx.test(err)); + assert.strictEqual(stream.rstCode, destroyCode); + })); + + session.destroy(destroyCode); + })); +}); + +server.listen(0, common.mustCall(() => { + const session = http2.connect(`http://localhost:${server.address().port}`); + + session.on('error', common.mustCall((err) => { + assert(errRegEx.test(err)); + assert.strictEqual(session.closed, false); + assert.strictEqual(session.destroyed, true); + })); + + const stream = session.request({ [http2.constants.HTTP2_HEADER_PATH]: '/' }); + + stream.on('error', common.mustCall((err) => { + assert(errRegEx.test(err)); + assert.strictEqual(stream.rstCode, destroyCode); + })); + + stream.on('close', common.mustCall(() => { + server.close(); + })); +}));