Skip to content

Commit f185e8a

Browse files
legendecasRafaelGSS
authored andcommitted
inspector: report loadingFinished until the response data is consumed
The `Network.loadingFinished` should be deferred until the response is complete and the data is fully consumed. Also, report correct request url with the specified port by retrieving the host from the request headers. PR-URL: #56372 Refs: #53946 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Kohei Ueno <[email protected]>
1 parent 1e201fd commit f185e8a

File tree

6 files changed

+412
-299
lines changed

6 files changed

+412
-299
lines changed

‎lib/internal/inspector/network.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
const{
4+
NumberMAX_SAFE_INTEGER,
5+
Symbol,
6+
}=primordials;
7+
8+
const{ now }=require('internal/perf/utils');
9+
constkInspectorRequestId=Symbol('kInspectorRequestId');
10+
11+
/**
12+
* Return a monotonically increasing time in seconds since an arbitrary point in the past.
13+
* @returns{number}
14+
*/
15+
functiongetMonotonicTime(){
16+
returnnow()/1000;
17+
}
18+
19+
letrequestId=0;
20+
functiongetNextRequestId(){
21+
if(requestId===NumberMAX_SAFE_INTEGER){
22+
requestId=0;
23+
}
24+
return`node-network-event-${++requestId}`;
25+
};
26+
27+
module.exports={
28+
kInspectorRequestId,
29+
getMonotonicTime,
30+
getNextRequestId,
31+
};
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
'use strict';
2+
3+
const{
4+
ArrayIsArray,
5+
DateNow,
6+
ObjectEntries,
7+
String,
8+
Symbol,
9+
}=primordials;
10+
11+
const{
12+
kInspectorRequestId,
13+
getMonotonicTime,
14+
getNextRequestId,
15+
}=require('internal/inspector/network');
16+
constdc=require('diagnostics_channel');
17+
const{ Network }=require('inspector');
18+
19+
constkResourceType='Other';
20+
constkRequestUrl=Symbol('kRequestUrl');
21+
22+
// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>)
23+
constconvertHeaderObject=(headers={})=>{
24+
// The 'host' header that contains the host and port of the URL.
25+
lethost;
26+
constdict={};
27+
for(const{0: key,1: value}ofObjectEntries(headers)){
28+
if(key.toLowerCase()==='host'){
29+
host=value;
30+
}
31+
if(typeofvalue==='string'){
32+
dict[key]=value;
33+
}elseif(ArrayIsArray(value)){
34+
if(key.toLowerCase()==='cookie')dict[key]=value.join(' ');
35+
// ChromeDevTools frontend treats 'set-cookie' as a special case
36+
// https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368
37+
elseif(key.toLowerCase()==='set-cookie')dict[key]=value.join('\n');
38+
elsedict[key]=value.join(', ');
39+
}else{
40+
dict[key]=String(value);
41+
}
42+
}
43+
return[host,dict];
44+
};
45+
46+
/**
47+
* When a client request starts, emit Network.requestWillBeSent event.
48+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent
49+
* @param{{request: import('http').ClientRequest }} event
50+
*/
51+
functiononClientRequestStart({ request }){
52+
request[kInspectorRequestId]=getNextRequestId();
53+
54+
const{0: host,1: headers}=convertHeaderObject(request.getHeaders());
55+
consturl=`${request.protocol}//${host}${request.path}`;
56+
request[kRequestUrl]=url;
57+
58+
Network.requestWillBeSent({
59+
requestId: request[kInspectorRequestId],
60+
timestamp: getMonotonicTime(),
61+
wallTime: DateNow(),
62+
request: {
63+
url,
64+
method: request.method,
65+
headers,
66+
},
67+
});
68+
}
69+
70+
/**
71+
* When a client request errors, emit Network.loadingFailed event.
72+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFailed
73+
* @param{{request: import('http').ClientRequest, error: any }} event
74+
*/
75+
functiononClientRequestError({ request, error }){
76+
if(typeofrequest[kInspectorRequestId]!=='string'){
77+
return;
78+
}
79+
Network.loadingFailed({
80+
requestId: request[kInspectorRequestId],
81+
timestamp: getMonotonicTime(),
82+
type: kResourceType,
83+
errorText: error.message,
84+
});
85+
}
86+
87+
/**
88+
* When response headers are received, emit Network.responseReceived event.
89+
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-responseReceived
90+
* @param{{request: import('http').ClientRequest, error: any }} event
91+
*/
92+
functiononClientResponseFinish({ request, response }){
93+
if(typeofrequest[kInspectorRequestId]!=='string'){
94+
return;
95+
}
96+
Network.responseReceived({
97+
requestId: request[kInspectorRequestId],
98+
timestamp: getMonotonicTime(),
99+
type: kResourceType,
100+
response: {
101+
url: request[kRequestUrl],
102+
status: response.statusCode,
103+
statusText: response.statusMessage??'',
104+
headers: convertHeaderObject(response.headers)[1],
105+
},
106+
});
107+
108+
// Wait until the response body is consumed by user code.
109+
response.once('end',()=>{
110+
Network.loadingFinished({
111+
requestId: request[kInspectorRequestId],
112+
timestamp: getMonotonicTime(),
113+
});
114+
});
115+
}
116+
117+
functionenable(){
118+
dc.subscribe('http.client.request.start',onClientRequestStart);
119+
dc.subscribe('http.client.request.error',onClientRequestError);
120+
dc.subscribe('http.client.response.finish',onClientResponseFinish);
121+
}
122+
123+
functiondisable(){
124+
dc.unsubscribe('http.client.request.start',onClientRequestStart);
125+
dc.unsubscribe('http.client.request.error',onClientRequestError);
126+
dc.unsubscribe('http.client.response.finish',onClientResponseFinish);
127+
}
128+
129+
module.exports={
130+
enable,
131+
disable,
132+
};

