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 <legendecas@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy