Skip to content

Commit 9a954fb

Browse files
legendecasaduh95
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 <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 17d59ef commit 9a954fb

6 files changed

Lines changed: 367 additions & 8 deletions

File tree

‎lib/internal/inspector/network.js‎

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

11+
// https://chromedevtools.github.io/devtools-protocol/1-3/Network/#type-ResourceType
12+
constkResourceType={
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‎

Lines changed: 3 additions & 3 deletions
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
constdc=require('diagnostics_channel');
1718
const{ Network }=require('inspector');
1819

19-
constkResourceType='Other';
2020
constkRequestUrl=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,
Lines changed: 141 additions & 0 deletions
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+
constdc=require('diagnostics_channel');
14+
const{ Network }=require('inspector');
15+
16+
// Convert an undici request headers array to a plain object (Map<string, string>)
17+
functionrequestHeadersArrayToDictionary(headers){
18+
constdict={};
19+
for(letidx=0;idx<headers.length;idx+=2){
20+
constkey=`${headers[idx]}`;
21+
constvalue=`${headers[idx+1]}`;
22+
dict[key]=value;
23+
}
24+
returndict;
25+
};
26+
27+
// Convert an undici response headers array to a plain object (Map<string, string>)
28+
functionresponseHeadersArrayToDictionary(headers){
29+
constdict={};
30+
for(letidx=0;idx<headers.length;idx+=2){
31+
constkey=`${headers[idx]}`;
32+
constvalue=`${headers[idx+1]}`;
33+
constprevValue=dict[key];
34+
35+
if(typeofprevValue==='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+
elsedict[key]=`${prevValue}, ${value}`;
40+
}else{
41+
dict[key]=value;
42+
}
43+
}
44+
returndict;
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+
functiononClientRequestStart({ request }){
53+
consturl=`${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+
functiononClientRequestError({ request, error }){
73+
if(typeofrequest[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+
functiononClientResponseHeaders({ request, response }){
91+
if(typeofrequest[kInspectorRequestId]!=='string'){
92+
return;
93+
}
94+
consturl=`${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+
functiononClientResponseFinish({ request }){
115+
if(typeofrequest[kInspectorRequestId]!=='string'){
116+
return;
117+
}
118+
Network.loadingFinished({
119+
requestId: request[kInspectorRequestId],
120+
timestamp: getMonotonicTime(),
121+
});
122+
}
123+
124+
functionenable(){
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+
functiondisable(){
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‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
functionenable(){
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
functiondisable(){
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‎

Lines changed: 2 additions & 1 deletion
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)