Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions doc/api/diagnostics_channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,15 @@ The event is emitted before the response is sent.

Emitted when server sends a response.

#### HTTP/2

`http2.client.stream.created`

* `stream` {ClientHttp2Stream}
* `headers` {HTTP/2 Headers Object}

Emitted when a stream is created on the client.

#### Modules

`module.require.start`
Expand Down
18 changes: 18 additions & 0 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ const { UV_EOF } = internalBinding('uv');

const { StreamPipe } = internalBinding('stream_pipe');
const { _connectionListener: httpConnectionListener } = http;

const dc = require('diagnostics_channel');
const onClientStreamCreatedChannel = dc.channel('http2.client.stream.created');

let debug = require('internal/util/debuglog').debuglog('http2', (fn) => {
debug = fn;
});
Expand Down Expand Up @@ -371,6 +375,12 @@ function onSessionHeaders(handle, id, cat, flags, headers, sensitiveHeaders) {
} else {
// eslint-disable-next-line no-use-before-define
stream = new ClientHttp2Stream(session, handle, id, {});
if (onClientStreamCreatedChannel.hasSubscribers) {
onClientStreamCreatedChannel.publish({
stream,
headers: obj,
});
}
if (endOfStream) {
stream.push(null);
}
Expand Down Expand Up @@ -1863,6 +1873,14 @@ class ClientHttp2Session extends Http2Session {
} else {
onConnect();
}

if (onClientStreamCreatedChannel.hasSubscribers) {
onClientStreamCreatedChannel.publish({
stream,
headers: headersParam,
});
}

return stream;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.client.stream.created' channel when
// ClientHttp2Streams are created by both:
// - the client calling ClientHttp2Session#request()
// - in response to an incoming 'push' event from the server

const Countdown = require('../common/countdown');
const assert = require('assert');
const dc = require('diagnostics_channel');
const http2 = require('http2');
const { Duplex } = require('stream');

const clientHttp2StreamCreationCount = 2;

dc.subscribe('http2.client.stream.created', common.mustCall(({ stream, headers }) => {
// 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');
}, clientHttp2StreamCreationCount));

const server = http2.createServer();
server.on('stream', common.mustCall((stream) => {
stream.respond();
stream.end();

stream.pushStream({}, common.mustSucceed((pushStream) => {
pushStream.respond();
pushStream.end();
}, 1));
}, 1));

server.listen(0, common.mustCall(() => {
const port = server.address().port;
const client = http2.connect(`http://localhost:${port}`);

const countdown = new Countdown(clientHttp2StreamCreationCount, () => {
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();
}, 1));
}, 1));
}, 1));
Loading