From d5a2e25f990fb836a45dcf2243505c7b4a39f2b4 Mon Sep 17 00:00:00 2001 From: Darshan Sen Date: Mon, 19 May 2025 17:10:54 +0530 Subject: [PATCH] http2: add diagnostics channel 'http2.server.stream.created' Signed-off-by: Darshan Sen --- doc/api/diagnostics_channel.md | 7 +++ lib/internal/http2/core.js | 14 +++++ ...ics-channel-http2-server-stream-created.js | 60 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 test/parallel/test-diagnostics-channel-http2-server-stream-created.js diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index 8f461eabe5dd5f..476911bd6a24b7 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1239,6 +1239,13 @@ Emitted when a stream is received on the client. Emitted when a stream is closed on the client. The HTTP/2 error code used when closing the stream can be retrieved using the `stream.rstCode` property. +`http2.server.stream.created` + +* `stream` {ServerHttp2Stream} +* `headers` {HTTP/2 Headers Object} + +Emitted when a stream is created on the server. + #### Modules > Stability: 1 - Experimental diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index a06663166d0750..17ffc3f33e0365 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -190,6 +190,7 @@ const onClientStreamStartChannel = dc.channel('http2.client.stream.start'); const onClientStreamErrorChannel = dc.channel('http2.client.stream.error'); const onClientStreamFinishChannel = dc.channel('http2.client.stream.finish'); const onClientStreamCloseChannel = dc.channel('http2.client.stream.close'); +const onServerStreamCreatedChannel = dc.channel('http2.server.stream.created'); let debug = require('internal/util/debuglog').debuglog('http2', (fn) => { debug = fn; @@ -367,6 +368,12 @@ function onSessionHeaders(handle, id, cat, flags, headers, sensitiveHeaders) { if (type === NGHTTP2_SESSION_SERVER) { // eslint-disable-next-line no-use-before-define stream = new ServerHttp2Stream(session, handle, id, {}, obj); + if (onServerStreamCreatedChannel.hasSubscribers) { + onServerStreamCreatedChannel.publish({ + stream, + headers: obj, + }); + } if (endOfStream) { stream.push(null); } @@ -2835,6 +2842,13 @@ class ServerHttp2Stream extends Http2Stream { stream[kState].flags |= STREAM_FLAGS_HEAD_REQUEST; process.nextTick(callback, null, stream, headers, 0); + + if (onServerStreamCreatedChannel.hasSubscribers) { + onServerStreamCreatedChannel.publish({ + stream, + headers, + }); + } } // Initiate a response on this Http2Stream diff --git a/test/parallel/test-diagnostics-channel-http2-server-stream-created.js b/test/parallel/test-diagnostics-channel-http2-server-stream-created.js new file mode 100644 index 00000000000000..b3b5fcd90aebe6 --- /dev/null +++ b/test/parallel/test-diagnostics-channel-http2-server-stream-created.js @@ -0,0 +1,60 @@ +'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.created' channel when +// ServerHttp2Streams are created by both: +// - in response to an incoming 'stream' event from the client +// - the server calling ServerHttp2Stream#pushStream() + +const Countdown = require('../common/countdown'); +const assert = require('assert'); +const dc = require('diagnostics_channel'); +const http2 = require('http2'); +const { Duplex } = require('stream'); + +const serverHttp2StreamCreationCount = 2; + +dc.subscribe('http2.server.stream.created', common.mustCall(({ stream, headers }) => { + // 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.ok(headers && !Array.isArray(headers) && typeof headers === 'object'); +}, serverHttp2StreamCreationCount)); + +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(serverHttp2StreamCreationCount, () => { + 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(); + })); + })); +}));