-
Notifications
You must be signed in to change notification settings - Fork 231
Feat/network #282
base: master
Are you sure you want to change the base?
Feat/network #282
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here (e.g. What to do if you already signed the CLAIndividual signers
Corporate signers
ℹ️ Googlers: Go here for more info. |
It looks exciting. Thanks for doing this! Do you think that it is ready for review or are you going to polish it first? |
@ak239 thanks for checking. It's definitely not ready (yet) for being reviewed. I'm going to refactor the code. But it's gonna be great if you can check to see whether you like the approach/feature in the meantime. |
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
CLAs look good, thanks! ℹ️ Googlers: Go here for more info. |
@ak239 I think this PR is ready for review 🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your work! I tried it out with this simple script to make a request to debug on Ubuntu 18.04 with node v10.16.0:
const http = require('http');
(async () => {
const options = new URL('http://google.com');
console.log('making http request to: ' + options);
const req = http.request(options, res => {
console.log('response: ' + res.statusCode + ' ' + JSON.stringify(res.headers, null, 2));
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
console.log('response body:\n' + data);
});
res.on('error', error => {
console.log('error reading response body: ' + error);
});
});
req.on('error', error => {
console.log('error sending request: ' + error);
});
req.end();
})();
But I got this error when I ran it with ndb:
making http request to: http://google.com/
/home/jarhar/ndb/lib/preload/ndb/httpMonkeyPatching.js:15
Object.keys(req.headers).reduce((acc, k) => {
^
TypeError: Cannot convert undefined or null to object
at Function.keys (<anonymous>)
at formatRequestHeaders (/home/jarhar/ndb/lib/preload/ndb/httpMonkeyPatching.js:15:10)
at ClientRequest.res (/home/jarhar/ndb/lib/preload/ndb/httpMonkeyPatching.js:51:18)
at Object.onceWrapper (events.js:286:20)
at ClientRequest.emit (events.js:198:13)
at HTTPParser.parserOnIncomingClient [as onIncoming] (_http_client.js:556:21)
at HTTPParser.parserOnHeadersComplete (_http_common.js:109:17)
at Socket.socketOnData (_http_client.js:442:20)
at Socket.emit (events.js:198:13)
at addChunk (_stream_readable.js:288:12)
@josepharhar good catch. I will look into it. |
@josepharhar I fixed the issue. But the preview tabs doesn't seem to work with html response. Will double check it this evening. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your effort! We need one more big refactoring before we can merge it. Feel free to ask any questions.
front_end/ndb/NdbMain.js
Outdated
@@ -5,14 +5,20 @@ | |||
*/ | |||
|
|||
Ndb.nodeExecPath = function() { | |||
if (!Ndb._nodeExecPathPromise) | |||
Ndb._nodeExecPathPromise = Ndb.backend.which('node').then(result => result.resolvedPath); | |||
if (!Ndb._nodeExecPathPromise) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please undo these formatting changes, it will simplify review.
front_end/ndb/NdbMain.js
Outdated
@@ -172,9 +198,21 @@ Ndb.NodeProcessManager = class extends Common.Object { | |||
static async create(targetManager) { | |||
const manager = new Ndb.NodeProcessManager(targetManager); | |||
manager._service = await Ndb.backend.createService('ndd_service.js', rpc.handle(manager)); | |||
InspectorFrontendHost.sendMessageToBackend = manager.sendMessageToBackend.bind(manager); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line was removed in master and I believe that you can remove it..
front_end/ndb/NdbMain.js
Outdated
* | ||
* @return {Promise} void | ||
*/ | ||
async sendMessageToBackend(message) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.. and this function.
@@ -0,0 +1,153 @@ | |||
const zlib = require('zlib'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to use another way to inject this script, and another channel for inspector node process to speak with DevTools frontend. We already have channel between node process and frontend - devtools protocol.
To inject this script please do following:
- add httpMonkeyPatchingSource method to backend.js, this method returns source of this file.
Ndb.NodeProcessManager.detected
method gets source of this script and inject it to the page using following snippet:
target.runtimeAgent().invoke_evaluate({
expression: await Ndb.backend.httpMonkeyPatchingSource()
});
After these steps we can inject monkey patching script to any inspected process. Second step is how we can build a channel. We can use Runtime.evaluate
with awaitPromise: true
flag to build a channel. Monkey patching script gets following code:
let messages = [];
let messageAdded = null;
// this function is our instrumentation, to report anything to frontend - call it instead of process.send
function reportMessage(message) {
messages.push(message);
if (messageAdded) {
setTimeout(messageAdded, 0);
messageAdded = null;
}
}
// this function should be called from frontend in the loop using `target.runtimeAgent().invoke_evaluate`
process._getNetworkMessages = async function() {
if (!messages.length)
await new Promise(resolve => messageAdded = resolve);
return messages.splice(0);
}
}
Frontend calls in the loop following code:
while (true) {
const messages = await target.runtimeAgent().invoke_evaluate({
expression: 'process._getNetworkMessages()', awaitPromise: true
});
// ... process these messages ...
}
Feel free to ask any questions! At the same time we can merge your pull request and I will refactor it.
services/ndd_service.js
Outdated
NODE_PATH: `${process.env.NODE_PATH || ''}${path.delimiter}${path.join(__dirname, '..', 'lib', 'preload')}`, | ||
NDD_IPC: this._pipe | ||
}; | ||
} | ||
|
||
sendMessage(rawMessage) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please take a look on my previous comment to avoid changes in ndd_service.js.
@ak239 thanks for such detailed comments. I will address all of them. |
front_end/ndb/NdbMain.js
Outdated
// this doesnt work | ||
// target.runtimeAgent().invoke_evaluate({ | ||
// expression: ` | ||
// const zlib = require('http'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ak239 Is the script below will be evaluated in the inspected script's context? I followed your suggestion but it didn't work (I think it's because of the import
)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The easiest way to get proper require
available is calling Runtime.evaluate
with includeCommandLineAPI
flag:
target.runtimeAgent().invoke_evaluate({
expression: `require('zlib')`,
includeCommandLineAPI: true
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was what I missed. I will try it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It did work 🎉
front_end/ndb/NdbMain.js
Outdated
// but this does | ||
// target.runtimeAgent().invoke_evaluate({ | ||
// expression: ` | ||
// console.log('foo'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.. this doesn't have import
and it worked. I could see the console in the ndb
log, but the script's context was VMxx
. Therefore, I'm not sure we could monkey patch the http/https request this way. Please correct me if I'm wrong.
if (parsedMes.method !== 'Network.getResponseBody') | ||
return; | ||
|
||
const mes = await target.runtimeAgent().invoke_evaluate({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ak239 how can I have target.runtimeAgent()
here?...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that we need some help from @josepharhar. We need to find some nicer way to avoid hacking network model.
In theory we can register our own network model, that will implement most stuff on top of runtime agent. Please take a look on NodeWorker
or NodeRuntime
models. There are examples how we can add custom models, in this case we will add our own NetworkModel implementation and I hope that DevTools frontend will use it as is.
@josepharhar do you have any suggestion how we can do it better and will it work if node target will just have own NetworkModel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@khanghoang could you rebase your change on top of latest master? It has separated Ndb.Connection
class. Alternative idea that will work to me, add Ndb.Connection.addInterceptor({sendRawMessage: function(string):boolean})
. If interceptor is added than Ndb.Connection.sendRawMessage
first tries to process message using one of interceptor and iff all interceptors return false will send this message to this._channel
.
For Network
you can add network interceptor in Ndb.NodeProcessManager.detected
, something like:
Ndb.NetworkInterceptor = class {
constructor() {
this._buffer = [];
this._target = null;
}
sendRawMessage(message) {
if (!JSON.parse(message).method.startsWith('Network.'))
return false;
if (!this._target)
this._buffer.push(message);
else
// do logic
}
setTarget(target) {
this._target = target;
// do logic with buffer here
}
}
// inside detected
const connection = await Ndb.Connection.create(channel);
const interceptor = new Ndb.NetworkInterceptor();
connection.addInterceptor(interceptor);
// createTarget can send messages - it is why we need to cache them in buffer.
const target = this._targetManager.createTarget(
info.id, userFriendlyName(info), SDK.Target.Type.Node,
this._targetManager.targetById(info.ppid) || this._targetManager.mainTarget(), undefined, false, connection);
interceptor.setTarget(target);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think having our own NetworkManager/NetworkModel for node sounds like a great idea, and seems like a step in the right direction of eventually forking the entire network panel. If I understand correctly, this means that instead of implementing the CDP interface of Network, we will instead be implementing the public methods of SDK.NetworkManager and emitting the events it emits in order to talk to the network panel. This sounds good to me because its closer to the network panel itself and is probably simpler. I'm not sure exactly how to do this within ndb, but I'm sure you know how @ak239
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@khanghoang I created proof of concept of network interceptor - feel free to use it - I hope that it covers your use case - 5aef915
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ak239 @josepharhar thanks a lot. I will check it.
}; | ||
|
||
while (true) { | ||
const message = await target.runtimeAgent().invoke_evaluate({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and here as well?
All (the pull request submitter and all commit authors) CLAs are signed, but one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that by leaving a comment that contains only Note to project maintainer: There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent. In those cases, you can manually confirm consent of the commit author(s), and set the ℹ️ Googlers: Go here for more info. |
f53a5c6
to
918828b
Compare
CLAs look good, thanks! ℹ️ Googlers: Go here for more info. |
@ak239 I have been trying to debug it but couldn't figure it out myself. Sometimes, it works Besides that, I didn't fully understand some parts of the code. I think I suppose to use this method to send the response body for the preview tab. I tried but it didn't work, I debugged and the "sessions/callbacks" array is empty. But calling this works. Also, I need to register a callback when the user clicks on the item in the requests list. I use this but I don't think it's the proper way. Any suggestions? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I debugged DevTools code and found that NetworkManager by default uses the main target Network domain. In the case of ndb
main target is not a real target, it means that I need to add interceptor for this target and interceptor code should manually fetch all available targets and send a command to all of them and get responses from all of them. I will prepare an interceptor this week.
It is the main reasons why you are observing strange issues with implementation.
@ak239 did you have a chance to take a look at it? |
@ak239 I want to follow up on this PR. Let me know what else I can do. |
@ak239 ping you one last time. |
@ak239 I know you're super busy. Please let me know if you want me to make any code changes. |
Good work guys, this will make all node devs life easier! Keep it up! |
What is the status on reviewing this and potentially getting it merged? I would really appreciate this feature being added to ndp. Thank! |
Love this PR - would be thrilled to see it merged! |
This Network tab is what i`ve been looking for years, thanks @khanghoang for this, hope this get merged soon. |
hey folks, you can try this feature today via a custom build by running |
@ak239 do you need assistance with the interceptor? |
Hey Guys, any update on this PR? @khanghoang I am getting this error on ndb-plus, works fine on ndb (using yarn) |
@superhit0 it's because macOS has changed the permission type names :( |
@khanghoang probably yeah |
Any update on this? |
我觉得这蛮重要的...为什么不更新呢 |
Add the network tab to ndb
Preview JSON response
Preview image
Log request body