Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d2087ba

Browse filesBrowse files
RaisinTenaduh95
authored andcommitted
inspector: support inspecting HTTP/2 request and response bodies
Signed-off-by: Darshan Sen <raisinten@gmail.com> PR-URL: #60483 Refs: #53946 Reviewed-By: Ryuhei Shima <shimaryuhei@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent ad376c3 commit d2087ba
Copy full SHA for d2087ba

File tree

Expand file treeCollapse file tree

2 files changed

+133
-10
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+133
-10
lines changed
Open diff view settings
Collapse file

‎lib/internal/inspector/network_http2.js‎

Copy file name to clipboardExpand all lines: lib/internal/inspector/network_http2.js
+83Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const {
2828
HTTP2_HEADER_STATUS,
2929
NGHTTP2_NO_ERROR,
3030
} = internalBinding('http2').constants;
31+
const EventEmitter = require('events');
32+
const { Buffer } = require('buffer');
3133

3234
const kRequestUrl = Symbol('kRequestUrl');
3335

@@ -99,6 +101,7 @@ function onClientStreamCreated({ stream, headers }) {
99101
url,
100102
method,
101103
headers: convertedHeaderObject,
104+
hasPostData: !stream.writableEnded,
102105
},
103106
});
104107
}
@@ -121,6 +124,66 @@ function onClientStreamError({ stream, error }) {
121124
});
122125
}
123126

127+
/**
128+
* When a chunk of the request body is being sent, cache it until `getRequestPostData` request.
129+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#method-getRequestPostData
130+
* @param {{
131+
* stream: import('http2').ClientHttp2Stream,
132+
* writev: boolean,
133+
* data: Buffer | string | Array<Buffer | {chunk: Buffer|string, encoding: string}>,
134+
* encoding: string,
135+
* }} event
136+
*/
137+
function onClientStreamBodyChunkSent({ stream, writev, data, encoding }) {
138+
if (typeof stream[kInspectorRequestId] !== 'string') {
139+
return;
140+
}
141+
142+
let chunk;
143+
144+
if (writev) {
145+
if (data.allBuffers) {
146+
chunk = Buffer.concat(data);
147+
} else {
148+
const buffers = [];
149+
for (let i = 0; i < data.length; ++i) {
150+
if (typeof data[i].chunk === 'string') {
151+
buffers.push(Buffer.from(data[i].chunk, data[i].encoding));
152+
} else {
153+
buffers.push(data[i].chunk);
154+
}
155+
}
156+
chunk = Buffer.concat(buffers);
157+
}
158+
} else if (typeof data === 'string') {
159+
chunk = Buffer.from(data, encoding);
160+
} else {
161+
chunk = data;
162+
}
163+
164+
Network.dataSent({
165+
requestId: stream[kInspectorRequestId],
166+
timestamp: getMonotonicTime(),
167+
dataLength: chunk.byteLength,
168+
data: chunk,
169+
});
170+
}
171+
172+
/**
173+
* Mark a request body as fully sent.
174+
* @param {{ stream: import('http2').ClientHttp2Stream }} event
175+
*/
176+
function onClientStreamBodySent({ stream }) {
177+
if (typeof stream[kInspectorRequestId] !== 'string') {
178+
return;
179+
}
180+
181+
Network.dataSent({
182+
requestId: stream[kInspectorRequestId],
183+
finished: true,
184+
});
185+
}
186+
124187
/**
125188
* When response headers are received, emit Network.responseReceived event.
126189
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-responseReceived
@@ -146,6 +209,24 @@ function onClientStreamFinish({ stream, headers }) {
146209
charset,
147210
},
148211
});
212+
213+
// Unlike stream.on('data', ...), this does not put the stream into flowing mode.
214+
EventEmitter.prototype.on.call(stream, 'data', (chunk) => {
215+
/**
216+
* When a chunk of the response body has been received, cache it until `getResponseBody` request
217+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#method-getResponseBody or
218+
* stream it with `streamResourceContent` request.
219+
* https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-streamResourceContent
220+
*/
221+
222+
Network.dataReceived({
223+
requestId: stream[kInspectorRequestId],
224+
timestamp: getMonotonicTime(),
225+
dataLength: chunk.byteLength,
226+
encodedDataLength: chunk.byteLength,
227+
data: chunk,
228+
});
229+
});
149230
}
150231

