Skip to content

Commit a38e2f5

Browse files
RaisinTenaduh95
authored andcommitted
http2: add diagnostics channels for client stream request body
These would allow inspection of HTTP/2 client stream request bodies. Signed-off-by: Darshan Sen <raisinten@gmail.com> PR-URL: #60480 Reviewed-By: Ilyas Shabi <ilyasshabi94@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent 3139834 commit a38e2f5

7 files changed

Lines changed: 331 additions & 0 deletions

‎doc/api/diagnostics_channel.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,23 @@ Emitted when an error occurs during the processing of a stream on the client.
12431243

12441244
Emitted when a stream is received on the client.
12451245

1246+
##### Event: `'http2.client.stream.bodyChunkSent'`
1247+
1248+
*`stream` {ClientHttp2Stream}
1249+
*`writev` {boolean}
1250+
*`data` {Buffer | string | Buffer\[] | Object\[]}
1251+
*`chunk` {Buffer|string}
1252+
*`encoding` {string}
1253+
*`encoding` {string}
1254+
1255+
Emitted when a chunk of the client stream body is being sent.
1256+
1257+
##### Event: `'http2.client.stream.bodySent'`
1258+
1259+
*`stream` {ClientHttp2Stream}
1260+
1261+
Emitted after the client stream body has been fully sent.
1262+
12461263
##### Event: `'http2.client.stream.close'`
12471264

12481265
*`stream` {ClientHttp2Stream}

‎lib/internal/http2/core.js‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ const dc = require('diagnostics_channel');
190190
constonClientStreamCreatedChannel=dc.channel('http2.client.stream.created');
191191
constonClientStreamStartChannel=dc.channel('http2.client.stream.start');
192192
constonClientStreamErrorChannel=dc.channel('http2.client.stream.error');
193+
constonClientStreamBodyChunkSentChannel=dc.channel('http2.client.stream.bodyChunkSent');
194+
constonClientStreamBodySentChannel=dc.channel('http2.client.stream.bodySent');
193195
constonClientStreamFinishChannel=dc.channel('http2.client.stream.finish');
194196
constonClientStreamCloseChannel=dc.channel('http2.client.stream.close');
195197
constonServerStreamCreatedChannel=dc.channel('http2.server.stream.created');
@@ -2300,6 +2302,15 @@ class Http2Stream extends Duplex {
23002302
req=writeGeneric(this,data,encoding,writeCallback);
23012303

23022304
trackWriteState(this,req.bytes);
2305+
2306+
if(this.session[kType]===NGHTTP2_SESSION_CLIENT&&onClientStreamBodyChunkSentChannel.hasSubscribers){
2307+
onClientStreamBodyChunkSentChannel.publish({
2308+
stream: this,
2309+
writev,
2310+
data,
2311+
encoding,
2312+
});
2313+
}
23032314
}
23042315

23052316
_write(data,encoding,cb){
@@ -2317,6 +2328,10 @@ class Http2Stream extends Duplex {
23172328
}
23182329
debugStreamObj(this,'shutting down writable on _final');
23192330
ReflectApply(shutdownWritable,this,[cb]);
2331+
2332+
if(this.session[kType]===NGHTTP2_SESSION_CLIENT&&onClientStreamBodySentChannel.hasSubscribers){
2333+
onClientStreamBodySentChannel.publish({stream: this});
2334+
}
23202335
}
23212336

