Skip to content

Commit d1007fb

Browse files
cola119targos
authored andcommitted
inspector: provide detailed info to fix DevTools frontend errors
PR-URL: #54156 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent 337cd41 commit d1007fb

File tree

6 files changed

+199
-12
lines changed

6 files changed

+199
-12
lines changed

lib/internal/inspector_network_tracking.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
'use strict';
22

33
const {
4+
ArrayIsArray,
45
DateNow,
6+
ObjectEntries,
7+
String,
58
} = primordials;
69

710
let dc;
@@ -10,6 +13,25 @@ let Network;
1013
let requestId = 0;
1114
const getNextRequestId = () => `node-network-event-${++requestId}`;
1215

16+
// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>)
17+
const headerObjectToDictionary = (headers = {}) => {
18+
const dict = {};
19+
for (const { 0: key, 1: value } of ObjectEntries(headers)) {
20+
if (typeof value === 'string') {
21+
dict[key] = value;
22+
} else if (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+
else if (key.toLowerCase() === 'set-cookie') dict[key] = value.join('\n');
27+
else dict[key] = value.join(', ');
28+
} else {
29+
dict[key] = String(value);
30+
}
31+
}
32+
return dict;
33+
};
34+
1335
function onClientRequestStart({ request }) {
1436
const url = `${request.protocol}//${request.host}${request.path}`;
1537
const wallTime = DateNow();
@@ -22,18 +44,27 @@ function onClientRequestStart({ request }) {
2244
request: {
2345
url,
2446
method: request.method,
47+
headers: headerObjectToDictionary(request.getHeaders()),
2548
},
2649
});
2750
}
2851

