Skip to content

Commit de1fbc2

Browse files
cola119targos
authored andcommitted
inspector: add initial support for network inspection
PR-URL: #53593 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent a94c3ae commit de1fbc2

20 files changed

+789
-3
lines changed

doc/api/cli.md

+11
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,17 @@ added:
10281028
10291029
Enable experimental support for the `https:` protocol in `import` specifiers.
10301030

1031+
### `--experimental-network-inspection`
1032+
1033+
<!-- YAML
1034+
added:
1035+
- REPLACEME
1036+
-->
1037+
1038+
> Stability: 1 - Experimental
1039+
1040+
Enable experimental support for the network inspection with Chrome DevTools.
1041+
10311042
### `--experimental-permission`
10321043

10331044
<!-- YAML

doc/api/inspector.md

+69
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,75 @@ Blocks until a client (existing or connected later) has sent
488488

489489
An exception will be thrown if there is no active inspector.
490490

491+
## Integration with DevTools
492+
493+
The `node:inspector` module provides an API for integrating with devtools that support Chrome DevTools Protocol.
494+
DevTools frontends connected to a running Node.js instance can capture protocol events emitted from the instance
495+
and display them accordingly to facilitate debugging.
496+
The following methods broadcast a protocol event to all connected frontends.
497+
The `params` passed to the methods can be optional, depending on the protocol.
498+
499+
```js
500+
// The `Network.requestWillBeSent` event will be fired.
501+
inspector.Network.requestWillBeSent({
502+
requestId: 'request-id-1',
503+
timestamp: Date.now() / 1000,
504+
wallTime: Date.now(),
505+
request: {
506+
url: 'https://nodejs.org/en',
507+
method: 'GET',
508+
}
509+
});
510+
```
511+
512+
### `inspector.Network.requestWillBeSent([params])`
513+
514+
<!-- YAML
515+
added:
516+
- REPLACEME
517+
-->
518+
519+
> Stability: 1 - Experimental
520+
521+
* `params` {Object}
522+
523+
This feature is only available with the `--experimental-network-inspection` flag enabled.
524+
525+
Broadcasts the `Network.requestWillBeSent` event to connected frontends. This event indicates that
526+
the application is about to send an HTTP request.
527+
528+
### `inspector.Network.responseReceived([params])`
529+
530+
<!-- YAML
531+
added:
532+
- REPLACEME
533+
-->
534+
535+
> Stability: 1 - Experimental
536+
537+
* `params` {Object}
538+
539+
This feature is only available with the `--experimental-network-inspection` flag enabled.
540+
541+
Broadcasts the `Network.responseReceived` event to connected frontends. This event indicates that
542+
HTTP response is available.
543+
544+
### `inspector.Network.loadingFinished([params])`
545+
546+
<!-- YAML
547+
added:
548+
- REPLACEME
549+
-->
550+
551+
> Stability: 1 - Experimental
552+
553+
* `params` {Object}
554+
555+
This feature is only available with the `--experimental-network-inspection` flag enabled.
556+
557+
Broadcasts the `Network.loadingFinished` event to connected frontends. This event indicates that
558+
HTTP request has finished loading.
559+
491560
## Support of breakpoints
492561

493562
The Chrome DevTools Protocol [`Debugger` domain][] allows an

lib/inspector.js

+16
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const {
4242
isEnabled,
4343
waitForDebugger,
4444
console,
45+
emitProtocolEvent,
4546
} = internalBinding('inspector');
4647

