mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
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: https://github.com/nodejs/node/pull/56372 Refs: https://github.com/nodejs/node/issues/53946 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Kohei Ueno <kohei.ueno119@gmail.com>
This commit is contained in:
31
lib/internal/inspector/network.js
Normal file
31
lib/internal/inspector/network.js
Normal file
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
NumberMAX_SAFE_INTEGER,
|
||||
Symbol,
|
||||
} = primordials;
|
||||
|
||||
const { now } = require('internal/perf/utils');
|
||||
const kInspectorRequestId = Symbol('kInspectorRequestId');
|
||||
|
||||
/**
|
||||
* Return a monotonically increasing time in seconds since an arbitrary point in the past.
|
||||
* @returns {number}
|
||||
*/
|
||||
function getMonotonicTime() {
|
||||
return now() / 1000;
|
||||
}
|
||||
|
||||
let requestId = 0;
|
||||
function getNextRequestId() {
|
||||
if (requestId === NumberMAX_SAFE_INTEGER) {
|
||||
requestId = 0;
|
||||
}
|
||||
return `node-network-event-${++requestId}`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
kInspectorRequestId,
|
||||
getMonotonicTime,
|
||||
getNextRequestId,
|
||||
};
|
||||
132
lib/internal/inspector/network_http.js
Normal file
132
lib/internal/inspector/network_http.js
Normal file
@@ -0,0 +1,132 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
ArrayIsArray,
|
||||
DateNow,
|
||||
ObjectEntries,
|
||||
String,
|
||||
Symbol,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
kInspectorRequestId,
|
||||
getMonotonicTime,
|
||||
getNextRequestId,
|
||||
} = require('internal/inspector/network');
|
||||
const dc = require('diagnostics_channel');
|
||||
const { Network } = require('inspector');
|
||||
|
||||
const kResourceType = 'Other';
|
||||
const kRequestUrl = Symbol('kRequestUrl');
|
||||
|
||||
// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>)
|
||||
const convertHeaderObject = (headers = {}) => {
|
||||
// The 'host' header that contains the host and port of the URL.
|
||||
let host;
|
||||
const dict = {};
|
||||
for (const { 0: key, 1: value } of ObjectEntries(headers)) {
|
||||
if (key.toLowerCase() === 'host') {
|
||||
host = value;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
dict[key] = value;
|
||||
} else if (ArrayIsArray(value)) {
|
||||
if (key.toLowerCase() === 'cookie') dict[key] = value.join('; ');
|
||||
// ChromeDevTools frontend treats 'set-cookie' as a special case
|
||||
// https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368
|
||||
else if (key.toLowerCase() === 'set-cookie') dict[key] = value.join('\n');
|
||||
else dict[key] = value.join(', ');
|
||||
} else {
|
||||
dict[key] = String(value);
|
||||
}
|
||||
}
|
||||
return [host, dict];
|
||||
};
|
||||
|
||||
/**
|
||||
* When a client request starts, emit Network.requestWillBeSent event.
|
||||
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent
|
||||
* @param {{ request: import('http').ClientRequest }} event
|
||||
*/
|
||||
function onClientRequestStart({ request }) {
|
||||
request[kInspectorRequestId] = getNextRequestId();
|
||||
|
||||
const { 0: host, 1: headers } = convertHeaderObject(request.getHeaders());
|
||||
const url = `${request.protocol}//${host}${request.path}`;
|
||||
request[kRequestUrl] = url;
|
||||
|
||||
Network.requestWillBeSent({
|
||||
requestId: request[kInspectorRequestId],
|
||||
timestamp: getMonotonicTime(),
|
||||
wallTime: DateNow(),
|
||||
request: {
|
||||
url,
|
||||
method: request.method,
|
||||
headers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When a client request errors, emit Network.loadingFailed event.
|
||||
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFailed
|
||||
* @param {{ request: import('http').ClientRequest, error: any }} event
|
||||
*/
|
||||
function onClientRequestError({ request, error }) {
|
||||
if (typeof request[kInspectorRequestId] !== 'string') {
|
||||
return;
|
||||
}
|
||||
Network.loadingFailed({
|
||||
requestId: request[kInspectorRequestId],
|
||||
timestamp: getMonotonicTime(),
|
||||
type: kResourceType,
|
||||
errorText: error.message,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When response headers are received, emit Network.responseReceived event.
|
||||
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-responseReceived
|
||||
* @param {{ request: import('http').ClientRequest, error: any }} event
|
||||
*/
|
||||
function onClientResponseFinish({ request, response }) {
|
||||
if (typeof request[kInspectorRequestId] !== 'string') {
|
||||
return;
|
||||
}
|
||||
Network.responseReceived({
|
||||
requestId: request[kInspectorRequestId],
|
||||
timestamp: getMonotonicTime(),
|
||||
type: kResourceType,
|
||||
response: {
|
||||
url: request[kRequestUrl],
|
||||
status: response.statusCode,
|
||||
statusText: response.statusMessage ?? '',
|
||||
headers: convertHeaderObject(response.headers)[1],
|
||||
},
|
||||
});
|
||||
|
||||
// Wait until the response body is consumed by user code.
|
||||
response.once('end', () => {
|
||||
Network.loadingFinished({
|
||||
requestId: request[kInspectorRequestId],
|
||||
timestamp: getMonotonicTime(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function enable() {
|
||||
dc.subscribe('http.client.request.start', onClientRequestStart);
|
||||
dc.subscribe('http.client.request.error', onClientRequestError);
|
||||
dc.subscribe('http.client.response.finish', onClientResponseFinish);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
dc.unsubscribe('http.client.request.start', onClientRequestStart);
|
||||
dc.unsubscribe('http.client.request.error', onClientRequestError);
|
||||
dc.unsubscribe('http.client.response.finish', onClientResponseFinish);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
enable,
|
||||
disable,
|
||||
};
|
||||
@@ -1,102 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
ArrayIsArray,
|
||||
DateNow,
|
||||
ObjectEntries,
|
||||
String,
|
||||
} = primordials;
|
||||
|
||||
let dc;
|
||||
let Network;
|
||||
|
||||
let requestId = 0;
|
||||
const getNextRequestId = () => `node-network-event-${++requestId}`;
|
||||
|
||||
// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>)
|
||||
const headerObjectToDictionary = (headers = {}) => {
|
||||
const dict = {};
|
||||
for (const { 0: key, 1: value } of ObjectEntries(headers)) {
|
||||
if (typeof value === 'string') {
|
||||
dict[key] = value;
|
||||
} else if (ArrayIsArray(value)) {
|
||||
if (key.toLowerCase() === 'cookie') dict[key] = value.join('; ');
|
||||
// ChromeDevTools frontend treats 'set-cookie' as a special case
|
||||
// https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368
|
||||
else if (key.toLowerCase() === 'set-cookie') dict[key] = value.join('\n');
|
||||
else dict[key] = value.join(', ');
|
||||
} else {
|
||||
dict[key] = String(value);
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
};
|
||||
|
||||
function onClientRequestStart({ request }) {
|
||||
const url = `${request.protocol}//${request.host}${request.path}`;
|
||||
const wallTime = DateNow();
|
||||
const timestamp = wallTime / 1000;
|
||||
request._inspectorRequestId = getNextRequestId();
|
||||
Network.requestWillBeSent({
|
||||
requestId: request._inspectorRequestId,
|
||||
timestamp,
|
||||
wallTime,
|
||||
request: {
|
||||
url,
|
||||
method: request.method,
|
||||
headers: headerObjectToDictionary(request.getHeaders()),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function onClientRequestError({ request, error }) {
|
||||
if (typeof request._inspectorRequestId !== 'string') {
|
||||
return;
|
||||
}
|
||||
const timestamp = DateNow() / 1000;
|
||||
Network.loadingFailed({
|
||||
requestId: request._inspectorRequestId,
|
||||
timestamp,
|
||||
type: 'Other',
|
||||
errorText: error.message,
|
||||
});
|
||||
}
|
||||
|
||||
function onClientResponseFinish({ request, response }) {
|
||||
if (typeof request._inspectorRequestId !== 'string') {
|
||||
return;
|
||||
}
|
||||
const url = `${request.protocol}//${request.host}${request.path}`;
|
||||
const timestamp = DateNow() / 1000;
|
||||
Network.responseReceived({
|
||||
requestId: request._inspectorRequestId,
|
||||
timestamp,
|
||||
type: 'Other',
|
||||
response: {
|
||||
url,
|
||||
status: response.statusCode,
|
||||
statusText: response.statusMessage ?? '',
|
||||
headers: headerObjectToDictionary(response.headers),
|
||||
},
|
||||
});
|
||||
Network.loadingFinished({
|
||||
requestId: request._inspectorRequestId,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
function enable() {
|
||||
dc ??= require('diagnostics_channel');
|
||||
Network ??= require('inspector').Network;
|
||||
dc.subscribe('http.client.request.start', onClientRequestStart);
|
||||
dc.subscribe('http.client.request.error', onClientRequestError);
|
||||
dc.subscribe('http.client.response.finish', onClientResponseFinish);
|
||||
require('internal/inspector/network_http').enable();
|
||||
// TODO: add undici request/websocket tracking.
|
||||
// https://github.com/nodejs/node/issues/53946
|
||||
}
|
||||
|
||||
function disable() {
|
||||
dc.unsubscribe('http.client.request.start', onClientRequestStart);
|
||||
dc.unsubscribe('http.client.request.error', onClientRequestError);
|
||||
dc.unsubscribe('http.client.response.finish', onClientResponseFinish);
|
||||
require('internal/inspector/network_http').disable();
|
||||
// TODO: add undici request/websocket tracking.
|
||||
// https://github.com/nodejs/node/issues/53946
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -119,6 +119,8 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
|
||||
builtin_categories.cannot_be_required = std::set<std::string> {
|
||||
#if !HAVE_INSPECTOR
|
||||
"inspector", "inspector/promises", "internal/util/inspector",
|
||||
"internal/inspector/network", "internal/inspector/network_http",
|
||||
"internal/inspector_async_hook", "internal/inspector_network_tracking",
|
||||
#endif // !HAVE_INSPECTOR
|
||||
|
||||
#if !NODE_USE_V8_PLATFORM || !defined(NODE_HAVE_I18N_SUPPORT)
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
// Flags: --inspect=0 --experimental-network-inspection
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
common.skipIfInspectorDisabled();
|
||||
|
||||
const assert = require('node:assert');
|
||||
const { addresses } = require('../common/internet');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const http = require('node:http');
|
||||
const https = require('node:https');
|
||||
const inspector = require('node:inspector/promises');
|
||||
|
||||
const session = new inspector.Session();
|
||||
session.connect();
|
||||
|
||||
const requestHeaders = {
|
||||
'accept-language': 'en-US',
|
||||
'Cookie': ['k1=v1', 'k2=v2'],
|
||||
'age': 1000,
|
||||
'x-header1': ['value1', 'value2']
|
||||
};
|
||||
|
||||
const setResponseHeaders = (res) => {
|
||||
res.setHeader('server', 'node');
|
||||
res.setHeader('etag', 12345);
|
||||
res.setHeader('Set-Cookie', ['key1=value1', 'key2=value2']);
|
||||
res.setHeader('x-header2', ['value1', 'value2']);
|
||||
};
|
||||
|
||||
const httpServer = http.createServer((req, res) => {
|
||||
const path = req.url;
|
||||
switch (path) {
|
||||
case '/hello-world':
|
||||
setResponseHeaders(res);
|
||||
res.writeHead(200);
|
||||
res.end('hello world\n');
|
||||
break;
|
||||
default:
|
||||
assert(false, `Unexpected path: ${path}`);
|
||||
}
|
||||
});
|
||||
|
||||
const httpsServer = https.createServer({
|
||||
key: fixtures.readKey('agent1-key.pem'),
|
||||
cert: fixtures.readKey('agent1-cert.pem')
|
||||
}, (req, res) => {
|
||||
const path = req.url;
|
||||
switch (path) {
|
||||
case '/hello-world':
|
||||
setResponseHeaders(res);
|
||||
res.writeHead(200);
|
||||
res.end('hello world\n');
|
||||
break;
|
||||
default:
|
||||
assert(false, `Unexpected path: ${path}`);
|
||||
}
|
||||
});
|
||||
|
||||
const terminate = () => {
|
||||
session.disconnect();
|
||||
httpServer.close();
|
||||
httpsServer.close();
|
||||
inspector.close();
|
||||
};
|
||||
|
||||
const testHttpGet = () => new Promise((resolve, reject) => {
|
||||
session.on('Network.requestWillBeSent', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(params.request.url, 'http://127.0.0.1/hello-world');
|
||||
assert.strictEqual(params.request.method, 'GET');
|
||||
assert.strictEqual(typeof params.request.headers, 'object');
|
||||
assert.strictEqual(params.request.headers['accept-language'], 'en-US');
|
||||
assert.strictEqual(params.request.headers.cookie, 'k1=v1; k2=v2');
|
||||
assert.strictEqual(params.request.headers.age, '1000');
|
||||
assert.strictEqual(params.request.headers['x-header1'], 'value1, value2');
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(typeof params.wallTime, 'number');
|
||||
}));
|
||||
session.on('Network.responseReceived', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(params.type, 'Other');
|
||||
assert.strictEqual(params.response.status, 200);
|
||||
assert.strictEqual(params.response.statusText, 'OK');
|
||||
assert.strictEqual(params.response.url, 'http://127.0.0.1/hello-world');
|
||||
assert.strictEqual(typeof params.response.headers, 'object');
|
||||
assert.strictEqual(params.response.headers.server, 'node');
|
||||
assert.strictEqual(params.response.headers.etag, '12345');
|
||||
assert.strictEqual(params.response.headers['set-cookie'], 'key1=value1\nkey2=value2');
|
||||
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
|
||||
}));
|
||||
session.on('Network.loadingFinished', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
resolve();
|
||||
}));
|
||||
|
||||
http.get({
|
||||
host: '127.0.0.1',
|
||||
port: httpServer.address().port,
|
||||
path: '/hello-world',
|
||||
headers: requestHeaders
|
||||
}, common.mustCall());
|
||||
});
|
||||
|
||||
const testHttpsGet = () => new Promise((resolve, reject) => {
|
||||
session.on('Network.requestWillBeSent', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(params.request.url, 'https://127.0.0.1/hello-world');
|
||||
assert.strictEqual(params.request.method, 'GET');
|
||||
assert.strictEqual(typeof params.request.headers, 'object');
|
||||
assert.strictEqual(params.request.headers['accept-language'], 'en-US');
|
||||
assert.strictEqual(params.request.headers.cookie, 'k1=v1; k2=v2');
|
||||
assert.strictEqual(params.request.headers.age, '1000');
|
||||
assert.strictEqual(params.request.headers['x-header1'], 'value1, value2');
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(typeof params.wallTime, 'number');
|
||||
}));
|
||||
session.on('Network.responseReceived', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(params.type, 'Other');
|
||||
assert.strictEqual(params.response.status, 200);
|
||||
assert.strictEqual(params.response.statusText, 'OK');
|
||||
assert.strictEqual(params.response.url, 'https://127.0.0.1/hello-world');
|
||||
assert.strictEqual(typeof params.response.headers, 'object');
|
||||
assert.strictEqual(params.response.headers.server, 'node');
|
||||
assert.strictEqual(params.response.headers.etag, '12345');
|
||||
assert.strictEqual(params.response.headers['set-cookie'], 'key1=value1\nkey2=value2');
|
||||
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
|
||||
}));
|
||||
session.on('Network.loadingFinished', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
resolve();
|
||||
}));
|
||||
|
||||
https.get({
|
||||
host: '127.0.0.1',
|
||||
port: httpsServer.address().port,
|
||||
path: '/hello-world',
|
||||
rejectUnauthorized: false,
|
||||
headers: requestHeaders,
|
||||
}, common.mustCall());
|
||||
});
|
||||
|
||||
const testHttpError = () => new Promise((resolve, reject) => {
|
||||
session.on('Network.requestWillBeSent', common.mustCall());
|
||||
session.on('Network.loadingFailed', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(params.type, 'Other');
|
||||
assert.strictEqual(typeof params.errorText, 'string');
|
||||
resolve();
|
||||
}));
|
||||
session.on('Network.responseReceived', common.mustNotCall());
|
||||
session.on('Network.loadingFinished', common.mustNotCall());
|
||||
|
||||
http.get({
|
||||
host: addresses.INVALID_HOST,
|
||||
}, common.mustNotCall()).on('error', common.mustCall());
|
||||
});
|
||||
|
||||
|
||||
const testHttpsError = () => new Promise((resolve, reject) => {
|
||||
session.on('Network.requestWillBeSent', common.mustCall());
|
||||
session.on('Network.loadingFailed', common.mustCall(({ params }) => {
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(params.type, 'Other');
|
||||
assert.strictEqual(typeof params.errorText, 'string');
|
||||
resolve();
|
||||
}));
|
||||
session.on('Network.responseReceived', common.mustNotCall());
|
||||
session.on('Network.loadingFinished', common.mustNotCall());
|
||||
|
||||
https.get({
|
||||
host: addresses.INVALID_HOST,
|
||||
}, common.mustNotCall()).on('error', common.mustCall());
|
||||
});
|
||||
|
||||
const testNetworkInspection = async () => {
|
||||
await testHttpGet();
|
||||
session.removeAllListeners();
|
||||
await testHttpsGet();
|
||||
session.removeAllListeners();
|
||||
await testHttpError();
|
||||
session.removeAllListeners();
|
||||
await testHttpsError();
|
||||
session.removeAllListeners();
|
||||
};
|
||||
|
||||
httpServer.listen(0, () => {
|
||||
httpsServer.listen(0, async () => {
|
||||
try {
|
||||
await session.post('Network.enable');
|
||||
await testNetworkInspection();
|
||||
await session.post('Network.disable');
|
||||
} catch (e) {
|
||||
assert.fail(e);
|
||||
} finally {
|
||||
terminate();
|
||||
}
|
||||
});
|
||||
});
|
||||
241
test/parallel/test-inspector-network-http.js
Normal file
241
test/parallel/test-inspector-network-http.js
Normal file
@@ -0,0 +1,241 @@
|
||||
// Flags: --inspect=0 --experimental-network-inspection
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
common.skipIfInspectorDisabled();
|
||||
|
||||
const assert = require('node:assert');
|
||||
const { once } = require('node:events');
|
||||
const { addresses } = require('../common/internet');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const http = require('node:http');
|
||||
const https = require('node:https');
|
||||
const inspector = require('node:inspector/promises');
|
||||
|
||||
const session = new inspector.Session();
|
||||
session.connect();
|
||||
|
||||
const requestHeaders = {
|
||||
'accept-language': 'en-US',
|
||||
'Cookie': ['k1=v1', 'k2=v2'],
|
||||
'age': 1000,
|
||||
'x-header1': ['value1', 'value2']
|
||||
};
|
||||
|
||||
const setResponseHeaders = (res) => {
|
||||
res.setHeader('server', 'node');
|
||||
res.setHeader('etag', 12345);
|
||||
res.setHeader('Set-Cookie', ['key1=value1', 'key2=value2']);
|
||||
res.setHeader('x-header2', ['value1', 'value2']);
|
||||
};
|
||||
|
||||
const kTimeout = 1000;
|
||||
const kDelta = 200;
|
||||
|
||||
const handleRequest = (req, res) => {
|
||||
const path = req.url;
|
||||
switch (path) {
|
||||
case '/hello-world':
|
||||
setResponseHeaders(res);
|
||||
res.writeHead(200);
|
||||
// Ensure the header is sent.
|
||||
res.write('\n');
|
||||
|
||||
setTimeout(() => {
|
||||
res.end('hello world\n');
|
||||
}, kTimeout);
|
||||
break;
|
||||
default:
|
||||
assert(false, `Unexpected path: ${path}`);
|
||||
}
|
||||
};
|
||||
|
||||
const httpServer = http.createServer(handleRequest);
|
||||
|
||||
const httpsServer = https.createServer({
|
||||
key: fixtures.readKey('agent1-key.pem'),
|
||||
cert: fixtures.readKey('agent1-cert.pem')
|
||||
}, handleRequest);
|
||||
|
||||
const terminate = () => {
|
||||
session.disconnect();
|
||||
httpServer.close();
|
||||
httpsServer.close();
|
||||
inspector.close();
|
||||
};
|
||||
|
||||
function verifyRequestWillBeSent({ method, params }, expect) {
|
||||
assert.strictEqual(method, 'Network.requestWillBeSent');
|
||||
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(params.request.url, expect.url);
|
||||
assert.strictEqual(params.request.method, 'GET');
|
||||
assert.strictEqual(typeof params.request.headers, 'object');
|
||||
assert.strictEqual(params.request.headers['accept-language'], 'en-US');
|
||||
assert.strictEqual(params.request.headers.cookie, 'k1=v1; k2=v2');
|
||||
assert.strictEqual(params.request.headers.age, '1000');
|
||||
assert.strictEqual(params.request.headers['x-header1'], 'value1, value2');
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(typeof params.wallTime, 'number');
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function verifyResponseReceived({ method, params }, expect) {
|
||||
assert.strictEqual(method, 'Network.responseReceived');
|
||||
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(params.type, 'Other');
|
||||
assert.strictEqual(params.response.status, 200);
|
||||
assert.strictEqual(params.response.statusText, 'OK');
|
||||
assert.strictEqual(params.response.url, expect.url);
|
||||
assert.strictEqual(typeof params.response.headers, 'object');
|
||||
assert.strictEqual(params.response.headers.server, 'node');
|
||||
assert.strictEqual(params.response.headers.etag, '12345');
|
||||
assert.strictEqual(params.response.headers['set-cookie'], 'key1=value1\nkey2=value2');
|
||||
assert.strictEqual(params.response.headers['x-header2'], 'value1, value2');
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function verifyLoadingFinished({ method, params }) {
|
||||
assert.strictEqual(method, 'Network.loadingFinished');
|
||||
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
return params;
|
||||
}
|
||||
|
||||
function verifyLoadingFailed({ method, params }) {
|
||||
assert.strictEqual(method, 'Network.loadingFailed');
|
||||
|
||||
assert.ok(params.requestId.startsWith('node-network-event-'));
|
||||
assert.strictEqual(typeof params.timestamp, 'number');
|
||||
assert.strictEqual(params.type, 'Other');
|
||||
assert.strictEqual(typeof params.errorText, 'string');
|
||||
}
|
||||
|
||||
async function testHttpGet() {
|
||||
const url = `http://127.0.0.1:${httpServer.address().port}/hello-world`;
|
||||
const requestWillBeSentFuture = once(session, 'Network.requestWillBeSent')
|
||||
.then(([event]) => verifyRequestWillBeSent(event, { url }));
|
||||
|
||||
const responseReceivedFuture = once(session, 'Network.responseReceived')
|
||||
.then(([event]) => verifyResponseReceived(event, { url }));
|
||||
|
||||
const loadingFinishedFuture = once(session, 'Network.loadingFinished')
|
||||
.then(([event]) => verifyLoadingFinished(event));
|
||||
|
||||
http.get({
|
||||
host: '127.0.0.1',
|
||||
port: httpServer.address().port,
|
||||
path: '/hello-world',
|
||||
headers: requestHeaders
|
||||
}, common.mustCall((res) => {
|
||||
// Dump the response.
|
||||
res.on('data', () => {});
|
||||
res.on('end', () => {});
|
||||
}));
|
||||
|
||||
await requestWillBeSentFuture;
|
||||
const responseReceived = await responseReceivedFuture;
|
||||
const loadingFinished = await loadingFinishedFuture;
|
||||
|
||||
const delta = (loadingFinished.timestamp - responseReceived.timestamp) * 1000;
|
||||
assert.ok(delta > kDelta);
|
||||
}
|
||||
|
||||
async function testHttpsGet() {
|
||||
const url = `https://127.0.0.1:${httpsServer.address().port}/hello-world`;
|
||||
const requestWillBeSentFuture = once(session, 'Network.requestWillBeSent')
|
||||
.then(([event]) => verifyRequestWillBeSent(event, { url }));
|
||||
|
||||
const responseReceivedFuture = once(session, 'Network.responseReceived')
|
||||
.then(([event]) => verifyResponseReceived(event, { url }));
|
||||
|
||||
const loadingFinishedFuture = once(session, 'Network.loadingFinished')
|
||||
.then(([event]) => verifyLoadingFinished(event));
|
||||
|
||||
https.get({
|
||||
host: '127.0.0.1',
|
||||
port: httpsServer.address().port,
|
||||
path: '/hello-world',
|
||||
rejectUnauthorized: false,
|
||||
headers: requestHeaders,
|
||||
}, common.mustCall((res) => {
|
||||
// Dump the response.
|
||||
res.on('data', () => {});
|
||||
res.on('end', () => {});
|
||||
}));
|
||||
|
||||
await requestWillBeSentFuture;
|
||||
const responseReceived = await responseReceivedFuture;
|
||||
const loadingFinished = await loadingFinishedFuture;
|
||||
|
||||
const delta = (loadingFinished.timestamp - responseReceived.timestamp) * 1000;
|
||||
assert.ok(delta > kDelta);
|
||||
}
|
||||
|
||||
async function testHttpError() {
|
||||
const url = `http://${addresses.INVALID_HOST}/`;
|
||||
const requestWillBeSentFuture = once(session, 'Network.requestWillBeSent')
|
||||
.then(([event]) => verifyRequestWillBeSent(event, { url }));
|
||||
session.on('Network.responseReceived', common.mustNotCall());
|
||||
session.on('Network.loadingFinished', common.mustNotCall());
|
||||
|
||||
const loadingFailedFuture = once(session, 'Network.loadingFailed')
|
||||
.then(([event]) => verifyLoadingFailed(event));
|
||||
|
||||
http.get({
|
||||
host: addresses.INVALID_HOST,
|
||||
headers: requestHeaders,
|
||||
}, common.mustNotCall()).on('error', common.mustCall());
|
||||
|
||||
await requestWillBeSentFuture;
|
||||
await loadingFailedFuture;
|
||||
}
|
||||
|
||||
async function testHttpsError() {
|
||||
const url = `https://${addresses.INVALID_HOST}/`;
|
||||
const requestWillBeSentFuture = once(session, 'Network.requestWillBeSent')
|
||||
.then(([event]) => verifyRequestWillBeSent(event, { url }));
|
||||
session.on('Network.responseReceived', common.mustNotCall());
|
||||
session.on('Network.loadingFinished', common.mustNotCall());
|
||||
|
||||
const loadingFailedFuture = once(session, 'Network.loadingFailed')
|
||||
.then(([event]) => verifyLoadingFailed(event));
|
||||
|
||||
https.get({
|
||||
host: addresses.INVALID_HOST,
|
||||
headers: requestHeaders,
|
||||
}, common.mustNotCall()).on('error', common.mustCall());
|
||||
|
||||
await requestWillBeSentFuture;
|
||||
await loadingFailedFuture;
|
||||
}
|
||||
|
||||
const testNetworkInspection = async () => {
|
||||
await testHttpGet();
|
||||
session.removeAllListeners();
|
||||
await testHttpsGet();
|
||||
session.removeAllListeners();
|
||||
await testHttpError();
|
||||
session.removeAllListeners();
|
||||
await testHttpsError();
|
||||
session.removeAllListeners();
|
||||
};
|
||||
|
||||
httpServer.listen(0, () => {
|
||||
httpsServer.listen(0, async () => {
|
||||
try {
|
||||
await session.post('Network.enable');
|
||||
await testNetworkInspection();
|
||||
await session.post('Network.disable');
|
||||
} catch (e) {
|
||||
assert.fail(e);
|
||||
} finally {
|
||||
terminate();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user