inspector: added --inspect-publish-uid

This flag specifies how inspector websocket url should be reported.
Tthre options are supported:
- stderr - reports websocket as a message to stderr,
- http - exposes /json/list endpoint that contains inspector websocket
  url,
- binding - require('inspector').url().

Related discussion: https://github.com/nodejs/diagnostics/issues/303

PR-URL: https://github.com/nodejs/node/pull/27741
Reviewed-By: Eugene Ostroukhov <eostroukhov@google.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
Aleksei Koziatinskii 2019-05-16 15:33:09 -07:00
parent 7e18c650de
commit f0018a5152
10 changed files with 148 additions and 22 deletions

View File

@ -389,6 +389,13 @@ default) is not firewall-protected.**
See the [debugging security implications][] section for more information. See the [debugging security implications][] section for more information.
### `--inspect-publish-uid=stderr,http`
Specify ways of the inspector web socket url exposure.
By default inspector websocket url is available in stderr and under `/json/list`
endpoint on `http://host:port/json/list`.
### `--loader=file` ### `--loader=file`
<!-- YAML <!-- YAML
added: v9.0.0 added: v9.0.0
@ -980,6 +987,7 @@ Node.js options that are allowed are:
- `--inspect` - `--inspect`
- `--inspect-brk` - `--inspect-brk`
- `--inspect-port` - `--inspect-port`
- `--inspect-publish-uid`
- `--loader` - `--loader`
- `--max-http-header-size` - `--max-http-header-size`
- `--napi-modules` - `--napi-modules`

View File