29-
function onClientResponseFinish({ request }) {
52+
function onClientResponseFinish({ request, response }) {
3053
if (typeof request._inspectorRequestId !== 'string') {
3154
return;
3255
}
56+
const url = `${request.protocol}//${request.host}${request.path}`;
3357
const timestamp = DateNow() / 1000;
3458
Network.responseReceived({
3559
requestId: request._inspectorRequestId,
3660
timestamp,
61+
type: 'Other',
62+
response: {
63+
url,
64+
status: response.statusCode,
65+
statusText: response.statusMessage ?? '',
66+
headers: headerObjectToDictionary(response.headers),
67+
},
3768
});
3869
Network.loadingFinished({
3970
requestId: request._inspectorRequestId,

src/inspector/network_agent.cc

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,28 @@ namespace node {
55
namespace inspector {
66
namespace protocol {
77

8-
std::unique_ptr<Network::Request> Request(const String& url,
9-
const String& method) {
10-
return Network::Request::create().setUrl(url).setMethod(method).build();
8+
std::unique_ptr<Network::Request> createRequest(
9+
const String& url,
10+
const String& method,
11+
std::unique_ptr<Network::Headers> headers) {
12+
return Network::Request::create()
13+
.setUrl(url)
14+
.setMethod(method)
15+
.setHeaders(std::move(headers))
16+
.build();
17+
}
18+
19+
std::unique_ptr<Network::Response> createResponse(
20+
const String& url,
21+
int status,
22+
const String& statusText,
23+
std::unique_ptr<Network::Headers> headers) {
24+
return Network::Response::create()
25+
.setUrl(url)
26+
.setStatus(status)
27+
.setStatusText(statusText)
28+
.setHeaders(std::move(headers))
29+
.build();
1130
}
1231

1332
NetworkAgent::NetworkAgent(NetworkInspector* inspector)
@@ -55,8 +74,17 @@ void NetworkAgent::requestWillBeSent(
5574
String method;
5675
request->getString("method", &method);
5776

58-
frontend_->requestWillBeSent(
59-
request_id, Request(url, method), timestamp, wall_time);
77+
ErrorSupport errors;
78+
auto headers =
79+
Network::Headers::fromValue(request->getObject("headers"), &errors);
80+
if (errors.hasErrors()) {
81+
headers = std::make_unique<Network::Headers>(DictionaryValue::create());
82+
}
83+
84+
frontend_->requestWillBeSent(request_id,
85+
createRequest(url, method, std::move(headers)),
86+
timestamp,
87+
wall_time);
6088
}
6189

6290
void NetworkAgent::responseReceived(
@@ -65,8 +93,28 @@ void NetworkAgent::responseReceived(
6593
params->getString("requestId", &request_id);
6694
double timestamp;
6795
params->getDouble("timestamp", &timestamp);
96+
String type;
97+
params->getString("type", &type);
98+
auto response = params->getObject("response");
99+
String url;
100+
response->getString("url", &url);
101+
int status;
102+
response->getInteger("status", &status);
103+
String statusText;
104+
response->getString("statusText", &statusText);
105+
106+
ErrorSupport errors;
107+
auto headers =
108+
Network::Headers::fromValue(response->getObject("headers"), &errors);
109+
if (errors.hasErrors()) {
110+
headers = std::make_unique<Network::Headers>(DictionaryValue::create());
111+
}
68112

69-
frontend_->responseReceived(request_id, timestamp);
113+
frontend_->responseReceived(
114+
request_id,
115+
timestamp,
116+
type,
117+
createResponse(url, status, statusText, std::move(headers)));
70118
}
71119

72120
void NetworkAgent::loadingFinished(

src/inspector/network_agent.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ class NetworkInspector;
1212

1313
namespace protocol {
1414

15-
std::unique_ptr<Network::Request> Request(const String& url,
16-
const String& method);
17-
1815
class NetworkAgent : public Network::Backend {
1916
public:
2017
explicit NetworkAgent(NetworkInspector* inspector);

src/inspector/node_protocol.pdl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,28 @@ experimental domain NodeWorker
101101
# Partial support for Network domain of ChromeDevTools Protocol.
102102
# https://chromedevtools.github.io/devtools-protocol/tot/Network
103103
experimental domain Network
104+
# Resource type as it was perceived by the rendering engine.
105+
type ResourceType extends string
106+
enum
107+
Document
108+
Stylesheet
109+
Image
110+
Media
111+
Font
112+
Script
113+
TextTrack
114+
XHR
115+
Fetch
116+
Prefetch
117+
EventSource
118+
WebSocket
119+
Manifest
120+
SignedExchange
121+
Ping
122+
CSPViolationReport
123+
Preflight
124+
Other
125+
104126
# Unique request identifier.
105127
type RequestId extends string
106128

@@ -115,6 +137,18 @@ experimental domain Network
115137
properties
116138
string url
117139
string method
140+
Headers headers
141+
142+
# HTTP response data.
143+
type Response extends object
144+
properties
145+
string url
146+
integer status
147+
string statusText
148+
Headers headers
149+
150+
# Request / response headers as keys / values of JSON object.
151+
type Headers extends object
118152

119153
# Disables network tracking, prevents network events from being sent to the client.
120154
command disable
@@ -141,6 +175,10 @@ experimental domain Network
141175
RequestId requestId
142176
# Timestamp.
143177
MonotonicTime timestamp
178+
# Resource type.
179+
ResourceType type
180+
# Response data.
181+
Response response
144182

145183
event loadingFinished
146184
parameters

test/parallel/test-inspector-emit-protocol-event.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,17 @@ const EXPECTED_EVENTS = {
1515
requestId: 'request-id-1',
1616
request: {
1717
url: 'https://nodejs.org/en',
18-
method: 'GET'
18+
method: 'GET',
19+
},
20+
timestamp: 1000,
21+
wallTime: 1000,
22+
},
23+
expected: {
24+
requestId: 'request-id-1',
25+
request: {
26+
url: 'https://nodejs.org/en',
27+
method: 'GET',
28+
headers: {} // Headers should be an empty object if not provided.
1929
},
2030
timestamp: 1000,
2131
wallTime: 1000,
@@ -26,6 +36,23 @@ const EXPECTED_EVENTS = {
2636
params: {
2737
requestId: 'request-id-1',
2838
timestamp: 1000,
39+
type: 'Other',
40+
response: {
41+
url: 'https://nodejs.org/en',
42+
status: 200,
43+
headers: { host: 'nodejs.org' }
44+
}
45+
},
46+
expected: {
47+
requestId: 'request-id-1',
48+
timestamp: 1000,
49+
type: 'Other',
50+
response: {
51+
url: 'https://nodejs.org/en',
52+
status: 200,
53+
statusText: '', // Status text should be an empty string if not provided.
54+
headers: { host: 'nodejs.org' }
55+
}
2956
}
3057
},
3158
{
@@ -68,7 +95,7 @@ const runAsyncTest = async () => {
6895
for (const [domain, events] of Object.entries(EXPECTED_EVENTS)) {
6996
for (const event of events) {
7097
session.on(`${domain}.${event.name}`, common.mustCall(({ params }) => {
71-
assert.deepStrictEqual(params, event.params);
98+
assert.deepStrictEqual(params, event.expected ?? event.params);
7299
}));
73100
inspector[domain][event.name](event.params);
74101
}

test/parallel/test-inspector-network-domain.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,25 @@ const inspector = require('node:inspector/promises');
1313
const session = new inspector.Session();
1414
session.connect();
1515

16+
const requestHeaders = {
17+
'accept-language': 'en-US',
18+
'Cookie': ['k1=v1', 'k2=v2'],
19+
'age': 1000,
20+
'x-header1': ['value1', 'value2']
21+
};
22+
23+
const setResponseHeaders = (res) => {
24+
res.setHeader('server', 'node');
25+
res.setHeader('etag', 12345);
26+
res.setHeader('Set-Cookie', ['key1=value1', 'key2=value2']);
27+
res.setHeader('x-header2', ['value1', 'value2']);
28+
};
29+
1630
const httpServer = http.createServer((req, res) => {
1731
const path = req.url;
1832
switch (path) {
1933
case '/hello-world':
34+
setResponseHeaders(res);
2035
res.writeHead(200);
2136
res.end('hello world\n');
2237
break;
@@ -32,6 +47,7 @@ const httpsServer = https.createServer({
3247
const path = req.url;
3348
switch (path) {
3449
case '/hello-world':
50+
setResponseHeaders(res);
3551
res.writeHead(200);
3652
res.end('hello world\n');
3753
break;
@@ -52,12 +68,26 @@ const testHttpGet = () => new Promise((resolve, reject) => {
5268
assert.ok(params.requestId.startsWith('node-network-event-'));
5369
assert.strictEqual(params.request.url, 'http://127.0.0.1/hello-world');
5470
assert.strictEqual(params.request.method, 'GET');
71+
assert.strictEqual(typeof params.request.headers, 'object');
72+
assert.strictEqual(params.request.headers['accept-language'], 'en-US');
73+
assert.strictEqual(params.request.headers.cookie, 'k1=v1; k2=v2');
74+
assert.strictEqual(params.request.headers.age, '1000');
75+
assert.strictEqual(params.request.headers['x-header1'], 'value1, value2');
5576
assert.strictEqual(typeof params.timestamp, 'number');
5677
assert.strictEqual(typeof params.wallTime, 'number');
5778
}));
5879
session.on('Network.responseReceived', common.mustCall(({ params }) => {
5980
assert.ok(params.requestId.startsWith('node-network-event-'));
6081
assert.strictEqual(typeof params.timestamp, 'number');
82+
assert.strictEqual(params.type, 'Other');
83+
assert.strictEqual(params.response.status, 200);
84+
assert.strictEqual(params.response.statusText, 'OK');
85+
assert.strictEqual(params.response.url, 'http://127.0.0.1/hello-world');
86+
assert.strictEqual(typeof params.response.headers, 'object');
87+
assert.strictEqual(params.response.headers.server, 'node');
88+
assert.strictEqual(params.response.headers.etag, '12345');
89+
assert.strictEqual(params.response.headers['set-cookie'], 'key1=value1\nkey2=value2');
90+
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
6191
}));
6292
session.on('Network.loadingFinished', common.mustCall(({ params }) => {
6393
assert.ok(params.requestId.startsWith('node-network-event-'));
@@ -69,6 +99,7 @@ const testHttpGet = () => new Promise((resolve, reject) => {
6999
host: '127.0.0.1',
70100
port: httpServer.address().port,
71101
path: '/hello-world',
102+
headers: requestHeaders
72103
}, common.mustCall());
73104
});
74105

@@ -77,12 +108,26 @@ const testHttpsGet = () => new Promise((resolve, reject) => {
77108
assert.ok(params.requestId.startsWith('node-network-event-'));
78109
assert.strictEqual(params.request.url, 'https://127.0.0.1/hello-world');
79110
assert.strictEqual(params.request.method, 'GET');
111+
assert.strictEqual(typeof params.request.headers, 'object');
112+
assert.strictEqual(params.request.headers['accept-language'], 'en-US');
113+
assert.strictEqual(params.request.headers.cookie, 'k1=v1; k2=v2');
114+
assert.strictEqual(params.request.headers.age, '1000');
115+
assert.strictEqual(params.request.headers['x-header1'], 'value1, value2');
80116
assert.strictEqual(typeof params.timestamp, 'number');
81117
assert.strictEqual(typeof params.wallTime, 'number');
82118
}));
83119
session.on('Network.responseReceived', common.mustCall(({ params }) => {
84120
assert.ok(params.requestId.startsWith('node-network-event-'));
85121
assert.strictEqual(typeof params.timestamp, 'number');
122+
assert.strictEqual(params.type, 'Other');
123+
assert.strictEqual(params.response.status, 200);
124+
assert.strictEqual(params.response.statusText, 'OK');
125+
assert.strictEqual(params.response.url, 'https://127.0.0.1/hello-world');
126+
assert.strictEqual(typeof params.response.headers, 'object');
127+
assert.strictEqual(params.response.headers.server, 'node');
128+
assert.strictEqual(params.response.headers.etag, '12345');
129+
assert.strictEqual(params.response.headers['set-cookie'], 'key1=value1\nkey2=value2');
130+
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
86131
}));
87132
session.on('Network.loadingFinished', common.mustCall(({ params }) => {
88133
assert.ok(params.requestId.startsWith('node-network-event-'));
@@ -95,6 +140,7 @@ const testHttpsGet = () => new Promise((resolve, reject) => {
95140
port: httpsServer.address().port,
96141
path: '/hello-world',
97142
rejectUnauthorized: false,
143+
headers: requestHeaders,
98144
}, common.mustCall());
99145
});
100146

0 commit comments

Comments
 (0)