diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index 6671f5ddab11bb..b9904df6d9e058 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1253,6 +1253,13 @@ Emitted when a stream is created on the server. Emitted when a stream is started on the server. +`http2.server.stream.error` + +* `stream` {ServerHttp2Stream} +* `error` {Error} + +Emitted when an error occurs during the processing of a stream on the server. + #### Modules > Stability: 1 - Experimental diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 2cda2dabaabe78..8c63284467707b 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -192,6 +192,7 @@ const onClientStreamFinishChannel = dc.channel('http2.client.stream.finish'); const onClientStreamCloseChannel = dc.channel('http2.client.stream.close'); const onServerStreamCreatedChannel = dc.channel('http2.server.stream.created'); const onServerStreamStartChannel = dc.channel('http2.server.stream.start'); +const onServerStreamErrorChannel = dc.channel('http2.server.stream.error'); let debug = require('internal/util/debuglog').debuglog('http2', (fn) => { debug = fn; @@ -2453,13 +2454,20 @@ class Http2Stream extends Duplex { setImmediate(() => { session[kMaybeDestroy](); }); - if (err && - session[kType] === NGHTTP2_SESSION_CLIENT && - onClientStreamErrorChannel.hasSubscribers) { - onClientStreamErrorChannel.publish({ - stream: this, - error: err, - }); + if (err) { + if (session[kType] === NGHTTP2_SESSION_CLIENT) { + if (onClientStreamErrorChannel.hasSubscribers) { + onClientStreamErrorChannel.publish({ + stream: this, + error: err, + }); + } + } else if (onServerStreamErrorChannel.hasSubscribers) { + onServerStreamErrorChannel.publish({ + stream: this, + error: err, + }); + } } callback(err); } diff --git a/test/parallel/test-diagnostics-channel-http2-server-stream-error.js b/test/parallel/test-diagnostics-channel-http2-server-stream-error.js new file mode 100644 index 00000000000000..ea4f49e200c88b --- /dev/null +++ b/test/parallel/test-diagnostics-channel-http2-server-stream-error.js @@ -0,0 +1,53 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +// This test ensures that the built-in HTTP/2 diagnostics channels are reporting +// the diagnostics messages for the 'http2.server.stream.error' channel when +// an error occurs during the processing of a ServerHttp2Stream. + +const assert = require('assert'); +const dc = require('diagnostics_channel'); +const http2 = require('http2'); +const { Duplex } = require('stream'); + +let expectedError = null; + +dc.subscribe('http2.server.stream.error', common.mustCall(({ stream, error }) => { + // Since ServerHttp2Stream is not exported from any module, this just checks + // if the stream is an instance of Duplex and the constructor name is + // 'ServerHttp2Stream'. + assert.ok(stream instanceof Duplex); + assert.strictEqual(stream.constructor.name, 'ServerHttp2Stream'); + assert.strictEqual(stream.closed, true); + assert.strictEqual(stream.destroyed, true); + + assert.ok(error); + assert.strictEqual(error, expectedError); +})); + +const server = http2.createServer(); + +server.on('stream', common.mustCall((stream) => { + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err, expectedError); + })); + + expectedError = new Error('HTTP/2 server stream error'); + stream.destroy(expectedError); +})); + +server.listen(0, common.mustCall(() => { + const port = server.address().port; + const client = http2.connect(`http://localhost:${port}`); + + const stream = client.request({}); + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_HTTP2_STREAM_ERROR'); + + client.close(); + server.close(); + })); +}));