diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index ce1fe0848a7d88..f2802486c15687 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1224,6 +1224,14 @@ Emitted when a stream is started on the client. Emitted when an error occurs during the processing of a stream on the client. +`http2.client.stream.finish` + +* `stream` {ClientHttp2Stream} +* `headers` {HTTP/2 Headers Object} +* `flags` {number} + +Emitted when a stream is received on the client. + #### Modules > Stability: 1 - Experimental diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 47c0cb4a9a85cc..d8e5121f635acd 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -188,6 +188,7 @@ const dc = require('diagnostics_channel'); const onClientStreamCreatedChannel = dc.channel('http2.client.stream.created'); const onClientStreamStartChannel = dc.channel('http2.client.stream.start'); const onClientStreamErrorChannel = dc.channel('http2.client.stream.error'); +const onClientStreamFinishChannel = dc.channel('http2.client.stream.finish'); let debug = require('internal/util/debuglog').debuglog('http2', (fn) => { debug = fn; @@ -427,6 +428,15 @@ function onSessionHeaders(handle, id, cat, flags, headers, sensitiveHeaders) { reqAsync.runInAsyncScope(process.nextTick, null, emit, stream, event, obj, flags, headers); else process.nextTick(emit, stream, event, obj, flags, headers); + if ((event === 'response' || + event === 'push') && + onClientStreamFinishChannel.hasSubscribers) { + onClientStreamFinishChannel.publish({ + stream, + headers: obj, + flags: flags, + }); + } } if (endOfStream) { stream.push(null); diff --git a/test/parallel/test-diagnostics-channel-http2-client-stream-finish.js b/test/parallel/test-diagnostics-channel-http2-client-stream-finish.js new file mode 100644 index 00000000000000..c8b26471e28209 --- /dev/null +++ b/test/parallel/test-diagnostics-channel-http2-client-stream-finish.js @@ -0,0 +1,63 @@ +'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.client.stream.finish' channel when +// ClientHttp2Streams are received by both: +// - the 'response' event +// - the 'push' event + +const Countdown = require('../common/countdown'); +const assert = require('assert'); +const dc = require('diagnostics_channel'); +const http2 = require('http2'); +const { Duplex } = require('stream'); + +const clientHttp2StreamFinishCount = 2; + +dc.subscribe('http2.client.stream.finish', common.mustCall(({ stream, headers, flags }) => { + // Since ClientHttp2Stream is not exported from any module, this just checks + // if the stream is an instance of Duplex and the constructor name is + // 'ClientHttp2Stream'. + assert.ok(stream instanceof Duplex); + assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream'); + + assert.ok(headers && !Array.isArray(headers) && typeof headers === 'object'); + + assert.strictEqual(typeof flags, 'number'); +}, clientHttp2StreamFinishCount)); + +const server = http2.createServer(); +server.on('stream', common.mustCall((stream) => { + stream.respond(); + stream.end(); + + stream.pushStream({}, common.mustSucceed((pushStream) => { + pushStream.respond(); + pushStream.end(); + })); +})); + +server.listen(0, common.mustCall(() => { + const port = server.address().port; + const client = http2.connect(`http://localhost:${port}`); + + const countdown = new Countdown(clientHttp2StreamFinishCount, () => { + client.close(); + server.close(); + }); + + const stream = client.request({}); + stream.on('response', common.mustCall(() => { + countdown.dec(); + })); + + client.on('stream', common.mustCall((pushStream) => { + pushStream.on('push', common.mustCall(() => { + countdown.dec(); + })); + })); +}));