summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/inspector_agent.cc424
-rw-r--r--src/inspector_agent.h3
-rw-r--r--src/inspector_socket_server.cc471
-rw-r--r--src/inspector_socket_server.h77
4 files changed, 677 insertions, 298 deletions
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
index ec713942f5..fc478c49a0 100644
--- a/src/inspector_agent.cc
+++ b/src/inspector_agent.cc
@@ -1,6 +1,6 @@
#include "inspector_agent.h"
-#include "inspector_socket.h"
+#include "inspector_socket_server.h"
#include "env.h"
#include "env-inl.h"
#include "node.h"
@@ -37,84 +37,6 @@ static const uint8_t PROTOCOL_JSON[] = {
#include "v8_inspector_protocol_json.h" // NOLINT(build/include_order)
};
-std::string GetWsUrl(int port, const std::string& id) {
- char buf[1024];
- snprintf(buf, sizeof(buf), "127.0.0.1:%d/%s", port, id.c_str());
- return buf;
-}
-
-void PrintDebuggerReadyMessage(int port, const std::string& id) {
- fprintf(stderr, "Debugger listening on port %d.\n"
- "Warning: This is an experimental feature and could change at any time.\n"
- "To start debugging, open the following URL in Chrome:\n"
- " chrome-devtools://devtools/bundled/inspector.html?"
- "experiments=true&v8only=true&ws=%s\n",
- port, GetWsUrl(port, id).c_str());
- fflush(stderr);
-}
-
-std::string MapToString(const std::map<std::string, std::string> object) {
- std::ostringstream json;
- json << "[ {\n";
- bool first = true;
- for (const auto& name_value : object) {
- if (!first)
- json << ",\n";
- json << " \"" << name_value.first << "\": \"";
- json << name_value.second << "\"";
- first = false;
- }
- json << "\n} ]\n\n";
- return json.str();
-}
-
-void Escape(std::string* string) {
- for (char& c : *string) {
- c = (c == '\"' || c == '\\') ? '_' : c;
- }
-}
-
-void DisposeInspector(InspectorSocket* socket, int status) {
- delete socket;
-}
-
-void DisconnectAndDisposeIO(InspectorSocket* socket) {
- if (socket) {
- inspector_close(socket, DisposeInspector);
- }
-}
-
-void OnBufferAlloc(uv_handle_t* handle, size_t len, uv_buf_t* buf) {
- buf->base = new char[len];
- buf->len = len;
-}
-
-void SendHttpResponse(InspectorSocket* socket, const char* response,
- size_t size) {
- const char HEADERS[] = "HTTP/1.0 200 OK\r\n"
- "Content-Type: application/json; charset=UTF-8\r\n"
- "Cache-Control: no-cache\r\n"
- "Content-Length: %zu\r\n"
- "\r\n";
- char header[sizeof(HEADERS) + 20];
- int header_len = snprintf(header, sizeof(header), HEADERS, size);
- inspector_write(socket, header, header_len);
- inspector_write(socket, response, size);
-}
-
-void SendHttpResponse(InspectorSocket* socket, const std::string& response) {
- SendHttpResponse(socket, response.data(), response.size());
-}
-
-void SendVersionResponse(InspectorSocket* socket) {
- static const char response[] =
- "{\n"
- " \"Browser\": \"node.js/" NODE_VERSION "\",\n"
- " \"Protocol-Version\": \"1.1\"\n"
- "}\n";
- SendHttpResponse(socket, response, sizeof(response) - 1);
-}
-
std::string GetProcessTitle() {
// uv_get_process_title will trim the title if it is too long.
char title[2048];
@@ -126,36 +48,6 @@ std::string GetProcessTitle() {
}
}
-void SendProtocolJson(InspectorSocket* socket) {
- z_stream strm;
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
- CHECK_EQ(Z_OK, inflateInit(&strm));
- static const size_t kDecompressedSize =
- PROTOCOL_JSON[0] * 0x10000u +
- PROTOCOL_JSON[1] * 0x100u +
- PROTOCOL_JSON[2];
- strm.next_in = const_cast<uint8_t*>(PROTOCOL_JSON + 3);
- strm.avail_in = sizeof(PROTOCOL_JSON) - 3;
- std::string data(kDecompressedSize, '\0');
- strm.next_out = reinterpret_cast<Byte*>(&data[0]);
- strm.avail_out = data.size();
- CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH));
- CHECK_EQ(0, strm.avail_out);
- CHECK_EQ(Z_OK, inflateEnd(&strm));
- SendHttpResponse(socket, data);
-}
-
-const char* match_path_segment(const char* path, const char* expected) {
- size_t len = strlen(expected);
- if (StringEqualNoCaseN(path, expected, len)) {
- if (path[len] == '/') return path + len + 1;
- if (path[len] == '\0') return path + len;
- }
- return nullptr;
-}
-
// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
// Used ver 4 - with numbers
std::string GenerateID() {
@@ -201,17 +93,39 @@ std::string StringViewToUtf8(const StringView& view) {
return result;
}
-std::unique_ptr<StringBuffer> Utf8ToStringView(const char* source,
- size_t length) {
- UnicodeString utf16 = UnicodeString::fromUTF8(StringPiece(source, length));
+std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string& message) {
+ UnicodeString utf16 =
+ UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));
StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()),
utf16.length());
return StringBuffer::create(view);
}
+
} // namespace
class V8NodeInspector;
+class InspectorAgentDelegate: public node::inspector::SocketServerDelegate {
+ public:
+ InspectorAgentDelegate(AgentImpl* agent, const std::string& script_path,
+ const std::string& script_name, bool wait);
+ bool StartSession(int session_id, const std::string& target_id) override;
+ void MessageReceived(int session_id, const std::string& message) override;
+ void EndSession(int session_id) override;
+ std::vector<std::string> GetTargetIds() override;
+ std::string GetTargetTitle(const std::string& id) override;
+ std::string GetTargetUrl(const std::string& id) override;
+ bool IsConnected() { return connected_; }
+ private:
+ AgentImpl* agent_;
+ bool connected_;
+ int session_id_;
+ const std::string script_name_;
+ const std::string script_path_;
+ const std::string target_id_;
+ bool waiting_;
+};
+
class AgentImpl {
public:
explicit AgentImpl(node::Environment* env);
@@ -223,42 +137,37 @@ class AgentImpl {
void Stop();
bool IsStarted();
- bool IsConnected() { return state_ == State::kConnected; }
+ bool IsConnected();
void WaitForDisconnect();
void FatalException(v8::Local<v8::Value> error,
v8::Local<v8::Message> message);
+ void PostIncomingMessage(int session_id, const std::string& message);
+ void ResumeStartup() {
+ uv_sem_post(&start_sem_);
+ }
+
private:
using MessageQueue =
std::vector<std::pair<int, std::unique_ptr<StringBuffer>>>;
enum class State { kNew, kAccepting, kConnected, kDone, kError };
static void ThreadCbIO(void* agent);
- static void OnSocketConnectionIO(uv_stream_t* server, int status);
- static bool OnInspectorHandshakeIO(InspectorSocket* socket,
- enum inspector_handshake_event state,
- const std::string& path);
static void WriteCbIO(uv_async_t* async);
void InstallInspectorOnProcess();
void WorkerRunIO();
- void OnInspectorConnectionIO(InspectorSocket* socket);
- void OnRemoteDataIO(InspectorSocket* stream, ssize_t read,
- const uv_buf_t* b);
void SetConnected(bool connected);
void DispatchMessages();
void Write(int session_id, const StringView& message);
bool AppendMessage(MessageQueue* vector, int session_id,
std::unique_ptr<StringBuffer> buffer);
void SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2);
- void PostIncomingMessage(const char* message, size_t len);
void WaitForFrontendMessage();
void NotifyMessageReceived();
State ToState(State state);
- void SendListResponse(InspectorSocket* socket);
- bool RespondToGet(InspectorSocket* socket, const std::string& path);
uv_sem_t start_sem_;
ConditionVariable incoming_message_cond_;
@@ -266,6 +175,8 @@ class AgentImpl {
uv_thread_t thread_;
uv_loop_t child_loop_;
+ InspectorAgentDelegate* delegate_;
+
int port_;
bool wait_;
bool shutting_down_;
@@ -274,18 +185,15 @@ class AgentImpl {
uv_async_t* data_written_;
uv_async_t io_thread_req_;
- InspectorSocket* client_socket_;
V8NodeInspector* inspector_;
v8::Platform* platform_;
MessageQueue incoming_message_queue_;
MessageQueue outgoing_message_queue_;
bool dispatching_messages_;
- int frontend_session_id_;
- int backend_session_id_;
+ int session_id_;
+ InspectorSocketServer* server_;
std::string script_name_;
- std::string script_path_;
- const std::string id_;
friend class ChannelImpl;
friend class DispatchOnInspectorBackendTask;
@@ -300,11 +208,6 @@ void InterruptCallback(v8::Isolate*, void* agent) {
static_cast<AgentImpl*>(agent)->DispatchMessages();
}
-void DataCallback(uv_stream_t* stream, ssize_t read, const uv_buf_t* buf) {
- InspectorSocket* socket = inspector_from_stream(stream);
- static_cast<AgentImpl*>(socket->data)->OnRemoteDataIO(socket, read, buf);
-}
-
class DispatchOnInspectorBackendTask : public v8::Task {
public:
explicit DispatchOnInspectorBackendTask(AgentImpl* agent) : agent_(agent) {}
@@ -333,7 +236,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
void flushProtocolNotifications() override { }
void sendMessageToFrontend(const StringView& message) {
- agent_->Write(agent_->frontend_session_id_, message);
+ agent_->Write(agent_->session_id_, message);
}
AgentImpl* const agent_;
@@ -414,19 +317,18 @@ class V8NodeInspector : public v8_inspector::V8InspectorClient {
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
};
-AgentImpl::AgentImpl(Environment* env) : port_(0),
+AgentImpl::AgentImpl(Environment* env) : delegate_(nullptr),
+ port_(0),
wait_(false),
shutting_down_(false),
state_(State::kNew),
parent_env_(env),
data_written_(new uv_async_t()),
- client_socket_(nullptr),
inspector_(nullptr),
platform_(nullptr),
dispatching_messages_(false),
- frontend_session_id_(0),
- backend_session_id_(0),
- id_(GenerateID()) {
+ session_id_(0),
+ server_(nullptr) {
CHECK_EQ(0, uv_sem_init(&start_sem_, 0));
memset(&io_thread_req_, 0, sizeof(io_thread_req_));
CHECK_EQ(0, uv_async_init(env->event_loop(), data_written_, nullptr));
@@ -543,6 +445,10 @@ void AgentImpl::Stop() {
delete inspector_;
}
+bool AgentImpl::IsConnected() {
+ return delegate_ != nullptr && delegate_->IsConnected();
+}
+
bool AgentImpl::IsStarted() {
return !!platform_;
}
@@ -550,6 +456,9 @@ bool AgentImpl::IsStarted() {
void AgentImpl::WaitForDisconnect() {
if (state_ == State::kConnected) {
shutting_down_ = true;
+ // Gives a signal to stop accepting new connections
+ // TODO(eugeneo): Introduce an API with explicit request names.
+ Write(0, StringView());
fprintf(stderr, "Waiting for the debugger to disconnect...\n");
fflush(stderr);
inspector_->runMessageLoopOnPause(0);
@@ -622,180 +531,58 @@ void AgentImpl::ThreadCbIO(void* agent) {
}
// static
-void AgentImpl::OnSocketConnectionIO(uv_stream_t* server, int status) {
- if (status == 0) {
- InspectorSocket* socket = new InspectorSocket();
- socket->data = server->data;
- if (inspector_accept(server, socket,
- AgentImpl::OnInspectorHandshakeIO) != 0) {
- delete socket;
- }
- }
-}
-
-// static
-bool AgentImpl::OnInspectorHandshakeIO(InspectorSocket* socket,
- enum inspector_handshake_event state,
- const std::string& path) {
- AgentImpl* agent = static_cast<AgentImpl*>(socket->data);
- switch (state) {
- case kInspectorHandshakeHttpGet:
- return agent->RespondToGet(socket, path);
- case kInspectorHandshakeUpgrading:
- return path.length() == agent->id_.length() + 1 &&
- path.find(agent->id_) == 1;
- case kInspectorHandshakeUpgraded:
- agent->OnInspectorConnectionIO(socket);
- return true;
- case kInspectorHandshakeFailed:
- delete socket;
- return false;
- default:
- UNREACHABLE();
- return false;
- }
-}
-
-void AgentImpl::OnRemoteDataIO(InspectorSocket* socket,
- ssize_t read,
- const uv_buf_t* buf) {
- if (read > 0) {
- // TODO(pfeldman): Instead of blocking execution while debugger
- // engages, node should wait for the run callback from the remote client
- // and initiate its startup. This is a change to node.cc that should be
- // upstreamed separately.
- if (wait_) {
- std::string message(buf->base, read);
- if (message.find("\"Runtime.runIfWaitingForDebugger\"") !=
- std::string::npos) {
- wait_ = false;
- uv_sem_post(&start_sem_);
- }
- }
- PostIncomingMessage(buf->base, read);
- } else {
- // EOF
- if (client_socket_ == socket) {
- client_socket_ = nullptr;
- PostIncomingMessage(TAG_DISCONNECT, sizeof(TAG_DISCONNECT) - 1);
- }
- DisconnectAndDisposeIO(socket);
- }
- if (buf) {
- delete[] buf->base;
- }
-}
-
-void AgentImpl::SendListResponse(InspectorSocket* socket) {
- std::map<std::string, std::string> response;
- response["description"] = "node.js instance";
- response["faviconUrl"] = "https://nodejs.org/static/favicon.ico";
- response["id"] = id_;
- response["title"] = script_name_.empty() ? GetProcessTitle() : script_name_;
- Escape(&response["title"]);
- response["type"] = "node";
- // This attribute value is a "best effort" URL that is passed as a JSON
- // string. It is not guaranteed to resolve to a valid resource.
- response["url"] = "file://" + script_path_;
- Escape(&response["url"]);
-
- if (!client_socket_) {
- std::string address = GetWsUrl(port_, id_);
-
- std::ostringstream frontend_url;
- frontend_url << "chrome-devtools://devtools/bundled";
- frontend_url << "/inspector.html?experiments=true&v8only=true&ws=";
- frontend_url << address;
-
- response["devtoolsFrontendUrl"] += frontend_url.str();
- response["webSocketDebuggerUrl"] = "ws://" + address;
- }
- SendHttpResponse(socket, MapToString(response));
-}
-
-bool AgentImpl::RespondToGet(InspectorSocket* socket, const std::string& path) {
- const char* command = match_path_segment(path.c_str(), "/json");
- if (command == nullptr)
- return false;
-
- if (match_path_segment(command, "list") || command[0] == '\0') {
- SendListResponse(socket);
- return true;
- } else if (match_path_segment(command, "protocol")) {
- SendProtocolJson(socket);
- return true;
- } else if (match_path_segment(command, "version")) {
- SendVersionResponse(socket);
- return true;
- } else if (const char* pid = match_path_segment(command, "activate")) {
- if (pid != id_)
- return false;
- SendHttpResponse(socket, "Target activated");
- return true;
- }
- return false;
-}
-
-// static
void AgentImpl::WriteCbIO(uv_async_t* async) {
AgentImpl* agent = static_cast<AgentImpl*>(async->data);
- InspectorSocket* socket = agent->client_socket_;
- if (socket) {
- MessageQueue outgoing_messages;
- agent->SwapBehindLock(&agent->outgoing_message_queue_, &outgoing_messages);
- for (const MessageQueue::value_type& outgoing : outgoing_messages) {
- if (outgoing.first == agent->frontend_session_id_) {
- StringView message = outgoing.second->string();
- std::string utf8Message = StringViewToUtf8(message);
- inspector_write(socket, utf8Message.c_str(), utf8Message.length());
- }
+ MessageQueue outgoing_messages;
+ agent->SwapBehindLock(&agent->outgoing_message_queue_, &outgoing_messages);
+ for (const MessageQueue::value_type& outgoing : outgoing_messages) {
+ StringView view = outgoing.second->string();
+ if (view.length() == 0) {
+ agent->server_->Stop(nullptr);
+ } else {
+ agent->server_->Send(outgoing.first,
+ StringViewToUtf8(outgoing.second->string()));
}
}
}
void AgentImpl::WorkerRunIO() {
- sockaddr_in addr;
- uv_tcp_t server;
int err = uv_loop_init(&child_loop_);
CHECK_EQ(err, 0);
err = uv_async_init(&child_loop_, &io_thread_req_, AgentImpl::WriteCbIO);
CHECK_EQ(err, 0);
io_thread_req_.data = this;
+ std::string script_path;
if (!script_name_.empty()) {
uv_fs_t req;
if (0 == uv_fs_realpath(&child_loop_, &req, script_name_.c_str(), nullptr))
- script_path_ = std::string(reinterpret_cast<char*>(req.ptr));
+ script_path = std::string(reinterpret_cast<char*>(req.ptr));
uv_fs_req_cleanup(&req);
}
- uv_tcp_init(&child_loop_, &server);
- uv_ip4_addr("0.0.0.0", port_, &addr);
- server.data = this;
- err = uv_tcp_bind(&server,
- reinterpret_cast<const struct sockaddr*>(&addr), 0);
- if (err == 0) {
- err = uv_listen(reinterpret_cast<uv_stream_t*>(&server), 1,
- OnSocketConnectionIO);
- }
- if (err != 0) {
+ InspectorAgentDelegate delegate(this, script_path, script_name_, wait_);
+ delegate_ = &delegate;
+ InspectorSocketServer server(&delegate, port_);
+ if (!server.Start(&child_loop_)) {
fprintf(stderr, "Unable to open devtools socket: %s\n", uv_strerror(err));
state_ = State::kError; // Safe, main thread is waiting on semaphore
uv_close(reinterpret_cast<uv_handle_t*>(&io_thread_req_), nullptr);
- uv_close(reinterpret_cast<uv_handle_t*>(&server), nullptr);
uv_loop_close(&child_loop_);
uv_sem_post(&start_sem_);
return;
}
- PrintDebuggerReadyMessage(port_, id_);
+ server_ = &server;
if (!wait_) {
uv_sem_post(&start_sem_);
}
uv_run(&child_loop_, UV_RUN_DEFAULT);
uv_close(reinterpret_cast<uv_handle_t*>(&io_thread_req_), nullptr);
- uv_close(reinterpret_cast<uv_handle_t*>(&server), nullptr);
- DisconnectAndDisposeIO(client_socket_);
+ server.Stop(nullptr);
+ server.TerminateConnections(nullptr);
uv_run(&child_loop_, UV_RUN_NOWAIT);
err = uv_loop_close(&child_loop_);
CHECK_EQ(err, 0);
+ delegate_ = nullptr;
+ server_ = nullptr;
}
bool AgentImpl::AppendMessage(MessageQueue* queue, int session_id,
@@ -811,9 +598,10 @@ void AgentImpl::SwapBehindLock(MessageQueue* vector1, MessageQueue* vector2) {
vector1->swap(*vector2);
}
-void AgentImpl::PostIncomingMessage(const char* message, size_t len) {
- if (AppendMessage(&incoming_message_queue_, frontend_session_id_,
- Utf8ToStringView(message, len))) {
+void AgentImpl::PostIncomingMessage(int session_id,
+ const std::string& message) {
+ if (AppendMessage(&incoming_message_queue_, session_id,
+ Utf8ToStringView(message))) {
v8::Isolate* isolate = parent_env_->isolate();
platform_->CallOnForegroundThread(isolate,
new DispatchOnInspectorBackendTask(this));
@@ -834,17 +622,6 @@ void AgentImpl::NotifyMessageReceived() {
incoming_message_cond_.Broadcast(scoped_lock);
}
-void AgentImpl::OnInspectorConnectionIO(InspectorSocket* socket) {
- if (client_socket_) {
- DisconnectAndDisposeIO(socket);
- return;
- }
- client_socket_ = socket;
- inspector_read_start(socket, OnBufferAlloc, DataCallback);
- frontend_session_id_++;
- PostIncomingMessage(TAG_CONNECT, sizeof(TAG_CONNECT) - 1);
-}
-
void AgentImpl::DispatchMessages() {
// This function can be reentered if there was an incoming message while
// V8 was processing another inspector request (e.g. if the user is
@@ -867,7 +644,7 @@ void AgentImpl::DispatchMessages() {
if (tag == TAG_CONNECT) {
CHECK_EQ(State::kAccepting, state_);
- backend_session_id_++;
+ session_id_ = pair.first;
state_ = State::kConnected;
fprintf(stderr, "Debugger attached.\n");
inspector_->connectFrontend();
@@ -876,7 +653,6 @@ void AgentImpl::DispatchMessages() {
if (shutting_down_) {
state_ = State::kDone;
} else {
- PrintDebuggerReadyMessage(port_, id_);
state_ = State::kAccepting;
}
inspector_->quitMessageLoopOnPause();
@@ -930,6 +706,60 @@ void Agent::FatalException(v8::Local<v8::Value> error,
impl->FatalException(error, message);
}
+InspectorAgentDelegate::InspectorAgentDelegate(AgentImpl* agent,
+ const std::string& script_path,
+ const std::string& script_name,
+ bool wait)
+ : agent_(agent),
+ connected_(false),
+ session_id_(0),
+ script_name_(script_name),
+ script_path_(script_path),
+ target_id_(GenerateID()),
+ waiting_(wait) { }
+
+
+bool InspectorAgentDelegate::StartSession(int session_id,
+ const std::string& target_id) {
+ if (connected_)
+ return false;
+ connected_ = true;
+ agent_->PostIncomingMessage(session_id, TAG_CONNECT);
+ return true;
+}
+
+void InspectorAgentDelegate::MessageReceived(int session_id,
+ const std::string& message) {
+ // TODO(pfeldman): Instead of blocking execution while debugger
+ // engages, node should wait for the run callback from the remote client
+ // and initiate its startup. This is a change to node.cc that should be
+ // upstreamed separately.
+ if (waiting_) {
+ if (message.find("\"Runtime.runIfWaitingForDebugger\"") !=
+ std::string::npos) {
+ waiting_ = false;
+ agent_->ResumeStartup();
+ }
+ }
+ agent_->PostIncomingMessage(session_id, message);
+}
+
+void InspectorAgentDelegate::EndSession(int session_id) {
+ connected_ = false;
+ agent_->PostIncomingMessage(session_id, TAG_DISCONNECT);
+}
+
+std::vector<std::string> InspectorAgentDelegate::GetTargetIds() {
+ return { target_id_ };
+}
+
+std::string InspectorAgentDelegate::GetTargetTitle(const std::string& id) {
+ return script_name_.empty() ? GetProcessTitle() : script_name_;
+}
+
+std::string InspectorAgentDelegate::GetTargetUrl(const std::string& id) {
+ return "file://" + script_path_;
+}
} // namespace inspector
} // namespace node
diff --git a/src/inspector_agent.h b/src/inspector_agent.h
index 3607cffba5..b31c77496b 100644
--- a/src/inspector_agent.h
+++ b/src/inspector_agent.h
@@ -1,6 +1,8 @@
#ifndef SRC_INSPECTOR_AGENT_H_
#define SRC_INSPECTOR_AGENT_H_
+#include <stddef.h>
+
#if !HAVE_INSPECTOR
#error("This header can only be used when inspector is enabled")
#endif
@@ -36,7 +38,6 @@ class Agent {
bool IsStarted();
bool IsConnected();
void WaitForDisconnect();
-
void FatalException(v8::Local<v8::Value> error,
v8::Local<v8::Message> message);
private:
diff --git a/src/inspector_socket_server.cc b/src/inspector_socket_server.cc
new file mode 100644
index 0000000000..e05a0c577d
--- /dev/null
+++ b/src/inspector_socket_server.cc
@@ -0,0 +1,471 @@
+#include "inspector_socket_server.h"
+
+#include "node.h"
+#include "uv.h"
+#include "zlib.h"
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <sstream>
+
+namespace node {
+namespace inspector {
+
+namespace {
+
+static const uint8_t PROTOCOL_JSON[] = {
+ #include "v8_inspector_protocol_json.h" // NOLINT(build/include_order)
+};
+
+void Escape(std::string* string) {
+ for (char& c : *string) {
+ c = (c == '\"' || c == '\\') ? '_' : c;
+ }
+}
+
+std::string GetWsUrl(int port, const std::string& id) {
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "127.0.0.1:%d/%s", port, id.c_str());
+ return buf;
+}
+
+std::string MapToString(const std::map<std::string, std::string>& object) {
+ bool first = true;
+ std::ostringstream json;
+ json << "{\n";
+ for (const auto& name_value : object) {
+ if (!first)
+ json << ",\n";
+ first = false;
+ json << " \"" << name_value.first << "\": \"";
+ json << name_value.second << "\"";
+ }
+ json << "\n} ";
+ return json.str();
+}
+
+std::string MapsToString(
+ const std::vector<std::map<std::string, std::string>>& array) {
+ bool first = true;
+ std::ostringstream json;
+ json << "[ ";
+ for (const auto& object : array) {
+ if (!first)
+ json << ", ";
+ first = false;
+ json << MapToString(object);
+ }
+ json << "]\n\n";
+ return json.str();
+}
+
+const char* MatchPathSegment(const char* path, const char* expected) {
+ size_t len = strlen(expected);
+ if (StringEqualNoCaseN(path, expected, len)) {
+ if (path[len] == '/') return path + len + 1;
+ if (path[len] == '\0') return path + len;
+ }
+ return nullptr;
+}
+
+void OnBufferAlloc(uv_handle_t* handle, size_t len, uv_buf_t* buf) {
+ buf->base = new char[len];
+ buf->len = len;
+}
+
+void PrintDebuggerReadyMessage(int port, const std::vector<std::string>& ids) {
+ fprintf(stderr,
+ "Debugger listening on port %d.\n"
+ "Warning: This is an experimental feature "
+ "and could change at any time.\n",
+ port);
+ if (ids.size() == 1)
+ fprintf(stderr, "To start debugging, open the following URL in Chrome:\n");
+ if (ids.size() > 1)
+ fprintf(stderr, "To start debugging, open the following URLs in Chrome:\n");
+ for (const std::string& id : ids) {
+ fprintf(stderr,
+ " chrome-devtools://devtools/bundled/inspector.html?"
+ "experiments=true&v8only=true&ws=%s\n", GetWsUrl(port, id).c_str());
+ }
+ fflush(stderr);
+}
+
+void SendHttpResponse(InspectorSocket* socket, const std::string& response) {
+ const char HEADERS[] = "HTTP/1.0 200 OK\r\n"
+ "Content-Type: application/json; charset=UTF-8\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n";
+ char header[sizeof(HEADERS) + 20];
+ int header_len = snprintf(header, sizeof(header), HEADERS, response.size());
+ inspector_write(socket, header, header_len);
+ inspector_write(socket, response.data(), response.size());
+}
+
+void SendVersionResponse(InspectorSocket* socket) {
+ std::map<std::string, std::string> response;
+ response["Browser"] = "node.js/" NODE_VERSION;
+ response["Protocol-Version"] = "1.1";
+ SendHttpResponse(socket, MapToString(response));
+}
+
+void SendProtocolJson(InspectorSocket* socket) {
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ CHECK_EQ(Z_OK, inflateInit(&strm));
+ static const size_t kDecompressedSize =
+ PROTOCOL_JSON[0] * 0x10000u +
+ PROTOCOL_JSON[1] * 0x100u +
+ PROTOCOL_JSON[2];
+ strm.next_in = const_cast<uint8_t*>(PROTOCOL_JSON + 3);
+ strm.avail_in = sizeof(PROTOCOL_JSON) - 3;
+ std::string data(kDecompressedSize, '\0');
+ strm.next_out = reinterpret_cast<Byte*>(&data[0]);
+ strm.avail_out = data.size();
+ CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH));
+ CHECK_EQ(0, strm.avail_out);
+ CHECK_EQ(Z_OK, inflateEnd(&strm));
+ SendHttpResponse(socket, data);
+}
+
+} // namespace
+
+
+class Closer {
+ public:
+ explicit Closer(InspectorSocketServer* server) : server_(server),
+ close_count_(0) { }
+
+ void AddCallback(InspectorSocketServer::ServerCallback callback) {
+ if (callback == nullptr)
+ return;
+ callbacks_.insert(callback);
+ }
+
+ void DecreaseExpectedCount() {
+ --close_count_;
+ NotifyIfDone();
+ }
+
+ void IncreaseExpectedCount() {
+ ++close_count_;
+ }
+
+ void NotifyIfDone() {
+ if (close_count_ == 0) {
+ for (auto callback : callbacks_) {
+ callback(server_);
+ }
+ InspectorSocketServer* server = server_;
+ delete server->closer_;
+ server->closer_ = nullptr;
+ }
+ }
+
+ private:
+ InspectorSocketServer* server_;
+ std::set<InspectorSocketServer::ServerCallback> callbacks_;
+ int close_count_;
+};
+
+class SocketSession {
+ public:
+ SocketSession(InspectorSocketServer* server, int id);
+ void Close(bool socket_cleanup, Closer* closer);
+ void Declined() { state_ = State::kDeclined; }
+ static SocketSession* From(InspectorSocket* socket) {
+ return node::ContainerOf(&SocketSession::socket_, socket);
+ }
+ void FrontendConnected();
+ InspectorSocketServer* GetServer() { return server_; }
+ int Id() { return id_; }
+ void Send(const std::string& message);
+ void SetTargetId(const std::string& target_id) {
+ CHECK(target_id_.empty());
+ target_id_ = target_id;
+ }
+ InspectorSocket* Socket() { return &socket_; }
+ const std::string TargetId() { return target_id_; }
+
+ private:
+ enum class State { kHttp, kWebSocket, kClosing, kEOF, kDeclined };
+ static void CloseCallback_(InspectorSocket* socket, int code);
+ static void ReadCallback_(uv_stream_t* stream, ssize_t read,
+ const uv_buf_t* buf);
+ void OnRemoteDataIO(InspectorSocket* socket, ssize_t read,
+ const uv_buf_t* buf);
+ const int id_;
+ Closer* closer_;
+ InspectorSocket socket_;
+ InspectorSocketServer* server_;
+ std::string target_id_;
+ State state_;
+};
+
+InspectorSocketServer::InspectorSocketServer(SocketServerDelegate* delegate,
+ int port) : loop_(nullptr),
+ delegate_(delegate),
+ port_(port),
+ closer_(nullptr),
+ next_session_id_(0) { }
+
+
+// static
+bool InspectorSocketServer::HandshakeCallback(InspectorSocket* socket,
+ inspector_handshake_event event,
+ const std::string& path) {
+ InspectorSocketServer* server = SocketSession::From(socket)->GetServer();
+ const std::string& id = path.empty() ? path : path.substr(1);
+ switch (event) {
+ case kInspectorHandshakeHttpGet:
+ return server->RespondToGet(socket, path);
+ case kInspectorHandshakeUpgrading:
+ return server->SessionStarted(SocketSession::From(socket), id);
+ case kInspectorHandshakeUpgraded:
+ SocketSession::From(socket)->FrontendConnected();
+ return true;
+ case kInspectorHandshakeFailed:
+ SocketSession::From(socket)->Close(false, nullptr);
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool InspectorSocketServer::SessionStarted(SocketSession* session,
+ const std::string& id) {
+ bool connected = false;
+ if (TargetExists(id)) {
+ connected = delegate_->StartSession(session->Id(), id);
+ }
+ if (connected) {
+ connected_sessions_[session->Id()] = session;
+ session->SetTargetId(id);
+ } else {
+ session->Declined();
+ }
+ return connected;
+}
+
+void InspectorSocketServer::SessionTerminated(int session_id) {
+ if (connected_sessions_.erase(session_id) == 0) {
+ return;
+ }
+ delegate_->EndSession(session_id);
+ if (connected_sessions_.empty() &&
+ uv_is_active(reinterpret_cast<uv_handle_t*>(&server_))) {
+ PrintDebuggerReadyMessage(port_, delegate_->GetTargetIds());
+ }
+}
+
+bool InspectorSocketServer::RespondToGet(InspectorSocket* socket,
+ const std::string& path) {
+ const char* command = MatchPathSegment(path.c_str(), "/json");
+ if (command == nullptr)
+ return false;
+
+ if (MatchPathSegment(command, "list") || command[0] == '\0') {
+ SendListResponse(socket);
+ return true;
+ } else if (MatchPathSegment(command, "protocol")) {
+ SendProtocolJson(socket);
+ return true;
+ } else if (MatchPathSegment(command, "version")) {
+ SendVersionResponse(socket);
+ return true;
+ } else if (const char* target_id = MatchPathSegment(command, "activate")) {
+ if (TargetExists(target_id)) {
+ SendHttpResponse(socket, "Target activated");
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+void InspectorSocketServer::SendListResponse(InspectorSocket* socket) {
+ std::vector<std::map<std::string, std::string>> response;
+ for (const std::string& id : delegate_->GetTargetIds()) {
+ response.push_back(std::map<std::string, std::string>());
+ std::map<std::string, std::string>& target_map = response.back();
+ target_map["description"] = "node.js instance";
+ target_map["faviconUrl"] = "https://nodejs.org/static/favicon.ico";
+ target_map["id"] = id;
+ target_map["title"] = delegate_->GetTargetTitle(id);
+ Escape(&target_map["title"]);
+ target_map["type"] = "node";
+ // This attribute value is a "best effort" URL that is passed as a JSON
+ // string. It is not guaranteed to resolve to a valid resource.
+ target_map["url"] = delegate_->GetTargetUrl(id);
+ Escape(&target_map["url"]);
+
+ bool connected = false;
+ for (const auto& session : connected_sessions_) {
+ if (session.second->TargetId() == id) {
+ connected = true;
+ break;
+ }
+ }
+ if (!connected) {
+ std::string address = GetWsUrl(port_, id);
+ std::ostringstream frontend_url;
+ frontend_url << "chrome-devtools://devtools/bundled";
+ frontend_url << "/inspector.html?experiments=true&v8only=true&ws=";
+ frontend_url << address;
+ target_map["devtoolsFrontendUrl"] += frontend_url.str();
+ target_map["webSocketDebuggerUrl"] = "ws://" + address;
+ }
+ }
+ SendHttpResponse(socket, MapsToString(response));
+}
+
+bool InspectorSocketServer::Start(uv_loop_t* loop) {
+ loop_ = loop;
+ sockaddr_in addr;
+ uv_tcp_init(loop_, &server_);
+ uv_ip4_addr("0.0.0.0", port_, &addr);
+ int err = uv_tcp_bind(&server_,
+ reinterpret_cast<const struct sockaddr*>(&addr), 0);
+ if (err == 0) {
+ err = uv_listen(reinterpret_cast<uv_stream_t*>(&server_), 1,
+ SocketConnectedCallback);
+ }
+ if (err == 0 && connected_sessions_.empty()) {
+ PrintDebuggerReadyMessage(port_, delegate_->GetTargetIds());
+ }
+ if (err != 0 && connected_sessions_.empty()) {
+ fprintf(stderr, "Unable to open devtools socket: %s\n", uv_strerror(err));
+ uv_close(reinterpret_cast<uv_handle_t*>(&server_), nullptr);
+ return false;
+ }
+ return true;
+}
+
+void InspectorSocketServer::Stop(ServerCallback cb) {
+ if (closer_ == nullptr) {
+ closer_ = new Closer(this);
+ }
+ closer_->AddCallback(cb);
+
+ uv_handle_t* handle = reinterpret_cast<uv_handle_t*>(&server_);
+ if (uv_is_active(handle)) {
+ closer_->IncreaseExpectedCount();
+ uv_close(reinterpret_cast<uv_handle_t*>(&server_), ServerClosedCallback);
+ }
+ closer_->NotifyIfDone();
+}
+
+void InspectorSocketServer::TerminateConnections(ServerCallback cb) {
+ if (closer_ == nullptr) {
+ closer_ = new Closer(this);
+ }
+ closer_->AddCallback(cb);
+ std::map<int, SocketSession*> sessions;
+ std::swap(sessions, connected_sessions_);
+ for (const auto& session : sessions) {
+ int id = session.second->Id();
+ session.second->Close(true, closer_);
+ delegate_->EndSession(id);
+ }
+ closer_->NotifyIfDone();
+}
+
+bool InspectorSocketServer::TargetExists(const std::string& id) {
+ const std::vector<std::string>& target_ids = delegate_->GetTargetIds();
+ const auto& found = std::find(target_ids.begin(), target_ids.end(), id);
+ return found != target_ids.end();
+}
+
+void InspectorSocketServer::Send(int session_id, const std::string& message) {
+ auto session_iterator = connected_sessions_.find(session_id);
+ if (session_iterator != connected_sessions_.end()) {
+ session_iterator->second->Send(message);
+ }
+}
+
+// static
+void InspectorSocketServer::ServerClosedCallback(uv_handle_t* server) {
+ InspectorSocketServer* socket_server = InspectorSocketServer::From(server);
+ if (socket_server->closer_)
+ socket_server->closer_->DecreaseExpectedCount();
+}
+
+// static
+void InspectorSocketServer::SocketConnectedCallback(uv_stream_t* server,
+ int status) {
+ if (status == 0) {
+ InspectorSocketServer* socket_server = InspectorSocketServer::From(server);
+ SocketSession* session =
+ new SocketSession(socket_server, socket_server->next_session_id_++);
+ if (inspector_accept(server, session->Socket(), HandshakeCallback) != 0) {
+ delete session;
+ }
+ }
+}
+
+// InspectorSession tracking
+SocketSession::SocketSession(InspectorSocketServer* server, int id)
+ : id_(id), closer_(nullptr), server_(server),
+ state_(State::kHttp) { }
+
+void SocketSession::Close(bool socket_cleanup, Closer* closer) {
+ CHECK_EQ(closer_, nullptr);
+ CHECK_NE(state_, State::kClosing);
+ server_->SessionTerminated(id_);
+ if (socket_cleanup) {
+ state_ = State::kClosing;
+ closer_ = closer;
+ if (closer_ != nullptr)
+ closer->IncreaseExpectedCount();
+ inspector_close(&socket_, CloseCallback_);
+ } else {
+ delete this;
+ }
+}
+
+// static
+void SocketSession::CloseCallback_(InspectorSocket* socket, int code) {
+ SocketSession* session = SocketSession::From(socket);
+ CHECK_EQ(State::kClosing, session->state_);
+ Closer* closer = session->closer_;
+ if (closer != nullptr)
+ closer->DecreaseExpectedCount();
+ delete session;
+}
+
+void SocketSession::FrontendConnected() {
+ CHECK_EQ(State::kHttp, state_);
+ state_ = State::kWebSocket;
+ inspector_read_start(&socket_, OnBufferAlloc, ReadCallback_);
+}
+
+// static
+void SocketSession::ReadCallback_(uv_stream_t* stream, ssize_t read,
+ const uv_buf_t* buf) {
+ InspectorSocket* socket = inspector_from_stream(stream);
+ SocketSession::From(socket)->OnRemoteDataIO(socket, read, buf);
+}
+
+void SocketSession::OnRemoteDataIO(InspectorSocket* socket, ssize_t read,
+ const uv_buf_t* buf) {
+ if (read > 0) {
+ server_->Delegate()->MessageReceived(id_, std::string(buf->base, read));
+ } else {
+ server_->SessionTerminated(id_);
+ Close(true, nullptr);
+ }
+ if (buf != nullptr && buf->base != nullptr)
+ delete[] buf->base;
+}
+
+void SocketSession::Send(const std::string& message) {
+ inspector_write(&socket_, message.data(), message.length());
+}
+
+} // namespace inspector
+} // namespace node
diff --git a/src/inspector_socket_server.h b/src/inspector_socket_server.h
new file mode 100644
index 0000000000..4c139e138f
--- /dev/null
+++ b/src/inspector_socket_server.h
@@ -0,0 +1,77 @@
+#ifndef SRC_INSPECTOR_SOCKET_SERVER_H_
+#define SRC_INSPECTOR_SOCKET_SERVER_H_
+
+#include "inspector_agent.h"
+#include "inspector_socket.h"
+#include "uv.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#if !HAVE_INSPECTOR
+#error("This header can only be used when inspector is enabled")
+#endif
+
+namespace node {
+namespace inspector {
+
+class Closer;
+class SocketSession;
+
+class SocketServerDelegate {
+ public:
+ virtual bool StartSession(int session_id, const std::string& target_id) = 0;
+ virtual void EndSession(int session_id) = 0;
+ virtual void MessageReceived(int session_id, const std::string& message) = 0;
+ virtual std::vector<std::string> GetTargetIds() = 0;
+ virtual std::string GetTargetTitle(const std::string& id) = 0;
+ virtual std::string GetTargetUrl(const std::string& id) = 0;
+};
+
+class InspectorSocketServer {
+ public:
+ using ServerCallback = void (*)(InspectorSocketServer*);
+ InspectorSocketServer(SocketServerDelegate* delegate, int port);
+ bool Start(uv_loop_t* loop);
+ void Stop(ServerCallback callback);
+ void Send(int session_id, const std::string& message);
+ void TerminateConnections(ServerCallback callback);
+
+ private:
+ static bool HandshakeCallback(InspectorSocket* socket,
+ enum inspector_handshake_event state,
+ const std::string& path);
+ static void SocketConnectedCallback(uv_stream_t* server, int status);
+ static void ServerClosedCallback(uv_handle_t* server);
+ template<typename SomeUvStruct>
+ static InspectorSocketServer* From(SomeUvStruct* server) {
+ return node::ContainerOf(&InspectorSocketServer::server_,
+ reinterpret_cast<uv_tcp_t*>(server));
+ }
+ bool RespondToGet(InspectorSocket* socket, const std::string& path);
+ void SendListResponse(InspectorSocket* socket);
+ void ReadCallback(InspectorSocket* socket, ssize_t read, const uv_buf_t* buf);
+ bool SessionStarted(SocketSession* session, const std::string& id);
+ void SessionTerminated(int id);
+ bool TargetExists(const std::string& id);
+ static void SocketSessionDeleter(SocketSession*);
+ SocketServerDelegate* Delegate() { return delegate_; }
+
+ uv_loop_t* loop_;
+ SocketServerDelegate* const delegate_;
+ const int port_;
+ std::string path_;
+ uv_tcp_t server_;
+ Closer* closer_;
+ std::map<int, SocketSession*> connected_sessions_;
+ int next_session_id_;
+
+ friend class SocketSession;
+ friend class Closer;
+};
+
+} // namespace inspector
+} // namespace node
+
+#endif // SRC_INSPECTOR_SOCKET_SERVER_H_