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 b806661

Browse filesBrowse files
RaisinTenrichardlau
authored andcommitted
inspector: add http2 tracking support
This allows tracking HTTP/2 calls through the Network tab of Chrome DevTools for Node.js. Signed-off-by: Darshan Sen <raisinten@gmail.com> PR-URL: #59611 Refs: #53946 Reviewed-By: Ryuhei Shima <shimaryuhei@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Kohei Ueno <kohei.ueno119@gmail.com>
1 parent fd7559f commit b806661
Copy full SHA for b806661

File tree

Expand file treeCollapse file tree

4 files changed

+496
-2
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+496
-2
lines changed
Open diff view settings
Collapse file
+190Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
'use strict';
2+
3+
const {
4+
ArrayIsArray,
5+
DateNow,
6+
ObjectEntries,
7+
String,
8+
Symbol,
9+
} = primordials;
10+
11+
const {
12+
kInspectorRequestId,
13+
kResourceType,
14+
getMonotonicTime,
15+
getNextRequestId,
16+
sniffMimeType,
17+
} = require('internal/inspector/network');
18+
const dc = require('diagnostics_channel');
19+
const { Network } = require('inspector');
20+
const {
21+
HTTP2_HEADER_AUTHORITY,
22+
HTTP2_HEADER_CONTENT_TYPE,
23+
HTTP2_HEADER_COOKIE,
24+
HTTP2_HEADER_METHOD,
25+
HTTP2_HEADER_PATH,
26+
HTTP2_HEADER_SCHEME,
27+
HTTP2_HEADER_SET_COOKIE,
28+
HTTP2_HEADER_STATUS,
29+
NGHTTP2_NO_ERROR,
30+
} = internalBinding('http2').constants;
31+
32+
const kRequestUrl = Symbol('kRequestUrl');
33+
34+
// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>)
35+
function convertHeaderObject(headers = {}) {
36+
let scheme;
37+
let authority;
38+
let path;
39+
let method;
40+
let statusCode;
41+
let charset;
42+
let mimeType;
43+
const dict = {};
44+
45+
for (const { 0: key, 1: value } of ObjectEntries(headers)) {
46+
const lowerCasedKey = key.toLowerCase();
47+
48+
if (lowerCasedKey === HTTP2_HEADER_SCHEME) {
49+
scheme = value;
50+
} else if (lowerCasedKey === HTTP2_HEADER_AUTHORITY) {
51+
authority = value;
52+
} else if (lowerCasedKey === HTTP2_HEADER_PATH) {
53+
path = value;
54+
} else if (lowerCasedKey === HTTP2_HEADER_METHOD) {
55+
method = value;
56+
} else if (lowerCasedKey === HTTP2_HEADER_STATUS) {
57+
statusCode = value;
58+
} else if (lowerCasedKey === HTTP2_HEADER_CONTENT_TYPE) {
59+
const result = sniffMimeType(value);
60+
charset = result.charset;
61+
mimeType = result.mimeType;
62+
}
63+
64+
if (typeof value === 'string') {
65+
dict[key] = value;
66+
} else if (ArrayIsArray(value)) {
67+
if (lowerCasedKey === HTTP2_HEADER_COOKIE) dict[key] = value.join('; ');
68+
// ChromeDevTools frontend treats 'set-cookie' as a special case
69+
// https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368
70+
else if (lowerCasedKey === HTTP2_HEADER_SET_COOKIE) dict[key] = value.join('\n');
71+
else dict[key] = value.join(', ');
72+
} else {
73+
dict[key] = String(value);
74+
}
75+
}
76+
77+
const url = `${scheme}://${authority}${path}`;
78+
79+
return [dict, url, method, statusCode, charset, mimeType];
80+
}
81+
82+
/**
83+
* When a client stream is created, emit Network.requestWillBeSent event.
84+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent
85+
* @param {{ stream: import('http2').ClientHttp2Stream, headers: object }} event
86+
*/
87+
function onClientStreamCreated({ stream, headers }) {
88+
stream[kInspectorRequestId] = getNextRequestId();
89+
90+
const { 0: convertedHeaderObject, 1: url, 2: method, 4: charset } = convertHeaderObject(headers);
91+
stream[kRequestUrl] = url;
92+
93+
Network.requestWillBeSent({
94+
requestId: stream[kInspectorRequestId],
95+
timestamp: getMonotonicTime(),
96+
wallTime: DateNow(),
97+
charset,
98+
request: {
99+
url,
100+
method,
101+
headers: convertedHeaderObject,
102+
},
103+
});
104+
}
105+
106+
/**
107+
* When a client stream errors, emit Network.loadingFailed event.
108+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFailed
109+
* @param {{ stream: import('http2').ClientHttp2Stream, error: any }} event
110+
*/
111+
function onClientStreamError({ stream, error }) {
112+
if (typeof stream[kInspectorRequestId] !== 'string') {
113+
return;
114+
}
115+
116+
Network.loadingFailed({
117+
requestId: stream[kInspectorRequestId],
118+
timestamp: getMonotonicTime(),
119+
type: kResourceType.Other,
120+
errorText: error.message,
121+
});
122+
}
123+
124+
/**
125+
* When response headers are received, emit Network.responseReceived event.
126+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-responseReceived
127+
* @param {{ stream: import('http2').ClientHttp2Stream, headers: object }} event
128+
*/
129+
function onClientStreamFinish({ stream, headers }) {
130+
if (typeof stream[kInspectorRequestId] !== 'string') {
131+
return;
132+
}
133+
134+
const { 0: convertedHeaderObject, 3: statusCode, 4: charset, 5: mimeType } = convertHeaderObject(headers);
135+
136+
Network.responseReceived({
137+
requestId: stream[kInspectorRequestId],
138+
timestamp: getMonotonicTime(),
139+
type: kResourceType.Other,
140+
response: {
141+
url: stream[kRequestUrl],
142+
status: statusCode,
143+
statusText: '',
144+
headers: convertedHeaderObject,
145+
mimeType,
146+
charset,
147+
},
148+
});
149+
}
150+
151+
/**
152+
* When user code completes consuming the response body, emit Network.loadingFinished event.
153+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFinished
154+
* @param {{ stream: import('http2').ClientHttp2Stream }} event
155+
*/
156+
function onClientStreamClose({ stream }) {
157+
if (typeof stream[kInspectorRequestId] !== 'string') {
158+
return;
159+
}
160+
161+
if (stream.rstCode !== NGHTTP2_NO_ERROR) {
162+
// This is an error case, so only Network.loadingFailed should be emitted
163+
// which is already done by onClientStreamError().
164+
return;
165+
}
166+
167+
Network.loadingFinished({
168+
requestId: stream[kInspectorRequestId],
169+
timestamp: getMonotonicTime(),
170+
});
171+
}
172+
173+
function enable() {
174+
dc.subscribe('http2.client.stream.created', onClientStreamCreated);
175+
dc.subscribe('http2.client.stream.error', onClientStreamError);
176+
dc.subscribe('http2.client.stream.finish', onClientStreamFinish);
177+
dc.subscribe('http2.client.stream.close', onClientStreamClose);
178+
}
179+
180+
function disable() {
181+
dc.unsubscribe('http2.client.stream.created', onClientStreamCreated);
182+
dc.unsubscribe('http2.client.stream.error', onClientStreamError);
183+
dc.unsubscribe('http2.client.stream.finish', onClientStreamFinish);
184+
dc.unsubscribe('http2.client.stream.close', onClientStreamClose);
185+
}
186+
187+
module.exports = {
188+
enable,
189+
disable,
190+
};
Collapse file