@ -792,7 +792,10 @@ bool Agent::StartIoThread() {
CHECK_NOT_NULL(client_); CHECK_NOT_NULL(client_);
io_ = InspectorIo::Start(client_->getThreadHandle(), path_, host_port_); io_ = InspectorIo::Start(client_->getThreadHandle(),
path_,
host_port_,
debug_options_.inspect_publish_uid);
if (io_ == nullptr) { if (io_ == nullptr) {
return false; return false;
} }

View File

@ -242,9 +242,13 @@ class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
std::unique_ptr<InspectorIo> InspectorIo::Start( std::unique_ptr<InspectorIo> InspectorIo::Start(
std::shared_ptr<MainThreadHandle> main_thread, std::shared_ptr<MainThreadHandle> main_thread,
const std::string& path, const std::string& path,
std::shared_ptr<HostPort> host_port) { std::shared_ptr<HostPort> host_port,
const InspectPublishUid& inspect_publish_uid) {
auto io = std::unique_ptr<InspectorIo>( auto io = std::unique_ptr<InspectorIo>(
new InspectorIo(main_thread, path, host_port)); new InspectorIo(main_thread,
path,
host_port,
inspect_publish_uid));
if (io->request_queue_->Expired()) { // Thread is not running if (io->request_queue_->Expired()) { // Thread is not running
return nullptr; return nullptr;
} }
@ -253,9 +257,11 @@ std::unique_ptr<InspectorIo> InspectorIo::Start(
InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread, InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread,
const std::string& path, const std::string& path,
std::shared_ptr<HostPort> host_port) std::shared_ptr<HostPort> host_port,
const InspectPublishUid& inspect_publish_uid)
: main_thread_(main_thread), : main_thread_(main_thread),
host_port_(host_port), host_port_(host_port),
inspect_publish_uid_(inspect_publish_uid),
thread_(), thread_(),
script_name_(path), script_name_(path),
id_(GenerateID()) { id_(GenerateID()) {
@ -293,7 +299,8 @@ void InspectorIo::ThreadMain() {
InspectorSocketServer server(std::move(delegate), InspectorSocketServer server(std::move(delegate),
&loop, &loop,
host_port_->host(), host_port_->host(),
host_port_->port()); host_port_->port(),
inspect_publish_uid_);
request_queue_ = queue->handle(); request_queue_ = queue->handle();
// Its lifetime is now that of the server delegate // Its lifetime is now that of the server delegate
queue.reset(); queue.reset();

View File

@ -48,7 +48,8 @@ class InspectorIo {
static std::unique_ptr<InspectorIo> Start( static std::unique_ptr<InspectorIo> Start(
std::shared_ptr<MainThreadHandle> main_thread, std::shared_ptr<MainThreadHandle> main_thread,
const std::string& path, const std::string& path,
std::shared_ptr<HostPort> host_port); std::shared_ptr<HostPort> host_port,
const InspectPublishUid& inspect_publish_uid);
// Will block till the transport thread shuts down // Will block till the transport thread shuts down
~InspectorIo(); ~InspectorIo();
@ -61,7 +62,8 @@ class InspectorIo {
private: private:
InspectorIo(std::shared_ptr<MainThreadHandle> handle, InspectorIo(std::shared_ptr<MainThreadHandle> handle,
const std::string& path, const std::string& path,
std::shared_ptr<HostPort> host_port); std::shared_ptr<HostPort> host_port,
const InspectPublishUid& inspect_publish_uid);
// Wrapper for agent->ThreadMain() // Wrapper for agent->ThreadMain()
static void ThreadMain(void* agent); static void ThreadMain(void* agent);
@ -76,6 +78,7 @@ class InspectorIo {
// running // running
std::shared_ptr<RequestQueue> request_queue_; std::shared_ptr<RequestQueue> request_queue_;
std::shared_ptr<HostPort> host_port_; std::shared_ptr<HostPort> host_port_;
InspectPublishUid inspect_publish_uid_;
// The IO thread runs its own uv_loop to implement the TCP server off // The IO thread runs its own uv_loop to implement the TCP server off
// the main thread. // the main thread.

View File

@ -94,14 +94,20 @@ const char* MatchPathSegment(const char* path, const char* expected) {
return nullptr; return nullptr;
} }
void SendHttpResponse(InspectorSocket* socket, const std::string& response) { void SendHttpResponse(InspectorSocket* socket,
const char HEADERS[] = "HTTP/1.0 200 OK\r\n" const std::string& response,
int code) {
const char HEADERS[] = "HTTP/1.0 %d OK\r\n"
"Content-Type: application/json; charset=UTF-8\r\n" "Content-Type: application/json; charset=UTF-8\r\n"
"Cache-Control: no-cache\r\n" "Cache-Control: no-cache\r\n"
"Content-Length: %zu\r\n" "Content-Length: %zu\r\n"
"\r\n"; "\r\n";
char header[sizeof(HEADERS) + 20]; char header[sizeof(HEADERS) + 20];
int header_len = snprintf(header, sizeof(header), HEADERS, response.size()); int header_len = snprintf(header,
sizeof(header),
HEADERS,
code,
response.size());
socket->Write(header, header_len); socket->Write(header, header_len);
socket->Write(response.data(), response.size()); socket->Write(response.data(), response.size());
} }
@ -110,7 +116,11 @@ void SendVersionResponse(InspectorSocket* socket) {
std::map<std::string, std::string> response; std::map<std::string, std::string> response;
response["Browser"] = "node.js/" NODE_VERSION; response["Browser"] = "node.js/" NODE_VERSION;
response["Protocol-Version"] = "1.1"; response["Protocol-Version"] = "1.1";
SendHttpResponse(socket, MapToString(response)); SendHttpResponse(socket, MapToString(response), 200);
}
void SendHttpNotFound(InspectorSocket* socket) {
SendHttpResponse(socket, "", 404);
} }
void SendProtocolJson(InspectorSocket* socket) { void SendProtocolJson(InspectorSocket* socket) {
@ -131,7 +141,7 @@ void SendProtocolJson(InspectorSocket* socket) {
CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH)); CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH));
CHECK_EQ(0, strm.avail_out); CHECK_EQ(0, strm.avail_out);
CHECK_EQ(Z_OK, inflateEnd(&strm)); CHECK_EQ(Z_OK, inflateEnd(&strm));
SendHttpResponse(socket, data); SendHttpResponse(socket, data, 200);
} }
} // namespace } // namespace
@ -224,8 +234,9 @@ void PrintDebuggerReadyMessage(
const std::string& host, const std::string& host,
const std::vector<InspectorSocketServer::ServerSocketPtr>& server_sockets, const std::vector<InspectorSocketServer::ServerSocketPtr>& server_sockets,
const std::vector<std::string>& ids, const std::vector<std::string>& ids,
bool publish_uid_stderr,
FILE* out) { FILE* out) {
if (out == nullptr) { if (!publish_uid_stderr || out == nullptr) {
return; return;
} }
for (const auto& server_socket : server_sockets) { for (const auto& server_socket : server_sockets) {
@ -241,9 +252,15 @@ void PrintDebuggerReadyMessage(
InspectorSocketServer::InspectorSocketServer( InspectorSocketServer::InspectorSocketServer(
std::unique_ptr<SocketServerDelegate> delegate, uv_loop_t* loop, std::unique_ptr<SocketServerDelegate> delegate, uv_loop_t* loop,
const std::string& host, int port, FILE* out) const std::string& host, int port,
: loop_(loop), delegate_(std::move(delegate)), host_(host), port_(port), const InspectPublishUid& inspect_publish_uid, FILE* out)
next_session_id_(0), out_(out) { : loop_(loop),
delegate_(std::move(delegate)),
host_(host),
port_(port),
inspect_publish_uid_(inspect_publish_uid),
next_session_id_(0),
out_(out) {
delegate_->AssignServer(this); delegate_->AssignServer(this);
state_ = ServerState::kNew; state_ = ServerState::kNew;
} }
@ -280,8 +297,11 @@ void InspectorSocketServer::SessionTerminated(int session_id) {
if (connected_sessions_.empty()) { if (connected_sessions_.empty()) {
if (was_attached && state_ == ServerState::kRunning if (was_attached && state_ == ServerState::kRunning
&& !server_sockets_.empty()) { && !server_sockets_.empty()) {
PrintDebuggerReadyMessage(host_, server_sockets_, PrintDebuggerReadyMessage(host_,
delegate_->GetTargetIds(), out_); server_sockets_,
delegate_->GetTargetIds(),
inspect_publish_uid_.console,
out_);
} }
if (state_ == ServerState::kStopped) { if (state_ == ServerState::kStopped) {
delegate_.reset(); delegate_.reset();
@ -294,6 +314,10 @@ bool InspectorSocketServer::HandleGetRequest(int session_id,
const std::string& path) { const std::string& path) {
SocketSession* session = Session(session_id); SocketSession* session = Session(session_id);
InspectorSocket* socket = session->ws_socket(); InspectorSocket* socket = session->ws_socket();
if (!inspect_publish_uid_.http) {
SendHttpNotFound(socket);
return true;
}
const char* command = MatchPathSegment(path.c_str(), "/json"); const char* command = MatchPathSegment(path.c_str(), "/json");
if (command == nullptr) if (command == nullptr)
return false; return false;
@ -342,7 +366,7 @@ void InspectorSocketServer::SendListResponse(InspectorSocket* socket,
formatted_address); formatted_address);
target_map["webSocketDebuggerUrl"] = FormatAddress(detected_host, id, true); target_map["webSocketDebuggerUrl"] = FormatAddress(detected_host, id, true);
} }
SendHttpResponse(socket, MapsToString(response)); SendHttpResponse(socket, MapsToString(response), 200);
} }
std::string InspectorSocketServer::GetFrontendURL(bool is_compat, std::string InspectorSocketServer::GetFrontendURL(bool is_compat,
@ -397,8 +421,11 @@ bool InspectorSocketServer::Start() {
} }
delegate_.swap(delegate_holder); delegate_.swap(delegate_holder);
state_ = ServerState::kRunning; state_ = ServerState::kRunning;
PrintDebuggerReadyMessage(host_, server_sockets_, PrintDebuggerReadyMessage(host_,
delegate_->GetTargetIds(), out_); server_sockets_,
delegate_->GetTargetIds(),
inspect_publish_uid_.console,
out_);
return true; return true;
} }

View File

@ -43,6 +43,7 @@ class InspectorSocketServer {
uv_loop_t* loop, uv_loop_t* loop,
const std::string& host, const std::string& host,
int port, int port,
const InspectPublishUid& inspect_publish_uid,
FILE* out = stderr); FILE* out = stderr);
~InspectorSocketServer(); ~InspectorSocketServer();
@ -88,6 +89,7 @@ class InspectorSocketServer {
std::unique_ptr<SocketServerDelegate> delegate_; std::unique_ptr<SocketServerDelegate> delegate_;
const std::string host_; const std::string host_;
int port_; int port_;
InspectPublishUid inspect_publish_uid_;
std::vector<ServerSocketPtr> server_sockets_; std::vector<ServerSocketPtr> server_sockets_;
std::map<int, std::pair<std::string, std::unique_ptr<SocketSession>>> std::map<int, std::pair<std::string, std::unique_ptr<SocketSession>>>
connected_sessions_; connected_sessions_;

View File

@ -44,6 +44,21 @@ void DebugOptions::CheckOptions(std::vector<std::string>* errors) {
errors->push_back("[DEP0062]: `node --inspect --debug-brk` is deprecated. " errors->push_back("[DEP0062]: `node --inspect --debug-brk` is deprecated. "
"Please use `node --inspect-brk` instead."); "Please use `node --inspect-brk` instead.");
} }
std::vector<std::string> destinations =
SplitString(inspect_publish_uid_string, ',');
inspect_publish_uid.console = false;
inspect_publish_uid.http = false;
for (const std::string& destination : destinations) {
if (destination == "stderr") {
inspect_publish_uid.console = true;
} else if (destination == "http") {
inspect_publish_uid.http = true;
} else {
errors->push_back("--inspect-publish-uid destination can be "
"stderr or http");
}
}
} }
void PerProcessOptions::CheckOptions(std::vector<std::string>* errors) { void PerProcessOptions::CheckOptions(std::vector<std::string>* errors) {
@ -276,6 +291,12 @@ DebugOptionsParser::DebugOptionsParser() {
AddOption("--debug-brk", "", &DebugOptions::break_first_line); AddOption("--debug-brk", "", &DebugOptions::break_first_line);
Implies("--debug-brk", "--debug"); Implies("--debug-brk", "--debug");
AddAlias("--debug-brk=", { "--inspect-port", "--debug-brk" }); AddAlias("--debug-brk=", { "--inspect-port", "--debug-brk" });
AddOption("--inspect-publish-uid",
"comma separated list of destinations for inspector uid"
"(default: stderr,http)",
&DebugOptions::inspect_publish_uid_string,
kAllowedInEnvironment);
} }
EnvironmentOptionsParser::EnvironmentOptionsParser() { EnvironmentOptionsParser::EnvironmentOptionsParser() {

View File

@ -50,6 +50,11 @@ class Options {
virtual ~Options() = default; virtual ~Options() = default;
}; };
struct InspectPublishUid {
bool console;
bool http;
};
// These options are currently essentially per-Environment, but it can be nice // These options are currently essentially per-Environment, but it can be nice
// to keep them separate since they are a group of options applying to a very // to keep them separate since they are a group of options applying to a very
// specific part of Node. It might also make more sense for them to be // specific part of Node. It might also make more sense for them to be
@ -70,6 +75,10 @@ class DebugOptions : public Options {
bool break_first_line = false; bool break_first_line = false;
// --inspect-brk-node // --inspect-brk-node
bool break_node_first_line = false; bool break_node_first_line = false;
// --inspect-publish-uid
std::string inspect_publish_uid_string = "stderr,http";
InspectPublishUid inspect_publish_uid;
enum { kDefaultInspectorPort = 9229 }; enum { kDefaultInspectorPort = 9229 };

View File

@ -1,6 +1,7 @@
#include "inspector_socket_server.h" #include "inspector_socket_server.h"
#include "node.h" #include "node.h"
#include "node_options.h"
#include "util-inl.h" #include "util-inl.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
@ -358,8 +359,11 @@ ServerHolder::ServerHolder(bool has_targets, uv_loop_t* loop,
targets = { MAIN_TARGET_ID }; targets = { MAIN_TARGET_ID };
std::unique_ptr<TestSocketServerDelegate> delegate( std::unique_ptr<TestSocketServerDelegate> delegate(
new TestSocketServerDelegate(this, targets)); new TestSocketServerDelegate(this, targets));
node::InspectPublishUid inspect_publish_uid;
inspect_publish_uid.console = true;
inspect_publish_uid.http = true;
server_ = std::make_unique<InspectorSocketServer>( server_ = std::make_unique<InspectorSocketServer>(
std::move(delegate), loop, host, port, out); std::move(delegate), loop, host, port, inspect_publish_uid, out);
} }
static void TestHttpRequest(int port, const std::string& path, static void TestHttpRequest(int port, const std::string& path,

View File

@ -0,0 +1,42 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const assert = require('assert');
const { spawnSync } = require('child_process');
(async function test() {
await testArg('stderr');
await testArg('http');
await testArg('http,stderr');
})();
async function testArg(argValue) {
console.log('Checks ' + argValue + '..');
const hasHttp = argValue.split(',').includes('http');
const hasStderr = argValue.split(',').includes('stderr');
const nodeProcess = spawnSync(process.execPath, [
'--inspect=0',
`--inspect-publish-uid=${argValue}`,
'-e', `(${scriptMain.toString()})(${hasHttp ? 200 : 404})`
]);
const hasWebSocketInStderr = checkStdError(
nodeProcess.stderr.toString('utf8'));
assert.strictEqual(hasWebSocketInStderr, hasStderr);
function checkStdError(data) {
const matches = data.toString('utf8').match(/ws:\/\/.+:(\d+)\/.+/);
return !!matches;
}
function scriptMain(code) {
const url = require('inspector').url();
const { host } = require('url').parse(url);
require('http').get('http://' + host + '/json/list', (response) => {
assert.strictEqual(response.statusCode, code);
response.destroy();
});
}
}