Skip to content

Commit 79b852a

Browse files
islandryutargos
authored andcommitted
inspector: add mimeType and charset support to Network.Response
Refs: #53946 PR-URL: #58192 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent a994d3d commit 79b852a

File tree

8 files changed

+232
-2
lines changed

8 files changed

+232
-2
lines changed

lib/internal/inspector/network_http.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
} = require('internal/inspector/network');
1717
const dc = require('diagnostics_channel');
1818
const { Network } = require('inspector');
19+
const { MIMEType } = require('internal/mime');
1920

2021
const kRequestUrl = Symbol('kRequestUrl');
2122

@@ -93,6 +94,18 @@ function onClientResponseFinish({ request, response }) {
9394
if (typeof request[kInspectorRequestId] !== 'string') {
9495
return;
9596
}
97+
98+
let mimeType;
99+
let charset;
100+
try {
101+
const mimeTypeObj = new MIMEType(response.headers['content-type']);
102+
mimeType = mimeTypeObj.essence || '';
103+
charset = mimeTypeObj.params.get('charset') || '';
104+
} catch {
105+
mimeType = '';
106+
charset = '';
107+
}
108+
96109
Network.responseReceived({
97110
requestId: request[kInspectorRequestId],
98111
timestamp: getMonotonicTime(),
@@ -102,6 +115,8 @@ function onClientResponseFinish({ request, response }) {
102115
status: response.statusCode,
103116
statusText: response.statusMessage ?? '',
104117
headers: convertHeaderObject(response.headers)[1],
118+
mimeType,
119+
charset,
105120
},
106121
});
107122

lib/internal/inspector/network_undici.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayPrototypeFindIndex,
45
DateNow,
56
} = primordials;
67

@@ -12,6 +13,7 @@ const {
1213
} = require('internal/inspector/network');
1314
const dc = require('diagnostics_channel');
1415
const { Network } = require('inspector');
16+
const { MIMEType } = require('internal/mime');
1517

1618
// Convert an undici request headers array to a plain object (Map<string, string>)
1719
function requestHeadersArrayToDictionary(headers) {
@@ -91,6 +93,21 @@ function onClientResponseHeaders({ request, response }) {
9193
if (typeof request[kInspectorRequestId] !== 'string') {
9294
return;
9395
}
96+
97+
let mimeType;
98+
let charset;
99+
try {
100+
const contentTypeKeyIndex =
101+
ArrayPrototypeFindIndex(response.headers, (header) => header.toString().toLowerCase() === 'content-type');
102+
const contentType = contentTypeKeyIndex !== -1 ? response.headers[contentTypeKeyIndex + 1].toString() : '';
103+
const mimeTypeObj = new MIMEType(contentType);
104+
mimeType = mimeTypeObj.essence || '';
105+
charset = mimeTypeObj.params.get('charset') || '';
106+
} catch {
107+
mimeType = '';
108+
charset = '';
109+
}
110+
94111
const url = `${request.origin}${request.path}`;
95112
Network.responseReceived({
96113
requestId: request[kInspectorRequestId],
@@ -102,6 +119,8 @@ function onClientResponseHeaders({ request, response }) {
102119
status: response.statusCode,
103120
statusText: response.statusText,
104121
headers: responseHeadersArrayToDictionary(response.headers),
122+
mimeType,
123+
charset,
105124
},
106125
});
107126
}

src/inspector/network_agent.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,23 @@ std::unique_ptr<protocol::Network::Response> createResponseFromObject(
169169
return {};
170170
}
171171

172+
protocol::String mimeType;
173+
if (!ObjectGetProtocolString(context, response, "mimeType").To(&mimeType)) {
174+
mimeType = protocol::String("");
175+
}
176+
177+
protocol::String charset = protocol::String();
178+
if (!ObjectGetProtocolString(context, response, "charset").To(&charset)) {
179+
charset = protocol::String("");
180+
}
181+
172182
return protocol::Network::Response::create()
173183
.setUrl(url)
174184
.setStatus(status)
175185
.setStatusText(statusText)
176186
.setHeaders(std::move(headers))
187+
.setMimeType(mimeType)
188+
.setCharset(charset)
177189
.build();
178190
}
179191