‎lib/internal/inspector_network_tracking.js‎

Copy file name to clipboardExpand all lines: lib/internal/inspector_network_tracking.js
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
function enable() {
44
require('internal/inspector/network_http').enable();
5+
require('internal/inspector/network_http2').enable();
56
require('internal/inspector/network_undici').enable();
67
}
78

89
function disable() {
910
require('internal/inspector/network_http').disable();
11+
require('internal/inspector/network_http2').disable();
1012
require('internal/inspector/network_undici').disable();
1113
}
1214

Collapse file

‎src/node_builtins.cc‎

Copy file name to clipboardExpand all lines: src/node_builtins.cc
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
123123
#if !HAVE_INSPECTOR
124124
"inspector", "inspector/promises", "internal/util/inspector",
125125
"internal/inspector/network", "internal/inspector/network_http",
126-
"internal/inspector/network_undici", "internal/inspector_async_hook",
127-
"internal/inspector_network_tracking",
126+
"internal/inspector/network_http2", "internal/inspector/network_undici",
127+
"internal/inspector_async_hook", "internal/inspector_network_tracking",
128128
#endif // !HAVE_INSPECTOR
129129

130130
#if !NODE_USE_V8_PLATFORM || !defined(NODE_HAVE_I18N_SUPPORT)

0 commit comments

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