23222337
_read(nread){
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
// This test ensures that the built-in HTTP/2 diagnostics channels are reporting
8+
// the diagnostics messages for the 'http2.client.stream.bodyChunkSent' and
9+
// 'http2.client.stream.bodySent' channels when ClientHttp2Streams bodies are
10+
// being sent with multiple Buffers and strings.
11+
12+
constassert=require('assert');
13+
constdc=require('diagnostics_channel');
14+
consthttp2=require('http2');
15+
const{ Duplex }=require('stream');
16+
17+
letbodyChunkSent=false;
18+
19+
dc.subscribe('http2.client.stream.bodyChunkSent',common.mustCall(({ stream, writev, data, encoding })=>{
20+
// Since ClientHttp2Stream is not exported from any module, this just checks
21+
// if the stream is an instance of Duplex.
22+
assert.ok(streaminstanceofDuplex);
23+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
24+
25+
assert.strictEqual(writev,true);
26+
27+
assert.ok(Array.isArray(data));
28+
assert.strictEqual(data.length,3);
29+
30+
assert.strictEqual(data[0].chunk,'héllo');
31+
assert.strictEqual(data[0].encoding,'latin1');
32+
33+
assert.ok(Buffer.from('foo').equals(data[1].chunk));
34+
assert.strictEqual(data[1].encoding,'buffer');
35+
36+
assert.ok(Buffer.from('bar').equals(data[2].chunk));
37+
assert.strictEqual(data[2].encoding,'buffer');
38+
39+
assert.strictEqual(encoding,'');
40+
41+
bodyChunkSent=true;
42+
}));
43+
44+
dc.subscribe('http2.client.stream.bodySent',common.mustCall(({ stream })=>{
45+
// 'http2.client.stream.bodyChunkSent' must run first.
46+
assert.ok(bodyChunkSent);
47+
48+
// Since ClientHttp2Stream is not exported from any module, this just checks
49+
// if the stream is an instance of Duplex.
50+
assert.ok(streaminstanceofDuplex);
51+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
52+
}));
53+
54+
constserver=http2.createServer();
55+
server.on('stream',common.mustCall((stream)=>{
56+
stream.respond({},{endStream: true});
57+
}));
58+
59+
server.listen(0,common.mustCall(()=>{
60+
constport=server.address().port;
61+
constclient=http2.connect(`http://localhost:${port}`);
62+
63+
conststream=client.request({[http2.constants.HTTP2_HEADER_METHOD]: 'POST'});
64+
stream.write('héllo','latin1');
65+
stream.write(Buffer.from('foo'));
66+
stream.write(newTextEncoder().encode('bar'));
67+
stream.end();
68+
69+
stream.on('response',common.mustCall(()=>{
70+
client.close();
71+
server.close();
72+
}));
73+
},1));
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
// This test ensures that the built-in HTTP/2 diagnostics channels are reporting
8+
// the diagnostics messages for the 'http2.client.stream.bodyChunkSent' and
9+
// 'http2.client.stream.bodySent' channels when ClientHttp2Streams bodies are
10+
// being sent with multiple Buffers.
11+
12+
constassert=require('assert');
13+
constdc=require('diagnostics_channel');
14+
consthttp2=require('http2');
15+
const{ Duplex }=require('stream');
16+
17+
letbodyChunkSent=false;
18+
19+
dc.subscribe('http2.client.stream.bodyChunkSent',common.mustCall(({ stream, writev, data, encoding })=>{
20+
// Since ClientHttp2Stream is not exported from any module, this just checks
21+
// if the stream is an instance of Duplex.
22+
assert.ok(streaminstanceofDuplex);
23+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
24+
25+
assert.strictEqual(writev,true);
26+
27+
assert.ok(Array.isArray(data));
28+
assert.strictEqual(data.length,2);
29+
30+
assert.ok(Buffer.from('foo').equals(data[0]));
31+
assert.ok(Buffer.from('bar').equals(data[1]));
32+
33+
assert.strictEqual(encoding,'');
34+
35+
bodyChunkSent=true;
36+
}));
37+
38+
dc.subscribe('http2.client.stream.bodySent',common.mustCall(({ stream })=>{
39+
// 'http2.client.stream.bodyChunkSent' must run first.
40+
assert.ok(bodyChunkSent);
41+
42+
// Since ClientHttp2Stream is not exported from any module, this just checks
43+
// if the stream is an instance of Duplex.
44+
assert.ok(streaminstanceofDuplex);
45+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
46+
}));
47+
48+
constserver=http2.createServer();
49+
server.on('stream',common.mustCall((stream)=>{
50+
stream.respond({},{endStream: true});
51+
}));
52+
53+
server.listen(0,common.mustCall(()=>{
54+
constport=server.address().port;
55+
constclient=http2.connect(`http://localhost:${port}`);
56+
57+
conststream=client.request({[http2.constants.HTTP2_HEADER_METHOD]: 'POST'});
58+
stream.write(Buffer.from('foo'));
59+
stream.write(Buffer.from('bar'));
60+
stream.end();
61+
62+
stream.on('response',common.mustCall(()=>{
63+
client.close();
64+
server.close();
65+
}));
66+
},1));
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
// This test ensures that the built-in HTTP/2 diagnostics channels are reporting
8+
// the diagnostics messages for the 'http2.client.stream.bodyChunkSent' and
9+
// 'http2.client.stream.bodySent' channels when ClientHttp2Streams bodies are
10+
// being sent with no chunks.
11+
12+
constassert=require('assert');
13+
constdc=require('diagnostics_channel');
14+
consthttp2=require('http2');
15+
const{ Duplex }=require('stream');
16+
17+
dc.subscribe('http2.client.stream.bodyChunkSent',common.mustNotCall());
18+
19+
dc.subscribe('http2.client.stream.bodySent',common.mustCall(({ stream })=>{
20+
// Since ClientHttp2Stream is not exported from any module, this just checks
21+
// if the stream is an instance of Duplex.
22+
assert.ok(streaminstanceofDuplex);
23+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
24+
}));
25+
26+
constserver=http2.createServer();
27+
server.on('stream',common.mustCall((stream)=>{
28+
stream.respond({},{endStream: true});
29+
}));
30+
31+
server.listen(0,common.mustCall(()=>{
32+
constport=server.address().port;
33+
constclient=http2.connect(`http://localhost:${port}`);
34+
35+
conststream=client.request({[http2.constants.HTTP2_HEADER_METHOD]: 'POST'});
36+
stream.end();
37+
38+
stream.on('response',common.mustCall(()=>{
39+
client.close();
40+
server.close();
41+
}));
42+
},1));
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
// This test ensures that the built-in HTTP/2 diagnostics channels are reporting
8+
// the diagnostics messages for the 'http2.client.stream.bodyChunkSent' and
9+
// 'http2.client.stream.bodySent' channels when ClientHttp2Streams bodies are
10+
// being sent with a single Buffer.
11+
12+
constassert=require('assert');
13+
constdc=require('diagnostics_channel');
14+
consthttp2=require('http2');
15+
const{ Duplex }=require('stream');
16+
17+
letbodyChunkSent=false;
18+
19+
dc.subscribe('http2.client.stream.bodyChunkSent',common.mustCall(({ stream, writev, data, encoding })=>{
20+
// Since ClientHttp2Stream is not exported from any module, this just checks
21+
// if the stream is an instance of Duplex.
22+
assert.ok(streaminstanceofDuplex);
23+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
24+
25+
assert.strictEqual(writev,false);
26+
assert.ok(Buffer.from('foo').equals(data));
27+
assert.strictEqual(encoding,'buffer');
28+
29+
bodyChunkSent=true;
30+
}));
31+
32+
dc.subscribe('http2.client.stream.bodySent',common.mustCall(({ stream })=>{
33+
// 'http2.client.stream.bodyChunkSent' must run first.
34+
assert.ok(bodyChunkSent);
35+
36+
// Since ClientHttp2Stream is not exported from any module, this just checks
37+
// if the stream is an instance of Duplex.
38+
assert.ok(streaminstanceofDuplex);
39+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
40+
}));
41+
42+
constserver=http2.createServer();
43+
server.on('stream',common.mustCall((stream)=>{
44+
stream.respond({},{endStream: true});
45+
}));
46+
47+
server.listen(0,common.mustCall(()=>{
48+
constport=server.address().port;
49+
constclient=http2.connect(`http://localhost:${port}`);
50+
51+
conststream=client.request({[http2.constants.HTTP2_HEADER_METHOD]: 'POST'});
52+
stream.write(Buffer.from('foo'));
53+
stream.end();
54+
55+
stream.on('response',common.mustCall(()=>{
56+
client.close();
57+
server.close();
58+
}));
59+
},1));
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
// This test ensures that the built-in HTTP/2 diagnostics channels are reporting
8+
// the diagnostics messages for the 'http2.client.stream.bodyChunkSent' and
9+
// 'http2.client.stream.bodySent' channels when ClientHttp2Streams bodies are
10+
// being sent with a single string.
11+
12+
constassert=require('assert');
13+
constdc=require('diagnostics_channel');
14+
consthttp2=require('http2');
15+
const{ Duplex }=require('stream');
16+
17+
letbodyChunkSent=false;
18+
19+
dc.subscribe('http2.client.stream.bodyChunkSent',common.mustCall(({ stream, writev, data, encoding })=>{
20+
// Since ClientHttp2Stream is not exported from any module, this just checks
21+
// if the stream is an instance of Duplex.
22+
assert.ok(streaminstanceofDuplex);
23+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
24+
25+
assert.strictEqual(writev,false);
26+
assert.strictEqual(data,'foo');
27+
assert.strictEqual(encoding,'utf8');
28+
29+
bodyChunkSent=true;
30+
}));
31+
32+
dc.subscribe('http2.client.stream.bodySent',common.mustCall(({ stream })=>{
33+
// 'http2.client.stream.bodyChunkSent' must run first.
34+
assert.ok(bodyChunkSent);
35+
36+
// Since ClientHttp2Stream is not exported from any module, this just checks
37+
// if the stream is an instance of Duplex.
38+
assert.ok(streaminstanceofDuplex);
39+
assert.strictEqual(stream.constructor.name,'ClientHttp2Stream');
40+
}));
41+
42+
constserver=http2.createServer();
43+
server.on('stream',common.mustCall((stream)=>{
44+
stream.respond({},{endStream: true});
45+
}));
46+
47+
server.listen(0,common.mustCall(()=>{
48+
constport=server.address().port;
49+
constclient=http2.connect(`http://localhost:${port}`);
50+
51+
conststream=client.request({[http2.constants.HTTP2_HEADER_METHOD]: 'POST'});
52+
stream.write('foo');
53+
stream.end();
54+
55+
stream.on('response',common.mustCall(()=>{
56+
client.close();
57+
server.close();
58+
}));
59+
},1));

0 commit comments

Comments
 (0)