src/inspector/node_protocol.pdl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ experimental domain Network
173173
integer status
174174
string statusText
175175
Headers headers
176+
string mimeType
177+
string charset
176178

177179
# Request / response headers as keys / values of JSON object.
178180
type Headers extends object

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ const EXPECTED_EVENTS = {
4242
url: 'https://nodejs.org/en',
4343
status: 200,
4444
statusText: '',
45-
headers: { host: 'nodejs.org' }
45+
headers: { host: 'nodejs.org' },
46+
mimeType: 'text/html',
47+
charset: 'utf-8'
4648
}
4749
},
4850
expected: {
@@ -53,7 +55,9 @@ const EXPECTED_EVENTS = {
5355
url: 'https://nodejs.org/en',
5456
status: 200,
5557
statusText: '',
56-
headers: { host: 'nodejs.org' }
58+
headers: { host: 'nodejs.org' },
59+
mimeType: 'text/html',
60+
charset: 'utf-8'
5761
}
5862
}
5963
},
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Flags: --inspect=0 --experimental-network-inspection
2+
'use strict';
3+
const common = require('../common');
4+
5+
common.skipIfInspectorDisabled();
6+
7+
const assert = require('node:assert');
8+
const http = require('node:http');
9+
const inspector = require('node:inspector/promises');
10+
11+
const testNetworkInspection = async (session, port, assert) => {
12+
let assertPromise = assert(session);
13+
fetch(`http://127.0.0.1:${port}/hello-world`).then(common.mustCall());
14+
await assertPromise;
15+
session.removeAllListeners();
16+
assertPromise = assert(session);
17+
new Promise((resolve, reject) => {
18+
const req = http.get(
19+
{
20+
host: '127.0.0.1',
21+
port,
22+
path: '/hello-world',
23+
},
24+
common.mustCall((res) => {
25+
res.on('data', () => {});
26+
res.on('end', () => {});
27+
resolve(res);
28+
})
29+
);
30+
req.on('error', reject);
31+
});
32+
await assertPromise;
33+
session.removeAllListeners();
34+
};
35+
36+
const test = (handleRequest, testSessionFunc) => new Promise((resolve) => {
37+
const session = new inspector.Session();
38+
session.connect();
39+
const httpServer = http.createServer(handleRequest);
40+
httpServer.listen(0, async () => {
41+
try {
42+
await session.post('Network.enable');
43+
await testNetworkInspection(
44+
session,
45+
httpServer.address().port,
46+
testSessionFunc
47+
);
48+
await session.post('Network.disable');
49+
} catch (err) {
50+
assert.fail(err);
51+
} finally {
52+
await session.disconnect();
53+
await httpServer.close();
54+
await inspector.close();
55+
resolve();
56+
}
57+
});
58+
});
59+
60+
(async () => {
61+
await test(
62+
(req, res) => {
63+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
64+
res.writeHead(200);
65+
res.end('hello world\n');
66+
},
67+
common.mustCall(
68+
(session) =>
69+
new Promise((resolve) => {
70+
session.on(
71+
'Network.responseReceived',
72+
common.mustCall(({ params }) => {
73+
assert.strictEqual(params.response.mimeType, 'text/plain');
74+
assert.strictEqual(params.response.charset, 'utf-8');
75+
})
76+
);
77+
session.on(
78+
'Network.loadingFinished',
79+
common.mustCall(({ params }) => {
80+
assert.ok(params.requestId.startsWith('node-network-event-'));
81+
assert.strictEqual(typeof params.timestamp, 'number');
82+
resolve();
83+
})
84+
);
85+
}),
86+
2
87+
)
88+
);
89+
90+
await test(
91+
(req, res) => {
92+
res.writeHead(200, {});
93+
res.end('hello world\n');
94+
},
95+
common.mustCall((session) =>
96+
new Promise((resolve) => {
97+
session.on(
98+
'Network.responseReceived',
99+
common.mustCall(({ params }) => {
100+
assert.strictEqual(params.response.mimeType, '');
101+
assert.strictEqual(params.response.charset, '');
102+
})
103+
);
104+
session.on(
105+
'Network.loadingFinished',
106+
common.mustCall(({ params }) => {
107+
assert.ok(params.requestId.startsWith('node-network-event-'));
108+
assert.strictEqual(typeof params.timestamp, 'number');
109+
resolve();
110+
})
111+
);
112+
}), 2
113+
)
114+
);
115+
116+
await test(
117+
(req, res) => {
118+
res.setHeader('Content-Type', 'invalid content-type');
119+
res.writeHead(200);
120+
res.end('hello world\n');
121+
},
122+
common.mustCall((session) =>
123+
new Promise((resolve) => {
124+
session.on(
125+
'Network.responseReceived',
126+
common.mustCall(({ params }) => {
127+
assert.strictEqual(params.response.mimeType, '');
128+
assert.strictEqual(params.response.charset, '');
129+
})
130+
);
131+
session.on(
132+
'Network.loadingFinished',
133+
common.mustCall(({ params }) => {
134+
assert.ok(params.requestId.startsWith('node-network-event-'));
135+
assert.strictEqual(typeof params.timestamp, 'number');
136+
resolve();
137+
})
138+
);
139+
}), 2
140+
)
141+
);
142+
143+
await test(
144+
(req, res) => {
145+
res.setHeader('Content-Type', 'text/plain');
146+
res.writeHead(200);
147+
res.end('hello world\n');
148+
},
149+
common.mustCall((session) =>
150+
new Promise((resolve) => {
151+
session.on(
152+
'Network.responseReceived',
153+
common.mustCall(({ params }) => {
154+
assert.strictEqual(params.response.mimeType, 'text/plain');
155+
assert.strictEqual(params.response.charset, '');
156+
})
157+
);
158+
session.on(
159+
'Network.loadingFinished',
160+
common.mustCall(({ params }) => {
161+
assert.ok(params.requestId.startsWith('node-network-event-'));
162+
assert.strictEqual(typeof params.timestamp, 'number');
163+
resolve();
164+
})
165+
);
166+
}), 2
167+
)
168+
);
169+
170+
})().then(common.mustCall());

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const setResponseHeaders = (res) => {
3636
res.setHeader('etag', 12345);
3737
res.setHeader('Set-Cookie', ['key1=value1', 'key2=value2']);
3838
res.setHeader('x-header2', ['value1', 'value2']);
39+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
3940
};
4041

