Skip to content

Commit f63c06d

Browse files
committed
inspector: fix requestWillBeSent body state
1 parent ad883c5 commit f63c06d

4 files changed

Lines changed: 208 additions & 20 deletions

File tree

lib/internal/inspector/network_http.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
const {
44
ArrayIsArray,
5+
ArrayPrototypePush,
56
DateNow,
7+
MathMax,
68
ObjectEntries,
79
String,
810
StringPrototypeStartsWith,
@@ -17,10 +19,13 @@ const {
1719
registerDiagnosticChannels,
1820
sniffMimeType,
1921
} = require('internal/inspector/network');
22+
const { getStructuredStack } = require('internal/util');
2023
const { Network } = require('inspector');
2124
const { Buffer } = require('buffer');
2225

2326
const kRequestUrl = Symbol('kRequestUrl');
27+
const kRequestWillBeSent = Symbol('kRequestWillBeSent');
28+
const kInitiator = Symbol('kInitiator');
2429

2530
function isAbsoluteURLPath(path) {
2631
return typeof path === 'string' &&
@@ -67,38 +72,80 @@ const convertHeaderObject = (headers = {}) => {
6772
return [dict, host, charset, mimeType];
6873
};
6974

75+
function createInitiator() {
76+
const callSites = getStructuredStack();
77+
const callFrames = [];
78+
for (let i = 0; i < callSites.length; i++) {
79+
const callSite = callSites[i];
80+
ArrayPrototypePush(callFrames, {
81+
functionName: callSite.getFunctionName() ?? callSite.getMethodName() ?? '',
82+
scriptId: '',
83+
url: callSite.getScriptNameOrSourceURL() ?? callSite.getFileName() ?? '',
84+
lineNumber: MathMax((callSite.getLineNumber() ?? 1) - 1, 0),
85+
columnNumber: MathMax((callSite.getColumnNumber() ?? 1) - 1, 0),
86+
});
87+
}
88+
return {
89+
type: 'script',
90+
stack: { callFrames },
91+
};
92+
}
93+
7094
/**
71-
* When a client request is created, emit Network.requestWillBeSent event.
72-
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent
95+
* When a client request is created, assign its inspector request id.
7396
* @param {{ request: import('http').ClientRequest }} event
7497
*/
7598
function onClientRequestCreated({ request }) {
7699
request[kInspectorRequestId] = getNextRequestId();
100+
request[kInitiator] = createInitiator();
101+
}
102+
103+
/**
104+
* Emit Network.requestWillBeSent once the request body state is known.
105+
* @param {import('http').ClientRequest} request
106+
* @param {boolean} hasPostData
107+
*/
108+
function emitRequestWillBeSent(request, hasPostData) {
109+
if (request[kRequestWillBeSent] ||
110+
typeof request[kInspectorRequestId] !== 'string') {
111+
return;
112+
}
77113

78114
const { 0: headers, 1: host, 2: charset } = convertHeaderObject(request.getHeaders());
79115
const url = getRequestURL(request, host);
80116
request[kRequestUrl] = url;
117+
request[kRequestWillBeSent] = true;
81118

82119
Network.requestWillBeSent({
83120
requestId: request[kInspectorRequestId],
84121
timestamp: getMonotonicTime(),
85122
wallTime: DateNow(),
86123
charset,
124+
initiator: request[kInitiator],
87125
request: {
88126
url,
89127
method: request.method,
90128
headers,
91-
hasPostData: !request.writableEnded,
129+
hasPostData,
92130
},
93131
});
94132
}
95133

134+
/**
135+
* When a client request starts without a body, emit Network.requestWillBeSent.
136+
* @param {{ request: import('http').ClientRequest }} event
137+
*/
138+
function onClientRequestStart({ request }) {
139+
emitRequestWillBeSent(request, false);
140+
}
141+
96142
/**
97143
* When a client request errors, emit Network.loadingFailed event.
98144
* https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFailed
99145
* @param {{ request: import('http').ClientRequest, error: any }} event
100146
*/
101147
function onClientRequestError({ request, error }) {
148+
emitRequestWillBeSent(request, false);
102149
if (typeof request[kInspectorRequestId] !== 'string') {
103150
return;
104151
}
@@ -121,6 +168,8 @@ function onClientRequestBodyChunkSent({ request, chunk, encoding }) {
121168
return;
122169
}
123170

171+
emitRequestWillBeSent(request, true);
172+
124173
const buffer = typeof chunk === 'string' ? Buffer.from(chunk, encoding) : Buffer.from(chunk);
125174
Network.dataSent({
126175
requestId: request[kInspectorRequestId],
@@ -174,6 +223,7 @@ function onClientResponseBodyChunkReceived({ request, chunk }) {
174223
* @param {{ request: import('http').ClientRequest, error: any }} event
175224
*/
176225
function onClientResponseFinish({ request, response }) {
226+
emitRequestWillBeSent(request, false);
177227
if (typeof request[kInspectorRequestId] !== 'string') {
178228
return;
179229
}
@@ -205,6 +255,7 @@ function onClientResponseFinish({ request, response }) {
205255

206256
module.exports = registerDiagnosticChannels([
207257
['http.client.request.created', onClientRequestCreated],
258+
['http.client.request.start', onClientRequestStart],
208259
['http.client.request.bodyChunkSent', onClientRequestBodyChunkSent],
209260
['http.client.request.bodySent', onClientRequestBodySent],
210261
['http.client.request.error', onClientRequestError],

src/inspector/network_agent.cc

Lines changed: 149 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "inspector/network_resource_manager.h"
77
#include "inspector/protocol_helper.h"
88
#include "network_inspector.h"
9+
#include "node/inspector/protocol/Runtime.h"
910
#include "node_metadata.h"
1011
#include "util-inl.h"
1112
#include "uv.h"
@@ -15,9 +16,12 @@
1516
namespace node {
1617
namespace inspector {
1718

19+
using v8::Array;
20+
using v8::Context;
1821
using v8::HandleScope;
1922
using v8::Isolate;
2023
using v8::Local;
24+
using v8::MaybeLocal;
2125
using v8::Object;
2226
using v8::Uint8Array;
2327
using v8::Value;
@@ -29,6 +33,76 @@ static void ThrowEventError(v8::Isolate* isolate, const std::string& message) {
2933
v8::String::NewFromUtf8(isolate, message.c_str()).ToLocalChecked()));
3034
}
3135

36+
static MaybeLocal<Value> GetProperty(Local<Context> context,
37+
Local<Object> object,
38+
Local<Value> key) {
39+
return object->Get(context, key);
40+
}
41+
42+
static std::unique_ptr<protocol::Value> V8ToProtocolValue(
43+
Local<Context> context,
44+
Local<Value> value) {
45+
Isolate* isolate = Isolate::GetCurrent();
46+
if (value->IsNullOrUndefined()) {
47+
return protocol::Value::null();
48+
}
49+
if (value->IsBoolean()) {
50+
return protocol::FundamentalValue::create(value.As<v8::Boolean>()->Value());
51+
}
52+
if (value->IsInt32()) {
53+
return protocol::FundamentalValue::create(value.As<v8::Int32>()->Value());
54+
}
55+
if (value->IsNumber()) {
56+
return protocol::FundamentalValue::create(value.As<v8::Number>()->Value());
57+
}
58+
if (value->IsString()) {
59+
return protocol::StringValue::create(ToProtocolString(isolate, value));
60+
}
61+
if (value->IsArray()) {
62+
Local<Array> array = value.As<Array>();
63+
std::unique_ptr<protocol::ListValue> list = protocol::ListValue::create();
64+
list->reserve(array->Length());
65+
for (uint32_t i = 0; i < array->Length(); i++) {
66+
Local<Value> element;
67+
if (!array->Get(context, i).ToLocal(&element)) {
68+
return nullptr;
69+
}
70+
std::unique_ptr<protocol::Value> protocol_value =
71+
V8ToProtocolValue(context, element);
72+
if (!protocol_value) {
73+
return nullptr;
74+
}
75+
list->pushValue(std::move(protocol_value));
76+
}
77+
return list;
78+
}
79+
if (value->IsObject()) {
80+
Local<Object> object = value.As<Object>();
81+
Local<Array> property_names;
82+
if (!object->GetOwnPropertyNames(context).ToLocal(&property_names)) {
83+
return nullptr;
84+
}
85+
std::unique_ptr<protocol::DictionaryValue> dict =
86+
protocol::DictionaryValue::create();
87+
for (uint32_t i = 0; i < property_names->Length(); i++) {
88+
Local<Value> key;
89+
Local<Value> property;
90+
if (!property_names->Get(context, i).ToLocal(&key) ||
91+
!GetProperty(context, object, key).ToLocal(&property)) {
92+
return nullptr;
93+
}
94+
std::unique_ptr<protocol::Value> protocol_value =
95+
V8ToProtocolValue(context, property);
96+
if (!protocol_value) {
97+
return nullptr;
98+
}
99+
dict->setValue(ToProtocolString(isolate, key), std::move(protocol_value));
100+
}
101+
return dict;
102+
}
103+
return nullptr;
104+
}
105+
32106
// Create a protocol::Network::Headers from the v8 object.
33107
std::unique_ptr<protocol::Network::Headers>
34108
NetworkAgent::createHeadersFromObject(v8::Local<v8::Context> context,
@@ -65,6 +139,67 @@ NetworkAgent::createHeadersFromObject(v8::Local<v8::Context> context,
65139
return std::make_unique<protocol::Network::Headers>(std::move(dict));
66140
}
67141

142+
std::unique_ptr<protocol::Network::Initiator>
143+
NetworkAgent::createInitiatorFromObject(v8::Local<v8::Context> context,
144+
Local<Object> initiator_obj) {
145+
HandleScope handle_scope(Isolate::GetCurrent());
146+
Isolate* isolate = env_->isolate();
147+
148+
protocol::String type;
149+
if (!ObjectGetProtocolString(context, initiator_obj, "type").To(&type)) {
150+
ThrowEventError(isolate, "Missing initiator.type in event");
151+
return {};
152+
}
153+
154+
std::unique_ptr<protocol::Network::Initiator> initiator =
155+
protocol::Network::Initiator::create().setType(type).build();
156+
157+
Local<Object> stack_obj;
158+
if (ObjectGetObject(context, initiator_obj, "stack").ToLocal(&stack_obj)) {
159+
std::unique_ptr<protocol::Value> stack_value =
160+
V8ToProtocolValue(context, stack_obj);
161+
if (!stack_value) {
162+
ThrowEventError(isolate, "Invalid initiator.stack in event");
163+
return {};
164+
}
165+
166+
protocol::ErrorSupport errors;
167+
std::unique_ptr<v8_inspector::protocol::Runtime::API::StackTrace> stack =
168+
protocol::ValueConversions<
169+
v8_inspector::protocol::Runtime::API::StackTrace>::fromValue(
170+
stack_value.get(), &errors);
171+
if (!stack) {
172+
ThrowEventError(isolate, "Invalid initiator.stack in event");
173+
return {};
174+
}
175+
initiator->setStack(std::move(stack));
176+
}
177+
178+
protocol::String url;
179+
if (ObjectGetProtocolString(context, initiator_obj, "url").To(&url)) {
180+
initiator->setUrl(url);
181+
}
182+
183+
double line_number;
184+
if (ObjectGetDouble(context, initiator_obj, "lineNumber").To(&line_number)) {
185+
initiator->setLineNumber(line_number);
186+
}
187+
188+
double column_number;
189+
if (ObjectGetDouble(context, initiator_obj, "columnNumber")
190+
.To(&column_number)) {
191+
initiator->setColumnNumber(column_number);
192+
}
193+
194+
protocol::String request_id;
195+
if (ObjectGetProtocolString(context, initiator_obj, "requestId")
196+
.To(&request_id)) {
197+
initiator->setRequestId(request_id);
198+
}
199+
200+
return initiator;
201+
}
202+
68203
// Create a protocol::Network::Request from the v8 object.
69204
std::unique_ptr<protocol::Network::Request>
70205
NetworkAgent::createRequestFromObject(v8::Local<v8::Context> context,
@@ -460,12 +595,20 @@ void NetworkAgent::requestWillBeSent(v8::Local<v8::Context> context,
460595
return;
461596
}
462597

463-
std::unique_ptr<protocol::Network::Initiator> initiator =
464-
protocol::Network::Initiator::create()
465-
.setType(protocol::Network::Initiator::TypeEnum::Script)
466-
.setStack(
467-
v8_inspector_->captureStackTrace(true)->buildInspectorObject(0))
468-
.build();
598+
std::unique_ptr<protocol::Network::Initiator> initiator;
599+
Local<Object> initiator_obj;
600+
if (ObjectGetObject(context, params, "initiator").ToLocal(&initiator_obj)) {
601+
initiator = createInitiatorFromObject(context, initiator_obj);
602+
if (!initiator) {
603+
return;
604+
}
605+
} else {
606+
initiator = protocol::Network::Initiator::create()
607+
.setType(protocol::Network::Initiator::TypeEnum::Script)
608+
.setStack(v8_inspector_->captureStackTrace(true)
609+
->buildInspectorObject(0))
610+
.build();
611+
}
469612

470613
if (requests_.contains(request_id)) {
471614
// Duplicate entry, ignore it.

src/inspector/network_agent.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ class NetworkAgent : public protocol::Network::Backend {
8181
private:
8282
std::unique_ptr<protocol::Network::Headers> createHeadersFromObject(
8383
v8::Local<v8::Context> context, v8::Local<v8::Object> headers_obj);
84+
std::unique_ptr<protocol::Network::Initiator> createInitiatorFromObject(
85+
v8::Local<v8::Context> context, v8::Local<v8::Object> initiator_obj);
8486
std::unique_ptr<protocol::Network::Request> createRequestFromObject(
8587
v8::Local<v8::Context> context, v8::Local<v8::Object> request);
8688
std::unique_ptr<protocol::Network::Response> createResponseFromObject(

test/parallel/test-inspector-network-http.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,7 @@ async function testHttpGet() {
300300
requestWillBeSentFuture,
301301
responseReceivedFuture,
302302
loadingFinishedFuture,
303-
} = createRequestTracker(url, getDefaultResponseExpect(url), {
304-
hasPostData: true,
305-
});
303+
} = createRequestTracker(url, getDefaultResponseExpect(url));
306304

307305
http.get({
308306
host: '127.0.0.1',
@@ -326,9 +324,7 @@ async function testHttpGetWithAbsoluteUrlPath() {
326324
requestWillBeSentFuture,
327325
responseReceivedFuture,
328326
loadingFinishedFuture,
329-
} = createRequestTracker(url, getDefaultResponseExpect(url), {
330-
hasPostData: true,
331-
});
327+
} = createRequestTracker(url, getDefaultResponseExpect(url));
332328

333329
http.get({
334330
host: '127.0.0.1',
@@ -397,9 +393,7 @@ async function testHttpsGet() {
397393
requestWillBeSentFuture,
398394
responseReceivedFuture,
399395
loadingFinishedFuture,
400-
} = createRequestTracker(url, getDefaultResponseExpect(url), {
401-
hasPostData: true,
402-
});
396+
} = createRequestTracker(url, getDefaultResponseExpect(url));
403397

404398
https.get({
405399
host: '127.0.0.1',
@@ -424,7 +418,6 @@ async function testHttpError() {
424418
.then(([event]) => verifyRequestWillBeSent(event, {
425419
url,
426420
method: 'GET',
427-
hasPostData: true,
428421
}));
429422
session.on('Network.responseReceived', common.mustNotCall());
430423
session.on('Network.loadingFinished', common.mustNotCall());
@@ -447,7 +440,6 @@ async function testHttpsError() {
447440
.then(([event]) => verifyRequestWillBeSent(event, {
448441
url,
449442
method: 'GET',
450-
hasPostData: true,
451443
}));
452444
session.on('Network.responseReceived', common.mustNotCall());
453445
session.on('Network.loadingFinished', common.mustNotCall());

0 commit comments

Comments
 (0)