inspector: allow --inspect=host:port from js
PR-URL: https://github.com/nodejs/node/pull/13228 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
dcfbbacba8
commit
2791b360c1
@ -10,6 +10,30 @@ It can be accessed using:
|
||||
const inspector = require('inspector');
|
||||
```
|
||||
|
||||
## inspector.open([port[, host[, wait]]])
|
||||
|
||||
* port {number} Port to listen on for inspector connections. Optional,
|
||||
defaults to what was specified on the CLI.
|
||||
* host {string} Host to listen on for inspector connections. Optional,
|
||||
defaults to what was specified on the CLI.
|
||||
* wait {boolean} Block until a client has connected. Optional, defaults
|
||||
to false.
|
||||
|
||||
Activate inspector on host and port. Equivalent to `node
|
||||
--inspect=[[host:]port]`, but can be done programatically after node has
|
||||
started.
|
||||
|
||||
If wait is `true`, will block until a client has connected to the inspect port
|
||||
and flow control has been passed to the debugger client.
|
||||
|
||||
### inspector.close()
|
||||
|
||||
Deactivate the inspector. Blocks until there are no active connections.
|
||||
|
||||
### inspector.url()
|
||||
|
||||
Return the URL of the active inspector, or `undefined` if there is none.
|
||||
|
||||
## Class: inspector.Session
|
||||
|
||||
The `inspector.Session` is used for dispatching messages to the V8 inspector
|
||||
@ -110,6 +134,7 @@ with an error. [`session.connect()`] will need to be called to be able to send
|
||||
messages again. Reconnected session will lose all inspector state, such as
|
||||
enabled agents or configured breakpoints.
|
||||
|
||||
|
||||
[`session.connect()`]: #sessionconnect
|
||||
[`Debugger.paused`]: https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-paused
|
||||
[`EventEmitter`]: events.html#events_class_eventemitter
|
||||
|
@ -1,8 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const connect = process.binding('inspector').connect;
|
||||
const EventEmitter = require('events');
|
||||
const util = require('util');
|
||||
const { connect, open, url } = process.binding('inspector');
|
||||
|
||||
if (!connect)
|
||||
throw new Error('Inspector is not available');
|
||||
@ -83,5 +83,8 @@ class Session extends EventEmitter {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
open: (port, host, wait) => open(port, host, !!wait),
|
||||
close: process._debugEnd,
|
||||
url: url,
|
||||
Session
|
||||
};
|
||||
|
@ -562,6 +562,7 @@ bool Agent::Start(v8::Platform* platform, const char* path,
|
||||
// Ignore failure, SIGUSR1 won't work, but that should not block node start.
|
||||
StartDebugSignalHandler();
|
||||
if (options.inspector_enabled()) {
|
||||
// This will return false if listen failed on the inspector port.
|
||||
return StartIoThread(options.wait_for_connect());
|
||||
}
|
||||
return true;
|
||||
@ -666,6 +667,50 @@ void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
|
||||
channel->schedulePauseOnNextStatement(reason);
|
||||
}
|
||||
|
||||
void Open(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
inspector::Agent* agent = env->inspector_agent();
|
||||
bool wait_for_connect = false;
|
||||
|
||||
if (args.Length() > 0 && args[0]->IsUint32()) {
|
||||
uint32_t port = args[0]->Uint32Value();
|
||||
agent->options().set_port(static_cast<int>(port));
|
||||
}
|
||||
|
||||
if (args.Length() > 1 && args[1]->IsString()) {
|
||||
node::Utf8Value host(env->isolate(), args[1].As<String>());
|
||||
agent->options().set_host_name(*host);
|
||||
}
|
||||
|
||||
if (args.Length() > 2 && args[2]->IsBoolean()) {
|
||||
wait_for_connect = args[2]->BooleanValue();
|
||||
}
|
||||
|
||||
agent->StartIoThread(wait_for_connect);
|
||||
}
|
||||
|
||||
void Url(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
inspector::Agent* agent = env->inspector_agent();
|
||||
inspector::InspectorIo* io = agent->io();
|
||||
|
||||
if (!io) return;
|
||||
|
||||
std::vector<std::string> ids = io->GetTargetIds();
|
||||
|
||||
if (ids.empty()) return;
|
||||
|
||||
std::string url = "ws://";
|
||||
url += io->host();
|
||||
url += ":";
|
||||
url += std::to_string(io->port());
|
||||
url += "/";
|
||||
url += ids[0];
|
||||
|
||||
args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str()));
|
||||
}
|
||||
|
||||
|
||||
// static
|
||||
void Agent::InitInspector(Local<Object> target, Local<Value> unused,
|
||||
Local<Context> context, void* priv) {
|
||||
@ -675,11 +720,13 @@ void Agent::InitInspector(Local<Object> target, Local<Value> unused,
|
||||
if (agent->debug_options_.wait_for_connect())
|
||||
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
|
||||
env->SetMethod(target, "connect", ConnectJSBindingsSession);
|
||||
env->SetMethod(target, "open", Open);
|
||||
env->SetMethod(target, "url", Url);
|
||||
}
|
||||
|
||||
void Agent::RequestIoThreadStart() {
|
||||
// We need to attempt to interrupt V8 flow (in case Node is running
|
||||
// continuous JS code) and to wake up libuv thread (in case Node is wating
|
||||
// continuous JS code) and to wake up libuv thread (in case Node is waiting
|
||||
// for IO events)
|
||||
uv_async_send(&start_io_thread_async);
|
||||
v8::Isolate* isolate = parent_env_->isolate();
|
||||
|
@ -95,6 +95,8 @@ class Agent {
|
||||
// Calls StartIoThread() from off the main thread.
|
||||
void RequestIoThreadStart();
|
||||
|
||||
DebugOptions& options() { return debug_options_; }
|
||||
|
||||
private:
|
||||
node::Environment* parent_env_;
|
||||
std::unique_ptr<NodeInspectorClient> client_;
|
||||
|
@ -354,6 +354,10 @@ void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id,
|
||||
NotifyMessageReceived();
|
||||
}
|
||||
|
||||
std::vector<std::string> InspectorIo::GetTargetIds() const {
|
||||
return delegate_ ? delegate_->GetTargetIds() : std::vector<std::string>();
|
||||
}
|
||||
|
||||
void InspectorIo::WaitForFrontendMessageWhilePaused() {
|
||||
dispatching_messages_ = false;
|
||||
Mutex::ScopedLock scoped_lock(state_lock_);
|
||||
|
@ -72,6 +72,8 @@ class InspectorIo {
|
||||
}
|
||||
|
||||
int port() const { return port_; }
|
||||
std::string host() const { return options_.host_name(); }
|
||||
std::vector<std::string> GetTargetIds() const;
|
||||
|
||||
private:
|
||||
template <typename Action>
|
||||
@ -152,7 +154,6 @@ class InspectorIo {
|
||||
|
||||
std::string script_name_;
|
||||
std::string script_path_;
|
||||
const std::string id_;
|
||||
const bool wait_for_connect_;
|
||||
int port_;
|
||||
|
||||
|
@ -264,6 +264,9 @@ static struct {
|
||||
#if HAVE_INSPECTOR
|
||||
bool StartInspector(Environment *env, const char* script_path,
|
||||
const node::DebugOptions& options) {
|
||||
// Inspector agent can't fail to start, but if it was configured to listen
|
||||
// right away on the websocket port and fails to bind/etc, this will return
|
||||
// false.
|
||||
return env->inspector_agent()->Start(platform_, script_path, options);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ class DebugOptions {
|
||||
}
|
||||
bool wait_for_connect() const { return break_first_line_; }
|
||||
std::string host_name() const { return host_name_; }
|
||||
void set_host_name(std::string host_name) { host_name_ = host_name; }
|
||||
int port() const;
|
||||
void set_port(int port) { port_ = port; }
|
||||
|
||||
|
104
test/parallel/test-inspector-open.js
Normal file
104
test/parallel/test-inspector-open.js
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
// Test inspector open()/close()/url() API. It uses ephemeral ports so can be
|
||||
// run safely in parallel.
|
||||
|
||||
const assert = require('assert');
|
||||
const fork = require('child_process').fork;
|
||||
const net = require('net');
|
||||
const url = require('url');
|
||||
|
||||
common.skipIfInspectorDisabled();
|
||||
|
||||
if (process.env.BE_CHILD)
|
||||
return beChild();
|
||||
|
||||
const child = fork(__filename, {env: {BE_CHILD: 1}});
|
||||
|
||||
child.once('message', common.mustCall((msg) => {
|
||||
assert.strictEqual(msg.cmd, 'started');
|
||||
|
||||
child.send({cmd: 'open', args: [0]});
|
||||
child.once('message', common.mustCall(firstOpen));
|
||||
}));
|
||||
|
||||
let firstPort;
|
||||
|
||||
function firstOpen(msg) {
|
||||
assert.strictEqual(msg.cmd, 'url');
|
||||
const port = url.parse(msg.url).port;
|
||||
ping(port, (err) => {
|
||||
assert.ifError(err);
|
||||
// Inspector is already open, and won't be reopened, so args don't matter.
|
||||
child.send({cmd: 'open', args: []});
|
||||
child.once('message', common.mustCall(tryToOpenWhenOpen));
|
||||
firstPort = port;
|
||||
});
|
||||
}
|
||||
|
||||
function tryToOpenWhenOpen(msg) {
|
||||
assert.strictEqual(msg.cmd, 'url');
|
||||
const port = url.parse(msg.url).port;
|
||||
// Reopen didn't do anything, the port was already open, and has not changed.
|
||||
assert.strictEqual(port, firstPort);
|
||||
ping(port, (err) => {
|
||||
assert.ifError(err);
|
||||
child.send({cmd: 'close'});
|
||||
child.once('message', common.mustCall(closeWhenOpen));
|
||||
});
|
||||
}
|
||||
|
||||
function closeWhenOpen(msg) {
|
||||
assert.strictEqual(msg.cmd, 'url');
|
||||
assert.strictEqual(msg.url, undefined);
|
||||
ping(firstPort, (err) => {
|
||||
assert(err);
|
||||
child.send({cmd: 'close'});
|
||||
child.once('message', common.mustCall(tryToCloseWhenClosed));
|
||||
});
|
||||
}
|
||||
|
||||
function tryToCloseWhenClosed(msg) {
|
||||
assert.strictEqual(msg.cmd, 'url');
|
||||
assert.strictEqual(msg.url, undefined);
|
||||
child.send({cmd: 'open', args: []});
|
||||
child.once('message', common.mustCall(reopenAfterClose));
|
||||
}
|
||||
|
||||
function reopenAfterClose(msg) {
|
||||
assert.strictEqual(msg.cmd, 'url');
|
||||
const port = url.parse(msg.url).port;
|
||||
assert.notStrictEqual(port, firstPort);
|
||||
ping(port, (err) => {
|
||||
assert.ifError(err);
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
function ping(port, callback) {
|
||||
net.connect(port)
|
||||
.on('connect', function() { close(this); })
|
||||
.on('error', function(err) { close(this, err); });
|
||||
|
||||
function close(self, err) {
|
||||
self.end();
|
||||
self.on('close', () => callback(err));
|
||||
}
|
||||
}
|
||||
|
||||
function beChild() {
|
||||
const inspector = require('inspector');
|
||||
|
||||
process.send({cmd: 'started'});
|
||||
|
||||
process.on('message', (msg) => {
|
||||
if (msg.cmd === 'open') {
|
||||
inspector.open(...msg.args);
|
||||
}
|
||||
if (msg.cmd === 'close') {
|
||||
inspector.close();
|
||||
}
|
||||
process.send({cmd: 'url', url: inspector.url()});
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user