4142
const handleRequest = (req, res) => {
@@ -101,6 +102,8 @@ const testHttpGet = () => new Promise((resolve, reject) => {
101102
assert.strictEqual(params.response.headers.etag, '12345');
102103
assert.strictEqual(params.response.headers['Set-Cookie'], 'key1=value1\nkey2=value2');
103104
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
105+
assert.strictEqual(params.response.mimeType, 'text/plain');
106+
assert.strictEqual(params.response.charset, 'utf-8');
104107
}));
105108
session.on('Network.loadingFinished', common.mustCall(({ params }) => {
106109
assert.ok(params.requestId.startsWith('node-network-event-'));
@@ -138,6 +141,8 @@ const testHttpsGet = () => new Promise((resolve, reject) => {
138141
assert.strictEqual(params.response.headers.etag, '12345');
139142
assert.strictEqual(params.response.headers['Set-Cookie'], 'key1=value1\nkey2=value2');
140143
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
144+
assert.strictEqual(params.response.mimeType, 'text/plain');
145+
assert.strictEqual(params.response.charset, 'utf-8');
141146
}));
142147
session.on('Network.loadingFinished', common.mustCall(({ params }) => {
143148
assert.ok(params.requestId.startsWith('node-network-event-'));

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const setResponseHeaders = (res) => {
2727
res.setHeader('etag', 12345);
2828
res.setHeader('Set-Cookie', ['key1=value1', 'key2=value2']);
2929
res.setHeader('x-header2', ['value1', 'value2']);
30+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
3031
};
3132

3233
const kTimeout = 1000;
@@ -106,6 +107,8 @@ function verifyResponseReceived({ method, params }, expect) {
106107
assert.strictEqual(params.response.headers.etag, '12345');
107108
assert.strictEqual(params.response.headers['set-cookie'], 'key1=value1\nkey2=value2');
108109
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
110+
assert.strictEqual(params.response.mimeType, 'text/plain');
111+
assert.strictEqual(params.response.charset, 'utf-8');
109112

110113
return params;
111114
}

0 commit comments

Comments
 (0)