4748
class Session extends EventEmitter {
@@ -188,11 +189,26 @@ function inspectorWaitForDebugger() {
188189
throw new ERR_INSPECTOR_NOT_ACTIVE();
189190
}
190191

192+
function broadcastToFrontend(eventName, params) {
193+
validateString(eventName, 'eventName');
194+
if (params) {
195+
validateObject(params, 'params');
196+
}
197+
emitProtocolEvent(eventName, JSONStringify(params ?? {}));
198+
}
199+
200+
const Network = {
201+
requestWillBeSent: (params) => broadcastToFrontend('Network.requestWillBeSent', params),
202+
responseReceived: (params) => broadcastToFrontend('Network.responseReceived', params),
203+
loadingFinished: (params) => broadcastToFrontend('Network.loadingFinished', params),
204+
};
205+
191206
module.exports = {
192207
open: inspectorOpen,
193208
close: _debugEnd,
194209
url,
195210
waitForDebugger: inspectorWaitForDebugger,
196211
console,
197212
Session,
213+
Network,
198214
};
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
3+
const {
4+
DateNow,
5+
} = primordials;
6+
7+
let dc;
8+
let Network;
9+
10+
let requestId = 0;
11+
const getNextRequestId = () => `node-network-event-${++requestId}`;
12+
13+
function onClientRequestStart({ request }) {
14+
const url = `${request.protocol}//${request.host}${request.path}`;
15+
const wallTime = DateNow();
16+
const timestamp = wallTime / 1000;
17+
request._inspectorRequestId = getNextRequestId();
18+
Network.requestWillBeSent({
19+
requestId: request._inspectorRequestId,
20+
timestamp,
21+
wallTime,
22+
request: {
23+
url,
24+
method: request.method,
25+
},
26+
});
27+
}
28+
29+
function onClientResponseFinish({ request }) {
30+
if (typeof request._inspectorRequestId !== 'string') {
31+
return;
32+
}
33+
const timestamp = DateNow() / 1000;
34+
Network.responseReceived({
35+
requestId: request._inspectorRequestId,
36+
timestamp,
37+
});
38+
Network.loadingFinished({
39+
requestId: request._inspectorRequestId,
40+
timestamp,
41+
});
42+
}
43+
44+
function enable() {
45+
if (!dc) {
46+
dc = require('diagnostics_channel');
47+
}
48+
if (!Network) {
49+
Network = require('inspector').Network;
50+
}
51+
dc.subscribe('http.client.request.start', onClientRequestStart);
52+
dc.subscribe('http.client.response.finish', onClientResponseFinish);
53+
}
54+
55+
function disable() {
56+
dc.unsubscribe('http.client.request.start', onClientRequestStart);
57+
dc.unsubscribe('http.client.response.finish', onClientResponseFinish);
58+
}
59+
60+
module.exports = {
61+
enable,
62+
disable,
63+
};

lib/internal/process/pre_execution.js

+11
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ function prepareExecution(options) {
106106
const mainEntry = patchProcessObject(expandArgv1);
107107
setupTraceCategoryState();
108108
setupInspectorHooks();
109+
setupNetworkInspection();
109110
setupNavigator();
110111
setupWarningHandler();
111112
setupUndici();
@@ -513,6 +514,16 @@ function setupInspectorHooks() {
513514
}
514515
}
515516

517+
function setupNetworkInspection() {
518+
if (internalBinding('config').hasInspector && getOptionValue('--experimental-network-inspection')) {
519+
const {
520+
enable,
521+
disable,
522+
} = require('internal/inspector_network_tracking');
523+
internalBinding('inspector').setupNetworkTracking(enable, disable);
524+
}
525+
}
526+
516527
// In general deprecations are initialized wherever the APIs are implemented,
517528
// this is used to deprecate APIs implemented in C++ where the deprecation
518529
// utilities are not easily accessible.

src/env_properties.h

+2
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,9 @@
448448
V(immediate_callback_function, v8::Function) \
449449
V(inspector_console_extension_installer, v8::Function) \
450450
V(inspector_disable_async_hooks, v8::Function) \
451+
V(inspector_disable_network_tracking, v8::Function) \
451452
V(inspector_enable_async_hooks, v8::Function) \
453+
V(inspector_enable_network_tracking, v8::Function) \
452454
V(maybe_cache_generated_source_map, v8::Function) \
453455
V(messaging_deserialize_create_object, v8::Function) \
454456
V(message_port, v8::Object) \

src/inspector/network_agent.cc

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include "network_agent.h"
2+
#include "network_inspector.h"
3+
4+
namespace node {
5+
namespace inspector {
6+
namespace protocol {
7+
8+
std::unique_ptr<Network::Request> Request(const String& url,
9+
const String& method) {
10+
return Network::Request::create().setUrl(url).setMethod(method).build();
11+
}
12+
13+
NetworkAgent::NetworkAgent(NetworkInspector* inspector)
14+
: inspector_(inspector) {
15+
event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent;
16+
event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived;
17+
event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished;
18+
}
19+
20+
void NetworkAgent::emitNotification(
21+
const String& event, std::unique_ptr<protocol::DictionaryValue> params) {
22+
if (!inspector_->IsEnabled()) return;
23+
auto it = event_notifier_map_.find(event);
24+
if (it != event_notifier_map_.end()) {
25+
(this->*(it->second))(std::move(params));
26+
}
27+
}
28+
29+
void NetworkAgent::Wire(UberDispatcher* dispatcher) {
30+
frontend_ = std::make_unique<Network::Frontend>(dispatcher->channel());
31+
Network::Dispatcher::wire(dispatcher, this);
32+
}
33+
34+
DispatchResponse NetworkAgent::enable() {
35+
inspector_->Enable();
36+
return DispatchResponse::OK();
37+
}
38+
39+
DispatchResponse NetworkAgent::disable() {
40+
inspector_->Disable();
41+
return DispatchResponse::OK();
42+
}
43+
44+
void NetworkAgent::requestWillBeSent(
45+
std::unique_ptr<protocol::DictionaryValue> params) {
46+
String request_id;
47+
params->getString("requestId", &request_id);
48+
double timestamp;
49+
params->getDouble("timestamp", &timestamp);
50+
double wall_time;
51+
params->getDouble("wallTime", &wall_time);
52+
auto request = params->getObject("request");
53+
String url;
54+
request->getString("url", &url);
55+
String method;
56+
request->getString("method", &method);
57+
58+
frontend_->requestWillBeSent(
59+
request_id, Request(url, method), timestamp, wall_time);
60+
}
61+
62+
void NetworkAgent::responseReceived(
63+
std::unique_ptr<protocol::DictionaryValue> params) {
64+
String request_id;
65+
params->getString("requestId", &request_id);
66+
double timestamp;
67+
params->getDouble("timestamp", &timestamp);
68+
69+
frontend_->responseReceived(request_id, timestamp);
70+
}
71+
72+
void NetworkAgent::loadingFinished(
73+
std::unique_ptr<protocol::DictionaryValue> params) {
74+
String request_id;
75+
params->getString("requestId", &request_id);
76+
double timestamp;
77+
params->getDouble("timestamp", &timestamp);
78+
79+
frontend_->loadingFinished(request_id, timestamp);
80+
}
81+
82+
} // namespace protocol
83+
} // namespace inspector
84+
} // namespace node

src/inspector/network_agent.h

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#ifndef SRC_INSPECTOR_NETWORK_AGENT_H_
2+
#define SRC_INSPECTOR_NETWORK_AGENT_H_
3+
4+
#include "node/inspector/protocol/Network.h"
5+
6+
#include <unordered_map>
7+
8+
namespace node {
9+
10+
namespace inspector {
11+
class NetworkInspector;
12+
13+
namespace protocol {
14+
15+
std::unique_ptr<Network::Request> Request(const String& url,
16+
const String& method);
17+
18+
class NetworkAgent : public Network::Backend {
19+
public:
20+
explicit NetworkAgent(NetworkInspector* inspector);
21+
22+
void Wire(UberDispatcher* dispatcher);
23+
24+
DispatchResponse enable() override;
25+
26+
DispatchResponse disable() override;
27+
28+
void emitNotification(const String& event,
29+
std::unique_ptr<protocol::DictionaryValue> params);
30+
31+
void requestWillBeSent(std::unique_ptr<protocol::DictionaryValue> params);
32+
33+
void responseReceived(std::unique_ptr<protocol::DictionaryValue> params);
34+
35+
void loadingFinished(std::unique_ptr<protocol::DictionaryValue> params);
36+
37+
private:
38+
NetworkInspector* inspector_;
39+
std::shared_ptr<Network::Frontend> frontend_;
40+
using EventNotifier =
41+
void (NetworkAgent::*)(std::unique_ptr<protocol::DictionaryValue>);
42+
std::unordered_map<String, EventNotifier> event_notifier_map_;
43+
};
44+
45+
} // namespace protocol
46+
} // namespace inspector
47+
} // namespace node
48+
49+
#endif // SRC_INSPECTOR_NETWORK_AGENT_H_

0 commit comments

Comments
 (0)