‎lib/internal/inspector_network_tracking.js‎

Lines changed: 6 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,15 @@
11
'use strict';
22

3-
const{
4-
ArrayIsArray,
5-
DateNow,
6-
ObjectEntries,
7-
String,
8-
}=primordials;
9-
10-
letdc;
11-
letNetwork;
12-
13-
letrequestId=0;
14-
constgetNextRequestId=()=>`node-network-event-${++requestId}`;
15-
16-
// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>)
17-
constheaderObjectToDictionary=(headers={})=>{
18-
constdict={};
19-
for(const{0: key,1: value}ofObjectEntries(headers)){
20-
if(typeofvalue==='string'){
21-
dict[key]=value;
22-
}elseif(ArrayIsArray(value)){
23-
if(key.toLowerCase()==='cookie')dict[key]=value.join(' ');
24-
// ChromeDevTools frontend treats 'set-cookie' as a special case
25-
// https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368
26-
elseif(key.toLowerCase()==='set-cookie')dict[key]=value.join('\n');
27-
elsedict[key]=value.join(', ');
28-
}else{
29-
dict[key]=String(value);
30-
}
31-
}
32-
returndict;
33-
};
34-
35-
functiononClientRequestStart({ request }){
36-
consturl=`${request.protocol}//${request.host}${request.path}`;
37-
constwallTime=DateNow();
38-
consttimestamp=wallTime/1000;
39-
request._inspectorRequestId=getNextRequestId();
40-
Network.requestWillBeSent({
41-
requestId: request._inspectorRequestId,
42-
timestamp,
43-
wallTime,
44-
request: {
45-
url,
46-
method: request.method,
47-
headers: headerObjectToDictionary(request.getHeaders()),
48-
},
49-
});
50-
}
51-
52-
functiononClientRequestError({ request, error }){
53-
if(typeofrequest._inspectorRequestId!=='string'){
54-
return;
55-
}
56-
consttimestamp=DateNow()/1000;
57-
Network.loadingFailed({
58-
requestId: request._inspectorRequestId,
59-
timestamp,
60-
type: 'Other',
61-
errorText: error.message,
62-
});
63-
}
64-
65-
functiononClientResponseFinish({ request, response }){
66-
if(typeofrequest._inspectorRequestId!=='string'){
67-
return;
68-
}
69-
consturl=`${request.protocol}//${request.host}${request.path}`;
70-
consttimestamp=DateNow()/1000;
71-
Network.responseReceived({
72-
requestId: request._inspectorRequestId,
73-
timestamp,
74-
type: 'Other',
75-
response: {
76-
url,
77-
status: response.statusCode,
78-
statusText: response.statusMessage??'',
79-
headers: headerObjectToDictionary(response.headers),
80-
},
81-
});
82-
Network.loadingFinished({
83-
requestId: request._inspectorRequestId,
84-
timestamp,
85-
});
86-
}
87-
883
functionenable(){
89-
dc??=require('diagnostics_channel');
90-
Network??=require('inspector').Network;
91-
dc.subscribe('http.client.request.start',onClientRequestStart);
92-
dc.subscribe('http.client.request.error',onClientRequestError);
93-
dc.subscribe('http.client.response.finish',onClientResponseFinish);
4+
require('internal/inspector/network_http').enable();
5+
// TODO: add undici request/websocket tracking.
6+
// https://github.com/nodejs/node/issues/53946
947
}
958

969
functiondisable(){
97-
dc.unsubscribe('http.client.request.start',onClientRequestStart);
98-
dc.unsubscribe('http.client.request.error',onClientRequestError);
99-
dc.unsubscribe('http.client.response.finish',onClientResponseFinish);
10+
require('internal/inspector/network_http').disable();
11+
// TODO: add undici request/websocket tracking.
12+
// https://github.com/nodejs/node/issues/53946
10013
}
10114

10215
module.exports={

‎src/node_builtins.cc‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const{
119119
builtin_categories.cannot_be_required = std::set<std::string>{
120120
#if !HAVE_INSPECTOR
121121
"inspector", "inspector/promises", "internal/util/inspector",
122+
"internal/inspector/network", "internal/inspector/network_http",
123+
"internal/inspector_async_hook", "internal/inspector_network_tracking",
122124
#endif// !HAVE_INSPECTOR
123125

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

0 commit comments

Comments
(0)