Skip to content

Commit ef0159b

Browse files
legendecastargos
authored andcommitted
inspector: add undici http tracking support
Add basic undici http tracking support via inspector protocol. This allows tracking `fetch` calls with an inspector. PR-URL: #56488 Refs: #53946 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 179a947 commit ef0159b

File tree

6 files changed

+367
-8
lines changed

6 files changed

+367
-8
lines changed

lib/internal/inspector/network.js

+23
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,28 @@ const {
88
const { now } = require('internal/perf/utils');
99
const kInspectorRequestId = Symbol('kInspectorRequestId');
1010

11+
// https://chromedevtools.github.io/devtools-protocol/1-3/Network/#type-ResourceType
12+
const kResourceType = {
13+
Document: 'Document',
14+
Stylesheet: 'Stylesheet',
15+
Image: 'Image',
16+
Media: 'Media',
17+
Font: 'Font',
18+
Script: 'Script',
19+
TextTrack: 'TextTrack',
20+
XHR: 'XHR',
21+
Fetch: 'Fetch',
22+
Prefetch: 'Prefetch',
23+
EventSource: 'EventSource',
24+
WebSocket: 'WebSocket',
25+
Manifest: 'Manifest',
26+
SignedExchange: 'SignedExchange',
27+
Ping: 'Ping',
28+
CSPViolationReport: 'CSPViolationReport',
29+
Preflight: 'Preflight',
30+
Other: 'Other',
31+
};
32+
1133
/**
1234
* Return a monotonically increasing time in seconds since an arbitrary point in the past.
1335
* @returns {number}
@@ -26,6 +48,7 @@ function getNextRequestId() {
2648

2749
module.exports = {
2850
kInspectorRequestId,
51+
kResourceType,
2952
getMonotonicTime,
3053
getNextRequestId,
3154
};

lib/internal/inspector/network_http.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ const {
1010

1111
const {
1212
kInspectorRequestId,
13+
kResourceType,
1314
getMonotonicTime,
1415
getNextRequestId,
1516
} = require('internal/inspector/network');
1617
const dc = require('diagnostics_channel');
1718
const { Network } = require('inspector');
1819

19-
const kResourceType = 'Other';
2020
const kRequestUrl = Symbol('kRequestUrl');
2121

2222
// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>)
@@ -79,7 +79,7 @@ function onClientRequestError({ request, error }) {
7979
Network.loadingFailed({
8080
requestId: request[kInspectorRequestId],
8181
timestamp: getMonotonicTime(),
82-
type: kResourceType,
82+
type: kResourceType.Other,
8383
errorText: error.message,
8484
});
8585
}
@@ -96,7 +96,7 @@ function onClientResponseFinish({ request, response }) {
9696
Network.responseReceived({
9797
requestId: request[kInspectorRequestId],
9898
timestamp: getMonotonicTime(),
99-
type: kResourceType,
99+
type: kResourceType.Other,
100100
response: {
101101
url: request[kRequestUrl],
102102
status: response.statusCode,
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
'use strict';
2+
3+
const {
4+
DateNow,
5+
} = primordials;
6+
7+
const {
8+
kInspectorRequestId,
9+
kResourceType,
10+
getMonotonicTime,
11+
getNextRequestId,
12+
} = require('internal/inspector/network');
13+
const dc = require('diagnostics_channel');
14+
const { Network } = require('inspector');
15+
16+
// Convert an undici request headers array to a plain object (Map<string, string>)
17+
function requestHeadersArrayToDictionary(headers) {
18+
const dict = {};
19+
for (let idx = 0; idx < headers.length; idx += 2) {
20+
const key = `${headers[idx]}`;
21+
const value = `${headers[idx + 1]}`;
22+
dict[key] = value;
23+
}
24+
return dict;
25+
};
26+
27+
// Convert an undici response headers array to a plain object (Map<string, string>)
28+
function responseHeadersArrayToDictionary(headers) {
29+
const dict = {};
30+
for (let idx = 0; idx < headers.length; idx += 2) {
31+
const key = `${headers[idx]}`;
32+
const value = `${headers[idx + 1]}`;
33+
const prevValue = dict[key];
34+
35+
if (typeof prevValue === 'string') {
36+
// ChromeDevTools frontend treats 'set-cookie' as a special case
37+
// https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368
38+
if (key.toLowerCase() === 'set-cookie') dict[key] = `${prevValue}\n${value}`;
39+
else dict[key] = `${prevValue}, ${value}`;
40+
} else {
41+
dict[key] = value;
42+
}
43+
}
44+
return dict;
45+
};
46+
47+
/**
48+
* When a client request starts, emit Network.requestWillBeSent event.
49+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent
50+
* @param {{ request: undici.Request }} event
51+
*/
52+
function onClientRequestStart({ request }) {
53+
const url = `${request.origin}${request.path}`;
54+
request[kInspectorRequestId] = getNextRequestId();
55+
Network.requestWillBeSent({
56+
requestId: request[kInspectorRequestId],
57+
timestamp: getMonotonicTime(),
58+
wallTime: DateNow(),
59+
request: {
60+
url,
61+
method: request.method,
62+
headers: requestHeadersArrayToDictionary(request.headers),
63+
},
64+
});
65+
}
66+
67+
/**
68+
* When a client request errors, emit Network.loadingFailed event.
69+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFailed
70+
* @param {{ request: undici.Request, error: any }} event
71+
*/
72+
function onClientRequestError({ request, error }) {
73+
if (typeof request[kInspectorRequestId] !== 'string') {
74+
return;
75+
}
76+
Network.loadingFailed({
77+
requestId: request[kInspectorRequestId],
78+
timestamp: getMonotonicTime(),
79+
// TODO(legendecas): distinguish between `undici.request` and `undici.fetch`.
80+
type: kResourceType.Fetch,
81+
errorText: error.message,
82+
});
83+
}
84+
85+
/**
86+
* When response headers are received, emit Network.responseReceived event.
87+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-responseReceived
88+
* @param {{ request: undici.Request, response: undici.Response }} event
89+
*/
90+
function onClientResponseHeaders({ request, response }) {
91+
if (typeof request[kInspectorRequestId] !== 'string') {
92+
return;
93+
}
94+
const url = `${request.origin}${request.path}`;
95+
Network.responseReceived({
96+
requestId: request[kInspectorRequestId],
97+
timestamp: getMonotonicTime(),
98+
// TODO(legendecas): distinguish between `undici.request` and `undici.fetch`.
99+
type: kResourceType.Fetch,
100+
response: {
101+
url,
102+
status: response.statusCode,
103+
statusText: response.statusText,
104+
headers: responseHeadersArrayToDictionary(response.headers),
105+
},
106+
});
107+
}
108+
109+
/**
110+
* When a response is completed, emit Network.loadingFinished event.
111+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFinished
112+
* @param {{ request: undici.Request, response: undici.Response }} event
113+
*/
114+
function onClientResponseFinish({ request }) {
115+
if (typeof request[kInspectorRequestId] !== 'string') {
116+
return;
117+
}
118+
Network.loadingFinished({
119+
requestId: request[kInspectorRequestId],
120+
timestamp: getMonotonicTime(),
121+
});
122+
}
123+
124+
function enable() {
125+
dc.subscribe('undici:request:create', onClientRequestStart);
126+
dc.subscribe('undici:request:error', onClientRequestError);
127+
dc.subscribe('undici:request:headers', onClientResponseHeaders);
128+
dc.subscribe('undici:request:trailers', onClientResponseFinish);
129+
}
130+
131+
function disable() {
132+
dc.subscribe('undici:request:create', onClientRequestStart);
133+
dc.subscribe('undici:request:error', onClientRequestError);
134+
dc.subscribe('undici:request:headers', onClientResponseHeaders);
135+
dc.subscribe('undici:request:trailers', onClientResponseFinish);
136+
}
137+
138+
module.exports = {
139+
enable,
140+
disable,
141+
};

lib/internal/inspector_network_tracking.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
function enable() {
44
require('internal/inspector/network_http').enable();
5-
// TODO: add undici request/websocket tracking.
6-
// https://github.com/nodejs/node/issues/53946
5+
require('internal/inspector/network_undici').enable();
76
}
87

98
function disable() {
109
require('internal/inspector/network_http').disable();
11-
// TODO: add undici request/websocket tracking.
12-
// https://github.com/nodejs/node/issues/53946
10+
require('internal/inspector/network_undici').disable();
1311
}
1412

1513
module.exports = {

src/node_builtins.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
120120
#if !HAVE_INSPECTOR
121121
"inspector", "inspector/promises", "internal/util/inspector",
122122
"internal/inspector/network", "internal/inspector/network_http",
123-
"internal/inspector_async_hook", "internal/inspector_network_tracking",
123+
"internal/inspector/network_undici", "internal/inspector_async_hook",
124+
"internal/inspector_network_tracking",
124125
#endif // !HAVE_INSPECTOR
125126

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

0 commit comments

Comments
 (0)