GeezerVison is showing github.com — inspector: add initial support for network inspection · nodejs/node@de1fbc2
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/cli.md +11Lines changed: 11 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1028,6 +1028,17 @@ added: | 1028 | 1028 |
| 1029 | 1029 | Enable experimental support for the `https:` protocol in `import` specifiers. | 1030 | 1030 |
| | 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 | + | 1031 | 1042 | ### `--experimental-permission` | 1032 | 1043 |
| 1033 | 1044 | <!-- YAML | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/inspector.md +69Lines changed: 69 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -488,6 +488,75 @@ Blocks until a client (existing or connected later) has sent | 488 | 488 |
| 489 | 489 | An exception will be thrown if there is no active inspector. | 490 | 490 |
| | 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 | + | 491 | 560 | ## Support of breakpoints | 492 | 561 |
| 493 | 562 | The Chrome DevTools Protocol [`Debugger` domain][] allows an | |
Collapse file Copy file name to clipboardExpand all lines: lib/inspector.js +16Lines changed: 16 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -42,6 +42,7 @@ const { | 42 | 42 | isEnabled, | 43 | 43 | waitForDebugger, | 44 | 44 | console, | | 45 | + emitProtocolEvent, | 45 | 46 | } = internalBinding('inspector'); | 46 | 47 |
| 47 | 48 | class Session extends EventEmitter { | @@ -188,11 +189,26 @@ function inspectorWaitForDebugger() { | 188 | 189 | throw new ERR_INSPECTOR_NOT_ACTIVE(); | 189 | 190 | } | 190 | 191 |
| | 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 | + | 191 | 206 | module.exports = { | 192 | 207 | open: inspectorOpen, | 193 | 208 | close: _debugEnd, | 194 | 209 | url, | 195 | 210 | waitForDebugger: inspectorWaitForDebugger, | 196 | 211 | console, | 197 | 212 | Session, | | 213 | + Network, | 198 | 214 | }; |
Collapse file Copy file name to clipboard +63Lines changed: 63 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +}; |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/pre_execution.js +11Lines changed: 11 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -106,6 +106,7 @@ function prepareExecution(options) { | 106 | 106 | const mainEntry = patchProcessObject(expandArgv1); | 107 | 107 | setupTraceCategoryState(); | 108 | 108 | setupInspectorHooks(); | | 109 | + setupNetworkInspection(); | 109 | 110 | setupNavigator(); | 110 | 111 | setupWarningHandler(); | 111 | 112 | setupUndici(); | @@ -513,6 +514,16 @@ function setupInspectorHooks() { | 513 | 514 | } | 514 | 515 | } | 515 | 516 |
| | 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 | + | 516 | 527 | // In general deprecations are initialized wherever the APIs are implemented, | 517 | 528 | // this is used to deprecate APIs implemented in C++ where the deprecation | 518 | 529 | // utilities are not easily accessible. | |
Collapse file Copy file name to clipboardExpand all lines: src/env_properties.h +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 448 | 448 | V(immediate_callback_function, v8::Function) \ | 449 | 449 | V(inspector_console_extension_installer, v8::Function) \ | 450 | 450 | V(inspector_disable_async_hooks, v8::Function) \ | | 451 | + V(inspector_disable_network_tracking, v8::Function) \ | 451 | 452 | V(inspector_enable_async_hooks, v8::Function) \ | | 453 | + V(inspector_enable_network_tracking, v8::Function) \ | 452 | 454 | V(maybe_cache_generated_source_map, v8::Function) \ | 453 | 455 | V(messaging_deserialize_create_object, v8::Function) \ | 454 | 456 | V(message_port, v8::Object) \ | |
Collapse file Copy file name to clipboard +84Lines changed: 84 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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", ×tamp); | | 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", ×tamp); | | 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", ×tamp); | | 78 | + | | 79 | + frontend_->loadingFinished(request_id, timestamp); | | 80 | +} | | 81 | + | | 82 | +} // namespace protocol | | 83 | +} // namespace inspector | | 84 | +} // namespace node |
Collapse file Copy file name to clipboard +49Lines changed: 49 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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_ |
|