inspector: add Network.Initiator in inspector protocol

Add initiator stack trace in inspector network events, reflecting
the location where the script created the request.

The `http.client.request.created` event is closer to where user code
creates the http request, and correctly reflects which script
initiated the request.

PR-URL: https://github.com/nodejs/node/pull/56805
Refs: https://github.com/nodejs/node/issues/53946
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Kohei Ueno <kohei.ueno119@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
Chengzhong Wu 2025-02-05 18:03:57 +00:00 committed by Node.js GitHub Bot
parent 297a4dd339
commit b18153598b
12 changed files with 92 additions and 15 deletions

View File

@ -44,11 +44,11 @@ const convertHeaderObject = (headers = {}) => {
}; };
/** /**
* When a client request starts, emit Network.requestWillBeSent event. * When a client request is created, emit Network.requestWillBeSent event.
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent * https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent
* @param {{ request: import('http').ClientRequest }} event * @param {{ request: import('http').ClientRequest }} event
*/ */
function onClientRequestStart({ request }) { function onClientRequestCreated({ request }) {
request[kInspectorRequestId] = getNextRequestId(); request[kInspectorRequestId] = getNextRequestId();
const { 0: host, 1: headers } = convertHeaderObject(request.getHeaders()); const { 0: host, 1: headers } = convertHeaderObject(request.getHeaders());
@ -115,13 +115,13 @@ function onClientResponseFinish({ request, response }) {
} }
function enable() { function enable() {
dc.subscribe('http.client.request.start', onClientRequestStart); dc.subscribe('http.client.request.created', onClientRequestCreated);
dc.subscribe('http.client.request.error', onClientRequestError); dc.subscribe('http.client.request.error', onClientRequestError);
dc.subscribe('http.client.response.finish', onClientResponseFinish); dc.subscribe('http.client.response.finish', onClientResponseFinish);
} }
function disable() { function disable() {
dc.unsubscribe('http.client.request.start', onClientRequestStart); dc.unsubscribe('http.client.request.created', onClientRequestCreated);
dc.unsubscribe('http.client.request.error', onClientRequestError); dc.unsubscribe('http.client.request.error', onClientRequestError);
dc.unsubscribe('http.client.response.finish', onClientResponseFinish); dc.unsubscribe('http.client.response.finish', onClientResponseFinish);
} }

View File

@ -129,10 +129,10 @@ function enable() {
} }
function disable() { function disable() {
dc.subscribe('undici:request:create', onClientRequestStart); dc.unsubscribe('undici:request:create', onClientRequestStart);
dc.subscribe('undici:request:error', onClientRequestError); dc.unsubscribe('undici:request:error', onClientRequestError);
dc.subscribe('undici:request:headers', onClientResponseHeaders); dc.unsubscribe('undici:request:headers', onClientResponseHeaders);
dc.subscribe('undici:request:trailers', onClientResponseFinish); dc.unsubscribe('undici:request:trailers', onClientResponseFinish);
} }
module.exports = { module.exports = {

View File

@ -29,8 +29,9 @@ std::unique_ptr<Network::Response> createResponse(
.build(); .build();
} }
NetworkAgent::NetworkAgent(NetworkInspector* inspector) NetworkAgent::NetworkAgent(NetworkInspector* inspector,
: inspector_(inspector) { v8_inspector::V8Inspector* v8_inspector)
: inspector_(inspector), v8_inspector_(v8_inspector) {
event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent; event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent;
event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived; event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived;
event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed; event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed;
@ -75,6 +76,13 @@ void NetworkAgent::requestWillBeSent(
String method; String method;
request->getString("method", &method); request->getString("method", &method);
std::unique_ptr<Network::Initiator> initiator =
Network::Initiator::create()
.setType(Network::Initiator::TypeEnum::Script)
.setStack(
v8_inspector_->captureStackTrace(true)->buildInspectorObject(0))
.build();
ErrorSupport errors; ErrorSupport errors;
errors.Push(); errors.Push();
errors.SetName("headers"); errors.SetName("headers");
@ -86,6 +94,7 @@ void NetworkAgent::requestWillBeSent(
frontend_->requestWillBeSent(request_id, frontend_->requestWillBeSent(request_id,
createRequest(url, method, std::move(headers)), createRequest(url, method, std::move(headers)),
std::move(initiator),
timestamp, timestamp,
wall_time); wall_time);
} }

View File

@ -14,7 +14,8 @@ namespace protocol {
class NetworkAgent : public Network::Backend { class NetworkAgent : public Network::Backend {
public: public:
explicit NetworkAgent(NetworkInspector* inspector); explicit NetworkAgent(NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector);
void Wire(UberDispatcher* dispatcher); void Wire(UberDispatcher* dispatcher);
@ -35,6 +36,7 @@ class NetworkAgent : public Network::Backend {
private: private:
NetworkInspector* inspector_; NetworkInspector* inspector_;
v8_inspector::V8Inspector* v8_inspector_;
std::shared_ptr<Network::Frontend> frontend_; std::shared_ptr<Network::Frontend> frontend_;
using EventNotifier = using EventNotifier =
void (NetworkAgent::*)(std::unique_ptr<protocol::DictionaryValue>); void (NetworkAgent::*)(std::unique_ptr<protocol::DictionaryValue>);

View File

@ -3,9 +3,10 @@
namespace node { namespace node {
namespace inspector { namespace inspector {
NetworkInspector::NetworkInspector(Environment* env) NetworkInspector::NetworkInspector(Environment* env,
v8_inspector::V8Inspector* v8_inspector)
: enabled_(false), env_(env) { : enabled_(false), env_(env) {
network_agent_ = std::make_unique<protocol::NetworkAgent>(this); network_agent_ = std::make_unique<protocol::NetworkAgent>(this, v8_inspector);
} }
NetworkInspector::~NetworkInspector() { NetworkInspector::~NetworkInspector() {
network_agent_.reset(); network_agent_.reset();

View File

@ -11,7 +11,8 @@ namespace inspector {
class NetworkInspector { class NetworkInspector {
public: public:
explicit NetworkInspector(Environment* env); explicit NetworkInspector(Environment* env,
v8_inspector::V8Inspector* v8_inspector);
~NetworkInspector(); ~NetworkInspector();
void Wire(protocol::UberDispatcher* dispatcher); void Wire(protocol::UberDispatcher* dispatcher);

View File

@ -97,6 +97,7 @@
'action_name': 'node_protocol_generated_sources', 'action_name': 'node_protocol_generated_sources',
'inputs': [ 'inputs': [
'node_protocol_config.json', 'node_protocol_config.json',
'node_protocol.pdl',
'<(SHARED_INTERMEDIATE_DIR)/src/node_protocol.json', '<(SHARED_INTERMEDIATE_DIR)/src/node_protocol.json',
'<@(node_protocol_files)', '<@(node_protocol_files)',
'<(protocol_tool_path)/code_generator.py', '<(protocol_tool_path)/code_generator.py',

View File

@ -101,6 +101,8 @@ experimental domain NodeWorker
# Partial support for Network domain of ChromeDevTools Protocol. # Partial support for Network domain of ChromeDevTools Protocol.
# https://chromedevtools.github.io/devtools-protocol/tot/Network # https://chromedevtools.github.io/devtools-protocol/tot/Network
experimental domain Network experimental domain Network
depends on Runtime
# Resource type as it was perceived by the rendering engine. # Resource type as it was perceived by the rendering engine.
type ResourceType extends string type ResourceType extends string
enum enum
@ -132,6 +134,31 @@ experimental domain Network
# Monotonically increasing time in seconds since an arbitrary point in the past. # Monotonically increasing time in seconds since an arbitrary point in the past.
type MonotonicTime extends number type MonotonicTime extends number
# Information about the request initiator.
type Initiator extends object
properties
# Type of this initiator.
enum type
parser
script
preload
SignedExchange
preflight
other
# Initiator JavaScript stack trace, set for Script only.
# Requires the Debugger domain to be enabled.
optional Runtime.StackTrace stack
# Initiator URL, set for Parser type or for Script type (when script is importing module) or for SignedExchange type.
optional string url
# Initiator line number, set for Parser type or for Script type (when script is importing
# module) (0-based).
optional number lineNumber
# Initiator column number, set for Parser type or for Script type (when script is importing
# module) (0-based).
optional number columnNumber
# Set if another request triggered this request (e.g. preflight).
optional RequestId requestId
# HTTP request data. # HTTP request data.
type Request extends object type Request extends object
properties properties
@ -163,6 +190,8 @@ experimental domain Network
RequestId requestId RequestId requestId
# Request data. # Request data.
Request request Request request
# Request initiator.
Initiator initiator
# Timestamp. # Timestamp.
MonotonicTime timestamp MonotonicTime timestamp
# Timestamp. # Timestamp.

View File

@ -5,6 +5,17 @@
"output": "node/inspector/protocol", "output": "node/inspector/protocol",
"namespace": ["node", "inspector", "protocol"] "namespace": ["node", "inspector", "protocol"]
}, },
"imported": {
"path": "../../deps/v8/include/js_protocol.pdl",
"header": "<v8-inspector-protocol.h>",
"namespace": ["v8_inspector", "protocol"],
"options": [
{
"domain": "Runtime",
"imported": ["StackTrace"]
}
]
},
"exported": { "exported": {
"package": "include/inspector", "package": "include/inspector",
"output": "../../include/inspector", "output": "../../include/inspector",

View File

@ -241,7 +241,8 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
} }
runtime_agent_ = std::make_unique<protocol::RuntimeAgent>(); runtime_agent_ = std::make_unique<protocol::RuntimeAgent>();
runtime_agent_->Wire(node_dispatcher_.get()); runtime_agent_->Wire(node_dispatcher_.get());
network_inspector_ = std::make_unique<NetworkInspector>(env); network_inspector_ =
std::make_unique<NetworkInspector>(env, inspector.get());
network_inspector_->Wire(node_dispatcher_.get()); network_inspector_->Wire(node_dispatcher_.get());
} }

View File

@ -65,6 +65,13 @@ const terminate = () => {
inspector.close(); inspector.close();
}; };
function findFrameInInitiator(scriptName, initiator) {
const frame = initiator.stack.callFrames.find((it) => {
return it.url === scriptName;
});
return frame;
}
const testHttpGet = () => new Promise((resolve, reject) => { const testHttpGet = () => new Promise((resolve, reject) => {
session.on('Network.requestWillBeSent', common.mustCall(({ params }) => { session.on('Network.requestWillBeSent', common.mustCall(({ params }) => {
assert.ok(params.requestId.startsWith('node-network-event-')); assert.ok(params.requestId.startsWith('node-network-event-'));
@ -77,6 +84,10 @@ const testHttpGet = () => new Promise((resolve, reject) => {
assert.strictEqual(params.request.headers['x-header1'], 'value1, value2'); assert.strictEqual(params.request.headers['x-header1'], 'value1, value2');
assert.strictEqual(typeof params.timestamp, 'number'); assert.strictEqual(typeof params.timestamp, 'number');
assert.strictEqual(typeof params.wallTime, 'number'); assert.strictEqual(typeof params.wallTime, 'number');
assert.strictEqual(typeof params.initiator, 'object');
assert.strictEqual(params.initiator.type, 'script');
assert.ok(findFrameInInitiator(__filename, params.initiator));
})); }));
session.on('Network.responseReceived', common.mustCall(({ params }) => { session.on('Network.responseReceived', common.mustCall(({ params }) => {
assert.ok(params.requestId.startsWith('node-network-event-')); assert.ok(params.requestId.startsWith('node-network-event-'));

View File

@ -64,6 +64,13 @@ const terminate = () => {
inspector.close(); inspector.close();
}; };
function findFrameInInitiator(scriptName, initiator) {
const frame = initiator.stack.callFrames.find((it) => {
return it.url === scriptName;
});
return frame;
}
function verifyRequestWillBeSent({ method, params }, expect) { function verifyRequestWillBeSent({ method, params }, expect) {
assert.strictEqual(method, 'Network.requestWillBeSent'); assert.strictEqual(method, 'Network.requestWillBeSent');
@ -78,6 +85,10 @@ function verifyRequestWillBeSent({ method, params }, expect) {
assert.strictEqual(typeof params.timestamp, 'number'); assert.strictEqual(typeof params.timestamp, 'number');
assert.strictEqual(typeof params.wallTime, 'number'); assert.strictEqual(typeof params.wallTime, 'number');
assert.strictEqual(typeof params.initiator, 'object');
assert.strictEqual(params.initiator.type, 'script');
assert.ok(findFrameInInitiator(__filename, params.initiator));
return params; return params;
} }