Skip to content

Commit 49ff459

Browse files
committed
http2: preserve original chunk type
Signed-off-by: Darshan Sen <raisinten@gmail.com>
1 parent 3e79504 commit 49ff459

7 files changed

+251
-31
lines changed

doc/api/diagnostics_channel.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,9 @@ Emitted when a stream is received on the client.
12461246
##### Event: `'http2.client.stream.bodyChunkSent'`
12471247

12481248
* `stream` {ClientHttp2Stream}
1249-
* `chunk` {Buffer}
1249+
* `writev` {boolean}
1250+
* `data` {Buffer | string | Array\<Buffer | {chunk: Buffer|string, encoding: string}>}
1251+
* `encoding` {string}
12501252

12511253
Emitted when a chunk of the client stream body is being sent.
12521254

lib/internal/http2/core.js

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,6 @@ const { UV_EOF } = internalBinding('uv');
186186
const { StreamPipe } = internalBinding('stream_pipe');
187187
const { _connectionListener: httpConnectionListener } = http;
188188

189-
const { Buffer } = require('buffer');
190-
191189
const dc = require('diagnostics_channel');
192190
const onClientStreamCreatedChannel = dc.channel('http2.client.stream.created');
193191
const onClientStreamStartChannel = dc.channel('http2.client.stream.start');
@@ -2306,30 +2304,11 @@ class Http2Stream extends Duplex {
23062304
trackWriteState(this, req.bytes);
23072305

23082306
if (this.session[kType] === NGHTTP2_SESSION_CLIENT && onClientStreamBodyChunkSentChannel.hasSubscribers) {
2309-
let chunk;
2310-
2311-
if (ArrayIsArray(data)) {
2312-
const buffers = [];
2313-
for (let i = 0; i < data.length; ++i) {
2314-
if (typeof data[i] === 'object') {
2315-
if (typeof data[i].chunk === 'string') {
2316-
buffers.push(Buffer.from(data[i].chunk, data[i].encoding));
2317-
} else {
2318-
buffers.push(data[i].chunk);
2319-
}
2320-
}
2321-
}
2322-
2323-
chunk = Buffer.concat(buffers);
2324-
} else if (typeof data === 'string') {
2325-
chunk = Buffer.from(data);
2326-
} else {
2327-
chunk = data;
2328-
}
2329-
23302307
onClientStreamBodyChunkSentChannel.publish({
23312308
stream: this,
2332-
chunk,
2309+
writev,
2310+
data,
2311+
encoding,
23332312
});
23342313
}
23352314
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict';
2+
3+
const common = 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+
const assert = require('assert');
13+
const dc = require('diagnostics_channel');
14+
const http2 = require('http2');
15+
const { Duplex } = require('stream');
16+
17+
let bodyChunkSent = 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(stream instanceof Duplex);
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(stream instanceof Duplex);
51+
assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream');
52+
}));
53+
54+
const server = http2.createServer();
55+
server.on('stream', common.mustCall((stream) => {
56+
stream.respond({}, { endStream: true });
57+
}));
58+
59+
server.listen(0, common.mustCall(() => {
60+
const port = server.address().port;
61+
const client = http2.connect(`http://localhost:${port}`);
62+
63+
const stream = client.request({ [http2.constants.HTTP2_HEADER_METHOD]: 'POST' });
64+
stream.write('héllo', 'latin1');
65+
stream.write(Buffer.from('foo'));
66+
stream.write(new TextEncoder().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+
const common = 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+
const assert = require('assert');
13+
const dc = require('diagnostics_channel');
14+
const http2 = require('http2');
15+
const { Duplex } = require('stream');
16+
17+
let bodyChunkSent = 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(stream instanceof Duplex);
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(stream instanceof Duplex);
45+
assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream');
46+
}));
47+
48+
const server = http2.createServer();
49+
server.on('stream', common.mustCall((stream) => {
50+
stream.respond({}, { endStream: true });
51+
}));
52+
53+
server.listen(0, common.mustCall(() => {
54+
const port = server.address().port;
55+
const client = http2.connect(`http://localhost:${port}`);
56+
57+
const stream = 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+
const common = 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+
const assert = require('assert');
13+
const dc = require('diagnostics_channel');
14+
const http2 = 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(stream instanceof Duplex);
23+
assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream');
24+
}));
25+
26+
const server = http2.createServer();
27+
server.on('stream', common.mustCall((stream) => {
28+
stream.respond({}, { endStream: true });
29+
}));
30+
31+
server.listen(0, common.mustCall(() => {
32+
const port = server.address().port;
33+
const client = http2.connect(`http://localhost:${port}`);
34+
35+
const stream = 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+
const common = 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+
const assert = require('assert');
13+
const dc = require('diagnostics_channel');
14+
const http2 = require('http2');
15+
const { Duplex } = require('stream');
16+
17+
let bodyChunkSent = 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(stream instanceof Duplex);
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(stream instanceof Duplex);
39+
assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream');
40+
}));
41+
42+
const server = http2.createServer();
43+
server.on('stream', common.mustCall((stream) => {
44+
stream.respond({}, { endStream: true });
45+
}));
46+
47+
server.listen(0, common.mustCall(() => {
48+
const port = server.address().port;
49+
const client = http2.connect(`http://localhost:${port}`);
50+
51+
const stream = 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));

test/parallel/test-diagnostics-channel-http2-client-stream-body.js renamed to test/parallel/test-diagnostics-channel-http2-client-stream-body-single-string.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ if (!common.hasCrypto)
77
// This test ensures that the built-in HTTP/2 diagnostics channels are reporting
88
// the diagnostics messages for the 'http2.client.stream.bodyChunkSent' and
99
// 'http2.client.stream.bodySent' channels when ClientHttp2Streams bodies are
10-
// being sent.
10+
// being sent with a single string.
1111

1212
const assert = require('assert');
1313
const dc = require('diagnostics_channel');
@@ -16,14 +16,15 @@ const { Duplex } = require('stream');
1616

1717
let bodyChunkSent = false;
1818

19-
dc.subscribe('http2.client.stream.bodyChunkSent', common.mustCall(({ stream, chunk }) => {
19+
dc.subscribe('http2.client.stream.bodyChunkSent', common.mustCall(({ stream, writev, data, encoding }) => {
2020
// Since ClientHttp2Stream is not exported from any module, this just checks
2121
// if the stream is an instance of Duplex.
2222
assert.ok(stream instanceof Duplex);
2323
assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream');
2424

25-
assert.strictEqual(Buffer.isBuffer(chunk), true);
26-
assert.strictEqual(chunk.toString(), 'foobarbaz');
25+
assert.strictEqual(writev, false);
26+
assert.strictEqual(data, 'foo');
27+
assert.strictEqual(encoding, 'utf8');
2728

2829
bodyChunkSent = true;
2930
}));
@@ -49,8 +50,6 @@ server.listen(0, common.mustCall(() => {
4950

5051
const stream = client.request({ [http2.constants.HTTP2_HEADER_METHOD]: 'POST' });
5152
stream.write('foo');
52-
stream.write(Buffer.from('bar'));
53-
stream.write(new TextEncoder().encode('baz'));
5453
stream.end();
5554

5655
stream.on('response', common.mustCall(() => {

0 commit comments

Comments
 (0)