Skip to content

Commit bda9a11

Browse files
committed
inspector: add protocol method Network.dataReceived
1 parent 2c315d2 commit bda9a11

File tree

8 files changed

+399
-10
lines changed

8 files changed

+399
-10
lines changed

doc/api/inspector.md

+13
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,19 @@ inspector.Network.requestWillBeSent({
511511
});
512512
```
513513

514+
### `inspector.Network.dataReceived([params])`
515+
516+
<!-- YAML
517+
added: REPLACEME
518+
-->
519+
520+
* `params` {Object}
521+
522+
This feature is only available with the `--experimental-network-inspection` flag enabled.
523+
524+
Broadcasts the `Network.dataReceived` event to connected frontends, or buffers the data if
525+
`Network.streamResourceContent` command was not invoked for the given request yet.
526+
514527
### `inspector.Network.requestWillBeSent([params])`
515528

516529
<!-- YAML

lib/inspector.js

+1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ const Network = {
214214
responseReceived: (params) => broadcastToFrontend('Network.responseReceived', params),
215215
loadingFinished: (params) => broadcastToFrontend('Network.loadingFinished', params),
216216
loadingFailed: (params) => broadcastToFrontend('Network.loadingFailed', params),
217+
dataReceived: (params) => broadcastToFrontend('Network.dataReceived', params),
217218
};
218219

219220
module.exports = {

src/inspector/network_agent.cc

+91
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ using v8::Maybe;
1515
using v8::MaybeLocal;
1616
using v8::Nothing;
1717
using v8::Object;
18+
using v8::Uint8Array;
1819
using v8::Value;
1920

2021
// Get a protocol string property from the object.
@@ -183,6 +184,7 @@ NetworkAgent::NetworkAgent(NetworkInspector* inspector,
183184
event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived;
184185
event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed;
185186
event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished;
187+
event_notifier_map_["dataReceived"] = &NetworkAgent::dataReceived;
186188
}
187189

188190
void NetworkAgent::emitNotification(v8::Local<v8::Context> context,
@@ -211,6 +213,30 @@ protocol::DispatchResponse NetworkAgent::disable() {
211213
return protocol::DispatchResponse::Success();
212214
}
213215

216+
protocol::DispatchResponse NetworkAgent::streamResourceContent(
217+
const protocol::String& in_requestId, protocol::Binary* out_bufferedData) {
218+
if (!requests_.contains(in_requestId)) {
219+
// Request not found, ignore it.
220+
return protocol::DispatchResponse::InvalidParams("Request not found");
221+
}
222+
223+
auto& it = requests_[in_requestId];
224+
225+
it.is_streaming = true;
226+
227+
// Concat response bodies.
228+
*out_bufferedData = protocol::Binary::concat(it.response_data_blobs);
229+
// Clear buffered data.
230+
it.response_data_blobs.clear();
231+
232+
if (it.is_finished) {
233+
// If the request is finished, remove the entry.
234+
requests_.erase(in_requestId);
235+
}
236+
237+
return protocol::DispatchResponse::Success();
238+
}
239+
214240
void NetworkAgent::requestWillBeSent(v8::Local<v8::Context> context,
215241
v8::Local<v8::Object> params) {
216242
protocol::String request_id;
@@ -247,6 +273,12 @@ void NetworkAgent::requestWillBeSent(v8::Local<v8::Context> context,
247273
std::move(initiator),
248274
timestamp,
249275
wall_time);
276+
277+
if (requests_.contains(request_id)) {
278+
// Duplicate entry, ignore it.
279+
return;
280+
}
281+
requests_.emplace(request_id, RequestEntry{timestamp, false, false, {}});
250282
}
251283

252284
void NetworkAgent::responseReceived(v8::Local<v8::Context> context,
@@ -295,6 +327,8 @@ void NetworkAgent::loadingFailed(v8::Local<v8::Context> context,
295327
}
296328

297329
frontend_->loadingFailed(request_id, timestamp, type, error_text);
330+
331+
requests_.erase(request_id);
298332
}
299333

300334
void NetworkAgent::loadingFinished(v8::Local<v8::Context> context,
@@ -309,6 +343,63 @@ void NetworkAgent::loadingFinished(v8::Local<v8::Context> context,
309343
}
310344

311345
frontend_->loadingFinished(request_id, timestamp);
346+
347+
auto request_entry = requests_.find(request_id);
348+
if (request_entry == requests_.end()) {
349+
// No entry found. Ignore it.
350+
return;
351+
}
352+
353+
if (request_entry->second.is_streaming) {
354+
// Streaming finished, remove the entry.
355+
requests_.erase(request_id);
356+
} else {
357+
request_entry->second.is_finished = true;
358+
}
359+
}
360+
361+
void NetworkAgent::dataReceived(v8::Local<v8::Context> context,
362+
v8::Local<v8::Object> params) {
363+
protocol::String request_id;
364+
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
365+
return;
366+
}
367+
368+
auto request_entry = requests_.find(request_id);
369+
if (request_entry == requests_.end()) {
370+
// No entry found. Ignore it.
371+
return;
372+
}
373+
374+
double timestamp;
375+
if (!ObjectGetDouble(context, params, "timestamp").To(&timestamp)) {
376+
return;
377+
}
378+
int data_length;
379+
if (!ObjectGetInt(context, params, "dataLength").To(&data_length)) {
380+
return;
381+
}
382+
int encoded_data_length;
383+
if (!ObjectGetInt(context, params, "encodedDataLength")
384+
.To(&encoded_data_length)) {
385+
return;
386+
}
387+
Local<Object> data_obj;
388+
if (!ObjectGetObject(context, params, "data").ToLocal(&data_obj)) {
389+
return;
390+
}
391+
if (!data_obj->IsUint8Array()) {
392+
return;
393+
}
394+
Local<Uint8Array> data = data_obj.As<Uint8Array>();
395+
auto data_bin = protocol::Binary::fromUint8Array(data);
396+
397+
if (request_entry->second.is_streaming) {
398+
frontend_->dataReceived(
399+
request_id, timestamp, data_length, encoded_data_length, data_bin);
400+
} else {
401+
requests_[request_id].response_data_blobs.push_back(data_bin);
402+
}
312403
}
313404

314405
} // namespace inspector

src/inspector/network_agent.h

+16
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
33

44
#include "node/inspector/protocol/Network.h"
55

6+
#include <map>
67
#include <unordered_map>
78

89
namespace node {
910
namespace inspector {
1011

1112
class NetworkInspector;
1213

14+
struct RequestEntry {
15+
double timestamp;
16+
bool is_finished;
17+
bool is_streaming;
18+
std::vector<protocol::Binary> response_data_blobs;
19+
};
20+
1321
class NetworkAgent : public protocol::Network::Backend {
1422
public:
1523
explicit NetworkAgent(NetworkInspector* inspector,
@@ -21,6 +29,10 @@ class NetworkAgent : public protocol::Network::Backend {
2129

2230
protocol::DispatchResponse disable() override;
2331

32+
protocol::DispatchResponse streamResourceContent(
33+
const protocol::String& in_requestId,
34+
protocol::Binary* out_bufferedData) override;
35+
2436
void emitNotification(v8::Local<v8::Context> context,
2537
const protocol::String& event,
2638
v8::Local<v8::Object> params);
@@ -37,13 +49,17 @@ class NetworkAgent : public protocol::Network::Backend {
3749
void loadingFinished(v8::Local<v8::Context> context,
3850
v8::Local<v8::Object> params);
3951

52+
void dataReceived(v8::Local<v8::Context> context,
53+
v8::Local<v8::Object> params);
54+
4055
private:
4156
NetworkInspector* inspector_;
4257
v8_inspector::V8Inspector* v8_inspector_;
4358
std::shared_ptr<protocol::Network::Frontend> frontend_;
4459
using EventNotifier = void (NetworkAgent::*)(v8::Local<v8::Context> context,
4560
v8::Local<v8::Object>);
4661
std::unordered_map<protocol::String, EventNotifier> event_notifier_map_;
62+
std::map<protocol::String, RequestEntry> requests_;
4763
};
4864

4965
} // namespace inspector

src/inspector/node_protocol.pdl

+24
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,16 @@ experimental domain Network
183183
# Enables network tracking, network events will now be delivered to the client.
184184
command enable
185185

186+
# Enables streaming of the response for the given requestId.
187+
# If enabled, the dataReceived event contains the data that was received during streaming.
188+
experimental command streamResourceContent
189+
parameters
190+
# Identifier of the request to stream.
191+
RequestId requestId
192+
returns
193+
# Data that has been buffered until streaming is enabled.
194+
binary bufferedData
195+
186196
# Fired when page is about to send HTTP request.
187197
event requestWillBeSent
188198
parameters
@@ -227,6 +237,20 @@ experimental domain Network
227237
# Timestamp.
228238
MonotonicTime timestamp
229239

240+
# Fired when data chunk was received over the network.
241+
event dataReceived
242+
parameters
243+
# Request identifier.
244+
RequestId requestId
245+
# Timestamp.
246+
MonotonicTime timestamp
247+
# Data chunk length.
248+
integer dataLength
249+
# Actual bytes received (might be less than dataLength for compressed encodings).
250+
integer encodedDataLength
251+
# Data that was received.
252+
experimental optional binary data
253+
230254
# Support for inspecting node process state.
231255
experimental domain NodeRuntime
232256
# Enable the NodeRuntime events except by `NodeRuntime.waitingForDisconnect`.

src/inspector/node_string.cc

+66
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@ void ProtocolTypeTraits<std::string>::Serialize(const std::string& value,
2828
cbor::EncodeString8(SpanFrom(value), bytes);
2929
}
3030

31+
bool ProtocolTypeTraits<node::inspector::protocol::Binary>::Deserialize(
32+
DeserializerState* state, node::inspector::protocol::Binary* value) {
33+
CHECK(state->tokenizer()->TokenTag() == cbor::CBORTokenTag::BINARY);
34+
span<uint8_t> cbor_span = state->tokenizer()->GetBinary();
35+
*value = node::inspector::protocol::Binary::fromSpan(cbor_span);
36+
return true;
37+
}
38+
39+
void ProtocolTypeTraits<node::inspector::protocol::Binary>::Serialize(
40+
const node::inspector::protocol::Binary& value,
41+
std::vector<uint8_t>* bytes) {
42+
cbor::EncodeString8(SpanFrom(value.toBase64()), bytes);
43+
}
44+
3145
} // namespace crdtp
3246

3347
namespace node {
@@ -93,6 +107,58 @@ size_t StringUtil::CharacterCount(const std::string_view s) {
93107
return s.length();
94108
}
95109

110+
String Binary::toBase64() const {
111+
MaybeStackBuffer<char> buffer;
112+
size_t str_len = simdutf::base64_length_from_binary(bytes_->size());
113+
buffer.SetLength(str_len);
114+
115+
size_t len =
116+
simdutf::binary_to_base64(reinterpret_cast<const char*>(bytes_->data()),
117+
bytes_->size(),
118+
buffer.out());
119+
CHECK_EQ(len, str_len);
120+
return buffer.ToString();
121+
}
122+
123+
// static
124+
Binary Binary::concat(const std::vector<Binary>& binaries) {
125+
size_t total_size = 0;
126+
for (const auto& binary : binaries) {
127+
total_size += binary.size();
128+
}
129+
auto bytes = std::make_shared<std::vector<uint8_t>>(total_size);
130+
uint8_t* data_ptr = bytes->data();
131+
for (const auto& binary : binaries) {
132+
memcpy(data_ptr, binary.data(), binary.size());
133+
data_ptr += binary.size();
134+
}
135+
return Binary(bytes);
136+
}
137+
138+
// static
139+
Binary Binary::fromBase64(const String& base64, bool* success) {
140+
Binary binary{};
141+
size_t base64_len = simdutf::maximal_binary_length_from_base64(
142+
base64.data(), base64.length());
143+
binary.bytes_->resize(base64_len);
144+
145+
simdutf::result result;
146+
result =
147+
simdutf::base64_to_binary(base64.data(),
148+
base64.length(),
149+
reinterpret_cast<char*>(binary.bytes_->data()));
150+
CHECK_EQ(result.error, simdutf::error_code::SUCCESS);
151+
return binary;
152+
}
153+
154+
// static
155+
Binary Binary::fromUint8Array(v8::Local<v8::Uint8Array> data) {
156+
auto bytes = std::make_shared<std::vector<uint8_t>>(data->ByteLength());
157+
size_t size = data->CopyContents(bytes->data(), data->ByteLength());
158+
CHECK_EQ(size, data->ByteLength());
159+
return Binary(bytes);
160+
}
161+
96162
} // namespace protocol
97163
} // namespace inspector
98164
} // namespace node

0 commit comments

Comments
 (0)