151232
/**
@@ -175,4 +256,6 @@ module.exports = registerDiagnosticChannels([
175256
['http2.client.stream.error', onClientStreamError],
176257
['http2.client.stream.finish', onClientStreamFinish],
177258
['http2.client.stream.close', onClientStreamClose],
259+
['http2.client.stream.bodyChunkSent', onClientStreamBodyChunkSent],
260+
['http2.client.stream.bodySent', onClientStreamBodySent],
178261
]);
Collapse file

‎test/parallel/test-inspector-network-http2.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-inspector-network-http2.js
+50-10Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ const inspector = require('node:inspector/promises');
1414
const session = new inspector.Session();
1515
session.connect();
1616

17+
const requestBody = { 'hello': 'world' };
18+
1719
const requestHeaders = {
1820
'x-header1': ['value1', 'value2'],
1921
[http2.constants.HTTP2_HEADER_ACCEPT_LANGUAGE]: 'en-US',
2022
[http2.constants.HTTP2_HEADER_AGE]: 1000,
23+
[http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/json; charset=utf-8',
2124
[http2.constants.HTTP2_HEADER_COOKIE]: ['k1=v1', 'k2=v2'],
22-
[http2.constants.HTTP2_HEADER_METHOD]: 'GET',
25+
[http2.constants.HTTP2_HEADER_METHOD]: 'POST',
2326
[http2.constants.HTTP2_HEADER_PATH]: '/hello-world',
2427
};
2528

@@ -54,23 +57,35 @@ const pushResponseHeaders = {
5457
[http2.constants.HTTP2_HEADER_STATUS]: 200,
5558
};
5659

60+
const styleCss = 'body { color: red; }\n';
61+
const serverResponse = 'hello world\n';
62+
5763
const kTimeout = 1000;
5864
const kDelta = 200;
5965

6066
const handleStream = (stream, headers) => {
6167
const path = headers[http2.constants.HTTP2_HEADER_PATH];
68+
let body = '';
6269
switch (path) {
6370
case '/hello-world':
64-
stream.pushStream(pushRequestHeaders, common.mustSucceed((pushStream) => {
65-
pushStream.respond(pushResponseHeaders);
66-
pushStream.end('body { color: red; }\n');
67-
}));
71+
stream.on('data', (chunk) => {
72+
body += chunk;
73+
});
6874

69-
stream.respond(responseHeaders);
75+
stream.on('end', () => {
76+
assert.strictEqual(body, JSON.stringify(requestBody));
7077

71-
setTimeout(() => {
72-
stream.end('hello world\n');
73-
}, kTimeout);
78+
stream.pushStream(pushRequestHeaders, common.mustSucceed((pushStream) => {
79+
pushStream.respond(pushResponseHeaders);
80+
pushStream.end(styleCss);
81+
}));
82+
83+
stream.respond(responseHeaders);
84+
85+
setTimeout(() => {
86+
stream.end(serverResponse);
87+
}, kTimeout);
88+
});
7489
break;
7590
case '/trigger-error':
7691
stream.close(http2.constants.NGHTTP2_STREAM_CLOSED);
@@ -114,7 +129,6 @@ function verifyRequestWillBeSent({ method, params }, expectedUrl) {
114129

115130
assert.ok(params.requestId.startsWith('node-network-event-'));
116131
assert.strictEqual(params.request.url, expectedUrl);
117-
assert.strictEqual(params.request.method, 'GET');
118132
assert.strictEqual(typeof params.request.headers, 'object');
119133

120134
if (expectedUrl.endsWith('/hello-world')) {
@@ -123,10 +137,17 @@ function verifyRequestWillBeSent({ method, params }, expectedUrl) {
123137
assert.strictEqual(params.request.headers.age, '1000');
124138
assert.strictEqual(params.request.headers['x-header1'], 'value1, value2');
125139
assert.ok(findFrameInInitiator(__filename, params.initiator));
140+
assert.strictEqual(params.request.hasPostData, true);
141+
assert.strictEqual(params.request.method, 'POST');
126142
} else if (expectedUrl.endsWith('/style.css')) {
127143
assert.strictEqual(params.request.headers['x-header3'], 'value1, value2');
128144
assert.strictEqual(params.request.headers['x-push'], 'true');
129145
assert.ok(!findFrameInInitiator(__filename, params.initiator));
146+
assert.strictEqual(params.request.hasPostData, true);
147+
assert.strictEqual(params.request.method, 'GET');
148+
} else {
149+
assert.strictEqual(params.request.hasPostData, false);
150+
assert.strictEqual(params.request.method, 'GET');
130151
}
131152

132153
assert.strictEqual(typeof params.timestamp, 'number');
@@ -198,6 +219,8 @@ async function testHttp2(secure = false) {
198219
rejectUnauthorized: false,
199220
});
200221
const request = client.request(requestHeaders);
222+
request.write(JSON.stringify(requestBody));
223+
request.end();
201224

202225
// Dump the responses.
203226
request.on('data', () => {});
@@ -216,6 +239,11 @@ async function testHttp2(secure = false) {
216239
verifyRequestWillBeSent(mainRequest, url);
217240
verifyRequestWillBeSent(pushRequest, pushedUrl);
218241

242+
const { postData } = await session.post('Network.getRequestPostData', {
243+
requestId: mainRequest.params.requestId
244+
});
245+
assert.strictEqual(postData, JSON.stringify(requestBody));
246+
219247
const [
220248
{ value: [ mainResponse ] },
221249
{ value: [ pushResponse ] },
@@ -230,6 +258,18 @@ async function testHttp2(secure = false) {
230258
verifyLoadingFinished(event1);
231259
verifyLoadingFinished(event2);
232260

261+
const responseBody = await session.post('Network.getResponseBody', {
262+
requestId: mainRequest.params.requestId,
263+
});
264+
assert.strictEqual(responseBody.base64Encoded, false);
265+
assert.strictEqual(responseBody.body, serverResponse);
266+
267+
const pushResponseBody = await session.post('Network.getResponseBody', {
268+
requestId: pushRequest.params.requestId,
269+
});
270+
assert.strictEqual(pushResponseBody.base64Encoded, true);
271+
assert.strictEqual(Buffer.from(pushResponseBody.body, 'base64').toString(), styleCss);
272+
233273
const mainFinished = [event1, event2]
234274
.find((event) => event.params.requestId === mainResponse.params.requestId);
235275
const pushFinished = [event1, event2]

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.