summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/env-inl.h3
-rw-r--r--src/env.h12
-rw-r--r--src/inspector_agent.cc506
-rw-r--r--src/inspector_agent.h97
-rw-r--r--src/inspector_socket.cc679
-rw-r--r--src/inspector_socket.h57
-rw-r--r--src/node.cc67
-rw-r--r--src/node_internals.h6
-rw-r--r--src/signal_wrap.cc9
9 files changed, 1431 insertions, 5 deletions
diff --git a/src/env-inl.h b/src/env-inl.h
index 34f9bf7d72..97e1ba8f76 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -225,6 +225,9 @@ inline Environment::Environment(v8::Local<v8::Context> context,
makecallback_cntr_(0),
async_wrap_uid_(0),
debugger_agent_(this),
+#if HAVE_INSPECTOR
+ inspector_agent_(this),
+#endif
http_parser_buffer_(nullptr),
context_(context->GetIsolate(), context) {
// We'll be creating new objects so make sure we've entered the context.
diff --git a/src/env.h b/src/env.h
index 0c95abd56c..4c310c8831 100644
--- a/src/env.h
+++ b/src/env.h
@@ -5,6 +5,9 @@
#include "ares.h"
#include "debug-agent.h"
+#if HAVE_INSPECTOR
+#include "inspector_agent.h"
+#endif
#include "handle_wrap.h"
#include "req-wrap.h"
#include "tree.h"
@@ -549,6 +552,12 @@ class Environment {
return &debugger_agent_;
}
+#if HAVE_INSPECTOR
+ inline inspector::Agent* inspector_agent() {
+ return &inspector_agent_;
+ }
+#endif
+
typedef ListHead<HandleWrap, &HandleWrap::handle_wrap_queue_> HandleWrapQueue;
typedef ListHead<ReqWrap<uv_req_t>, &ReqWrap<uv_req_t>::req_wrap_queue_>
ReqWrapQueue;
@@ -586,6 +595,9 @@ class Environment {
size_t makecallback_cntr_;
int64_t async_wrap_uid_;
debugger::Agent debugger_agent_;
+#if HAVE_INSPECTOR
+ inspector::Agent inspector_agent_;
+#endif
HandleWrapQueue handle_wrap_queue_;
ReqWrapQueue req_wrap_queue_;
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
new file mode 100644
index 0000000000..cd2ae83b19
--- /dev/null
+++ b/src/inspector_agent.cc
@@ -0,0 +1,506 @@
+#include "inspector_agent.h"
+
+#include "node.h"
+#include "env.h"
+#include "env-inl.h"
+#include "node_version.h"
+#include "v8-platform.h"
+#include "util.h"
+
+#include "platform/v8_inspector/public/V8Inspector.h"
+#include "platform/inspector_protocol/FrontendChannel.h"
+#include "platform/inspector_protocol/String16.h"
+#include "platform/inspector_protocol/Values.h"
+
+#include "libplatform/libplatform.h"
+
+#include <string.h>
+
+// We need pid to use as ID with Chrome
+#if defined(_MSC_VER)
+#include <direct.h>
+#include <io.h>
+#define getpid GetCurrentProcessId
+#else
+#include <unistd.h> // setuid, getuid
+#endif
+
+namespace node {
+namespace {
+
+const char DEVTOOLS_PATH[] = "/node";
+
+void PrintDebuggerReadyMessage(int port) {
+ fprintf(stderr, "Debugger listening on port %d. "
+ "To start debugging, open the following URL in Chrome:\n"
+ " chrome-devtools://devtools/remote/serve_file/"
+ "@521e5b7e2b7cc66b4006a8a54cb9c4e57494a5ef/inspector.html?"
+ "experiments=true&v8only=true&ws=localhost:%d/node\n", port, port);
+}
+
+bool AcceptsConnection(inspector_socket_t* socket, const char* path) {
+ return strncmp(DEVTOOLS_PATH, path, sizeof(DEVTOOLS_PATH)) == 0;
+}
+
+void DisposeInspector(inspector_socket_t* socket, int status) {
+ free(socket);
+}
+
+void DisconnectAndDisposeIO(inspector_socket_t* socket) {
+ if (socket) {
+ inspector_close(socket, DisposeInspector);
+ }
+}
+
+void OnBufferAlloc(uv_handle_t* handle, size_t len, uv_buf_t* buf) {
+ if (len > 0) {
+ buf->base = static_cast<char*>(malloc(len));
+ CHECK_NE(buf->base, nullptr);
+ }
+ buf->len = len;
+}
+
+void SendHttpResponse(inspector_socket_t* socket, const char* response,
+ size_t len) {
+ 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: %ld\r\n"
+ "\r\n";
+ char header[sizeof(HEADERS) + 20];
+ int header_len = snprintf(header, sizeof(header), HEADERS, len);
+ inspector_write(socket, header, header_len);
+ inspector_write(socket, response, len);
+}
+
+void SendVersionResponse(inspector_socket_t* socket) {
+ const char VERSION_RESPONSE_TEMPLATE[] =
+ "[ {"
+ " \"Browser\": \"node.js/%s\","
+ " \"Protocol-Version\": \"1.1\","
+ " \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
+ "(KHTML, like Gecko) Chrome/45.0.2446.0 Safari/537.36\","
+ " \"WebKit-Version\": \"537.36 (@198122)\""
+ "} ]";
+ char buffer[sizeof(VERSION_RESPONSE_TEMPLATE) + 128];
+ size_t len = snprintf(buffer, sizeof(buffer), VERSION_RESPONSE_TEMPLATE,
+ NODE_VERSION);
+ ASSERT_LT(len, sizeof(buffer));
+ SendHttpResponse(socket, buffer, len);
+}
+
+void SendTargentsListResponse(inspector_socket_t* socket) {
+ const char LIST_RESPONSE_TEMPLATE[] =
+ "[ {"
+ " \"description\": \"node.js instance\","
+ " \"devtoolsFrontendUrl\": "
+ "\"https://chrome-devtools-frontend.appspot.com/serve_file/"
+ "@4604d24a75168768584760ba56d175507941852f/inspector.html\","
+ " \"faviconUrl\": \"https://nodejs.org/static/favicon.ico\","
+ " \"id\": \"%d\","
+ " \"title\": \"%s\","
+ " \"type\": \"node\","
+ " \"webSocketDebuggerUrl\": \"ws://%s\""
+ "} ]";
+ char buffer[sizeof(LIST_RESPONSE_TEMPLATE) + 4096];
+ char title[2048]; // uv_get_process_title trims the title if too long
+ int err = uv_get_process_title(title, sizeof(title));
+ ASSERT_EQ(0, err);
+ char* c = title;
+ while (!c) {
+ if (*c < ' ' || *c == '\"') {
+ *c = '_';
+ }
+ c++;
+ }
+ size_t len = snprintf(buffer, sizeof(buffer), LIST_RESPONSE_TEMPLATE,
+ getpid(), title, DEVTOOLS_PATH);
+ ASSERT_LT(len, sizeof(buffer));
+ SendHttpResponse(socket, buffer, len);
+}
+
+bool RespondToGet(inspector_socket_t* socket, const char* path) {
+ const char PATH[] = "/json";
+ const char PATH_LIST[] = "/json/list";
+ const char PATH_VERSION[] = "/json/version";
+ const char PATH_ACTIVATE[] = "/json/activate/";
+ if (!strncmp(PATH_VERSION, path, sizeof(PATH_VERSION))) {
+ SendVersionResponse(socket);
+ } else if (!strncmp(PATH_LIST, path, sizeof(PATH_LIST)) ||
+ !strncmp(PATH, path, sizeof(PATH))) {
+ SendTargentsListResponse(socket);
+ } else if (!strncmp(path, PATH_ACTIVATE, sizeof(PATH_ACTIVATE) - 1) &&
+ atoi(path + (sizeof(PATH_ACTIVATE) - 1)) == getpid()) {
+ const char TARGET_ACTIVATED[] = "Target activated";
+ SendHttpResponse(socket, TARGET_ACTIVATED, sizeof(TARGET_ACTIVATED) - 1);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace inspector {
+
+using blink::protocol::DictionaryValue;
+using blink::protocol::String16;
+
+void InterruptCallback(v8::Isolate*, void* agent) {
+ static_cast<Agent*>(agent)->PostMessages();
+}
+
+class DispatchOnInspectorBackendTask : public v8::Task {
+ public:
+ explicit DispatchOnInspectorBackendTask(Agent* agent) : agent_(agent) {}
+
+ void Run() override {
+ agent_->PostMessages();
+ }
+
+ private:
+ Agent* agent_;
+};
+
+class ChannelImpl final : public blink::protocol::FrontendChannel {
+ public:
+ explicit ChannelImpl(Agent* agent): agent_(agent) {}
+ virtual ~ChannelImpl() {}
+ private:
+ virtual void sendProtocolResponse(int sessionId, int callId,
+ std::unique_ptr<DictionaryValue> message)
+ override {
+ sendMessageToFrontend(std::move(message));
+ }
+
+ virtual void sendProtocolNotification(
+ std::unique_ptr<DictionaryValue> message) override {
+ sendMessageToFrontend(std::move(message));
+ }
+
+ virtual void flush() override { }
+
+ void sendMessageToFrontend(std::unique_ptr<DictionaryValue> message) {
+ agent_->Write(message->toJSONString().utf8());
+ }
+
+ Agent* const agent_;
+};
+
+class SetConnectedTask : public v8::Task {
+ public:
+ SetConnectedTask(Agent* agent, bool connected)
+ : agent_(agent),
+ connected_(connected) {}
+
+ void Run() override {
+ agent_->SetConnected(connected_);
+ }
+
+ private:
+ Agent* agent_;
+ bool connected_;
+};
+
+class V8NodeInspector : public blink::V8Inspector {
+ public:
+ V8NodeInspector(Agent* agent, node::Environment* env, v8::Platform* platform)
+ : blink::V8Inspector(env->isolate(), env->context()),
+ agent_(agent),
+ isolate_(env->isolate()),
+ platform_(platform),
+ terminated_(false),
+ running_nested_loop_(false) {}
+
+ void runMessageLoopOnPause(int context_group_id) override {
+ if (running_nested_loop_)
+ return;
+ terminated_ = false;
+ running_nested_loop_ = true;
+ do {
+ uv_mutex_lock(&agent_->pause_lock_);
+ uv_cond_wait(&agent_->pause_cond_, &agent_->pause_lock_);
+ uv_mutex_unlock(&agent_->pause_lock_);
+ while (v8::platform::PumpMessageLoop(platform_, isolate_))
+ {}
+ } while (!terminated_);
+ terminated_ = false;
+ running_nested_loop_ = false;
+ }
+
+ void quitMessageLoopOnPause() override {
+ terminated_ = true;
+ }
+
+ private:
+ Agent* agent_;
+ v8::Isolate* isolate_;
+ v8::Platform* platform_;
+ bool terminated_;
+ bool running_nested_loop_;
+};
+
+Agent::Agent(Environment* env) : port_(9229),
+ wait_(false),
+ connected_(false),
+ shutting_down_(false),
+ parent_env_(env),
+ client_socket_(nullptr),
+ inspector_(nullptr),
+ platform_(nullptr),
+ dispatching_messages_(false) {
+ int err;
+ err = uv_sem_init(&start_sem_, 0);
+ CHECK_EQ(err, 0);
+}
+
+Agent::~Agent() {
+ if (!inspector_)
+ return;
+ uv_mutex_destroy(&queue_lock_);
+ uv_mutex_destroy(&pause_lock_);
+ uv_cond_destroy(&pause_cond_);
+ uv_close(reinterpret_cast<uv_handle_t*>(&data_written_), nullptr);
+}
+
+void Agent::Start(v8::Platform* platform, int port, bool wait) {
+ auto env = parent_env_;
+ inspector_ = new V8NodeInspector(this, env, platform);
+
+ int err;
+
+ platform_ = platform;
+
+ err = uv_loop_init(&child_loop_);
+ CHECK_EQ(err, 0);
+ err = uv_async_init(env->event_loop(), &data_written_, nullptr);
+ CHECK_EQ(err, 0);
+ err = uv_mutex_init(&queue_lock_);
+ CHECK_EQ(err, 0);
+ err = uv_mutex_init(&pause_lock_);
+ CHECK_EQ(err, 0);
+ err = uv_cond_init(&pause_cond_);
+ CHECK_EQ(err, 0);
+
+ uv_unref(reinterpret_cast<uv_handle_t*>(&data_written_));
+
+ port_ = port;
+ wait_ = wait;
+
+ err = uv_thread_create(&thread_, Agent::ThreadCbIO, this);
+ CHECK_EQ(err, 0);
+ uv_sem_wait(&start_sem_);
+
+ if (wait) {
+ // Flush messages in case of wait to connect, see OnRemoteDataIO on how it
+ // should be fixed.
+ SetConnected(true);
+ PostMessages();
+ }
+}
+
+void Agent::Stop() {
+ // TODO(repenaxa): hop on the right thread.
+ DisconnectAndDisposeIO(client_socket_);
+ int err = uv_thread_join(&thread_);
+ CHECK_EQ(err, 0);
+
+ uv_run(&child_loop_, UV_RUN_NOWAIT);
+
+ err = uv_loop_close(&child_loop_);
+ CHECK_EQ(err, 0);
+ delete inspector_;
+}
+
+bool Agent::IsStarted() {
+ return !!platform_;
+}
+
+void Agent::WaitForDisconnect() {
+ shutting_down_ = true;
+ fprintf(stderr, "Waiting for the debugger to disconnect...\n");
+ inspector_->runMessageLoopOnPause(0);
+}
+
+// static
+void Agent::ThreadCbIO(void* agent) {
+ static_cast<Agent*>(agent)->WorkerRunIO();
+}
+
+// static
+void Agent::OnSocketConnectionIO(uv_stream_t* server, int status) {
+ if (status == 0) {
+ inspector_socket_t* socket =
+ static_cast<inspector_socket_t*>(malloc(sizeof(*socket)));
+ ASSERT_NE(nullptr, socket);
+ memset(socket, 0, sizeof(*socket));
+ socket->data = server->data;
+ if (inspector_accept(server, socket, Agent::OnInspectorHandshakeIO) != 0) {
+ free(socket);
+ }
+ }
+}
+
+// static
+bool Agent::OnInspectorHandshakeIO(inspector_socket_t* socket,
+ enum inspector_handshake_event state,
+ const char* path) {
+ Agent* agent = static_cast<Agent*>(socket->data);
+ switch (state) {
+ case kInspectorHandshakeHttpGet:
+ return RespondToGet(socket, path);
+ case kInspectorHandshakeUpgrading:
+ return AcceptsConnection(socket, path);
+ case kInspectorHandshakeUpgraded:
+ agent->OnInspectorConnectionIO(socket);
+ return true;
+ case kInspectorHandshakeFailed:
+ return false;
+ default:
+ UNREACHABLE();
+ }
+}
+
+// static
+void Agent::OnRemoteDataIO(uv_stream_t* stream,
+ ssize_t read,
+ const uv_buf_t* b) {
+ inspector_socket_t* socket = static_cast<inspector_socket_t*>(stream->data);
+ Agent* agent = static_cast<Agent*>(socket->data);
+ if (read > 0) {
+ std::string str(b->base, read);
+ agent->PushPendingMessage(&agent->message_queue_, str);
+ free(b->base);
+
+ // 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 (agent->wait_ && str.find("\"Runtime.run\"") != std::string::npos) {
+ agent->wait_ = false;
+ uv_sem_post(&agent->start_sem_);
+ }
+
+ agent->platform_->CallOnForegroundThread(agent->parent_env_->isolate(),
+ new DispatchOnInspectorBackendTask(agent));
+ agent->parent_env_->isolate()
+ ->RequestInterrupt(InterruptCallback, agent);
+ uv_async_send(&agent->data_written_);
+ } else if (read < 0) {
+ if (agent->client_socket_ == socket) {
+ agent->client_socket_ = nullptr;
+ }
+ DisconnectAndDisposeIO(socket);
+ } else {
+ // EOF
+ if (agent->client_socket_ == socket) {
+ agent->client_socket_ = nullptr;
+ agent->platform_->CallOnForegroundThread(agent->parent_env_->isolate(),
+ new SetConnectedTask(agent, false));
+ uv_async_send(&agent->data_written_);
+ }
+ }
+ uv_cond_broadcast(&agent->pause_cond_);
+}
+
+void Agent::PushPendingMessage(std::vector<std::string>* queue,
+ const std::string& message) {
+ uv_mutex_lock(&queue_lock_);
+ queue->push_back(message);
+ uv_mutex_unlock(&queue_lock_);
+}
+
+void Agent::SwapBehindLock(std::vector<std::string> Agent::*queue,
+ std::vector<std::string>* output) {
+ uv_mutex_lock(&queue_lock_);
+ (this->*queue).swap(*output);
+ uv_mutex_unlock(&queue_lock_);
+}
+
+// static
+void Agent::WriteCbIO(uv_async_t* async) {
+ Agent* agent = static_cast<Agent*>(async->data);
+ inspector_socket_t* socket = agent->client_socket_;
+ if (socket) {
+ std::vector<std::string> outgoing_messages;
+ agent->SwapBehindLock(&Agent::outgoing_message_queue_, &outgoing_messages);
+ for (auto const& message : outgoing_messages)
+ inspector_write(socket, message.c_str(), message.length());
+ }
+}
+
+void Agent::WorkerRunIO() {
+ sockaddr_in addr;
+ uv_tcp_t server;
+ int err = uv_async_init(&child_loop_, &io_thread_req_, Agent::WriteCbIO);
+ CHECK_EQ(0, err);
+ io_thread_req_.data = this;
+ 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) {
+ PrintDebuggerReadyMessage(port_);
+ } else {
+ fprintf(stderr, "Unable to open devtools socket: %s\n", uv_strerror(err));
+ ABORT();
+ }
+ 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);
+ uv_run(&child_loop_, UV_RUN_DEFAULT);
+}
+
+void Agent::OnInspectorConnectionIO(inspector_socket_t* socket) {
+ if (client_socket_) {
+ return;
+ }
+ client_socket_ = socket;
+ inspector_read_start(socket, OnBufferAlloc, Agent::OnRemoteDataIO);
+ platform_->CallOnForegroundThread(parent_env_->isolate(),
+ new SetConnectedTask(this, true));
+}
+
+void Agent::PostMessages() {
+ if (dispatching_messages_)
+ return;
+ dispatching_messages_ = true;
+ std::vector<std::string> messages;
+ SwapBehindLock(&Agent::message_queue_, &messages);
+ for (auto const& message : messages)
+ inspector_->dispatchMessageFromFrontend(
+ String16::fromUTF8(message.c_str(), message.length()));
+ uv_async_send(&data_written_);
+ dispatching_messages_ = false;
+}
+
+void Agent::SetConnected(bool connected) {
+ if (connected_ == connected)
+ return;
+
+ connected_ = connected;
+ if (connected) {
+ fprintf(stderr, "Debugger attached.\n");
+ inspector_->connectFrontend(new ChannelImpl(this));
+ } else {
+ if (!shutting_down_)
+ PrintDebuggerReadyMessage(port_);
+ inspector_->quitMessageLoopOnPause();
+ inspector_->disconnectFrontend();
+ }
+}
+
+void Agent::Write(const std::string& message) {
+ PushPendingMessage(&outgoing_message_queue_, message);
+ ASSERT_EQ(0, uv_async_send(&io_thread_req_));
+}
+} // namespace debugger
+} // namespace node
diff --git a/src/inspector_agent.h b/src/inspector_agent.h
new file mode 100644
index 0000000000..65a4abeff7
--- /dev/null
+++ b/src/inspector_agent.h
@@ -0,0 +1,97 @@
+#ifndef SRC_INSPECTOR_AGENT_H_
+#define SRC_INSPECTOR_AGENT_H_
+
+#if !HAVE_INSPECTOR
+#error("This header can only be used when inspector is enabled")
+#endif
+
+#include "inspector_socket.h"
+#include "uv.h"
+#include "v8.h"
+#include "util.h"
+
+#include <string>
+#include <vector>
+
+namespace blink {
+class V8Inspector;
+}
+
+// Forward declaration to break recursive dependency chain with src/env.h.
+namespace node {
+class Environment;
+} // namespace node
+
+namespace node {
+namespace inspector {
+
+class ChannelImpl;
+
+class Agent {
+ public:
+ explicit Agent(node::Environment* env);
+ ~Agent();
+
+ // Start the inspector agent thread
+ void Start(v8::Platform* platform, int port, bool wait);
+ // Stop the inspector agent
+ void Stop();
+
+ bool IsStarted();
+ bool connected() { return connected_; }
+ void WaitForDisconnect();
+
+ protected:
+ static void ThreadCbIO(void* agent);
+ static void OnSocketConnectionIO(uv_stream_t* server, int status);
+ static bool OnInspectorHandshakeIO(inspector_socket_t* socket,
+ enum inspector_handshake_event state,
+ const char* path);
+ static void OnRemoteDataIO(uv_stream_t* stream, ssize_t read,
+ const uv_buf_t* b);
+ static void WriteCbIO(uv_async_t* async);
+
+ void WorkerRunIO();
+ void OnInspectorConnectionIO(inspector_socket_t* socket);
+ void PushPendingMessage(std::vector<std::string>* queue,
+ const std::string& message);
+ void SwapBehindLock(std::vector<std::string> Agent::*queue,
+ std::vector<std::string>* output);
+ void PostMessages();
+ void SetConnected(bool connected);
+ void Write(const std::string& message);
+
+ uv_sem_t start_sem_;
+ uv_cond_t pause_cond_;
+ uv_mutex_t queue_lock_;
+ uv_mutex_t pause_lock_;
+ uv_thread_t thread_;
+ uv_loop_t child_loop_;
+ uv_tcp_t server_;
+
+ int port_;
+ bool wait_;
+ bool connected_;
+ bool shutting_down_;
+ node::Environment* parent_env_;
+
+ uv_async_t data_written_;
+ uv_async_t io_thread_req_;
+ inspector_socket_t* client_socket_;
+ blink::V8Inspector* inspector_;
+ v8::Platform* platform_;
+ std::vector<std::string> message_queue_;
+ std::vector<std::string> outgoing_message_queue_;
+ bool dispatching_messages_;
+
+ friend class ChannelImpl;
+ friend class DispatchOnInspectorBackendTask;
+ friend class SetConnectedTask;
+ friend class V8NodeInspector;
+ friend void InterruptCallback(v8::Isolate*, void* agent);
+};
+
+} // namespace inspector
+} // namespace node
+
+#endif // SRC_INSPECTOR_AGENT_H_
diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc
new file mode 100644
index 0000000000..cb248ec59f
--- /dev/null
+++ b/src/inspector_socket.cc
@@ -0,0 +1,679 @@
+#include "inspector_socket.h"
+
+#define NODE_WANT_INTERNALS 1
+#include "base64.h"
+
+#include "openssl/sha.h" // Sha-1 hash
+
+#include <string.h>
+#include <vector>
+
+#define ACCEPT_KEY_LENGTH base64_encoded_size(20)
+#define BUFFER_GROWTH_CHUNK_SIZE 1024
+
+#define DUMP_READS 0
+#define DUMP_WRITES 0
+
+static const char CLOSE_FRAME[] = {'\x88', '\x00'};
+
+struct http_parsing_state_s {
+ http_parser parser;
+ http_parser_settings parser_settings;
+ handshake_cb callback;
+ bool parsing_value;
+ char* ws_key;
+ char* path;
+ char* current_header;
+};
+
+struct ws_state_s {
+ uv_alloc_cb alloc_cb;
+ uv_read_cb read_cb;
+ inspector_cb close_cb;
+ bool close_sent;
+ bool received_close;
+};
+
+enum ws_decode_result {
+ FRAME_OK, FRAME_INCOMPLETE, FRAME_CLOSE, FRAME_ERROR
+};
+
+#if DUMP_READS || DUMP_WRITES
+static void dump_hex(const char* buf, size_t len) {
+ const char* ptr = buf;
+ const char* end = ptr + len;
+ const char* cptr;
+ char c;
+ int i;
+
+ while (ptr < end) {
+ cptr = ptr;
+ for (i = 0; i < 16 && ptr < end; i++) {
+ printf("%2.2X ", *(ptr++));
+ }
+ for (i = 72 - (i * 4); i > 0; i--) {
+ printf(" ");
+ }
+ for (i = 0; i < 16 && cptr < end; i++) {
+ c = *(cptr++);
+ printf("%c", (c > 0x19) ? c : '.');
+ }
+ printf("\n");
+ }
+ printf("\n\n");
+}
+#endif
+
+static void dispose_inspector(uv_handle_t* handle) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(handle->data);
+ inspector_cb close =
+ inspector->ws_mode ? inspector->ws_state->close_cb : nullptr;
+ free(inspector->buffer);
+ free(inspector->ws_state);
+ inspector->ws_state = nullptr;
+ inspector->buffer = nullptr;
+ inspector->buffer_size = 0;
+ inspector->data_len = 0;
+ inspector->last_read_end = 0;
+ if (close) {
+ close(inspector, 0);
+ }
+}
+
+static void close_connection(inspector_socket_t* inspector) {
+ uv_handle_t* socket = reinterpret_cast<uv_handle_t*>(&inspector->client);
+ if (!uv_is_closing(socket)) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(socket));
+ uv_close(socket, dispose_inspector);
+ } else if (inspector->ws_state->close_cb) {
+ inspector->ws_state->close_cb(inspector, 0);
+ }
+}
+
+// Cleanup
+static void write_request_cleanup(uv_write_t* req, int status) {
+ free((reinterpret_cast<uv_buf_t*>(req->data))->base);
+ free(req->data);
+ free(req);
+}
+
+static int write_to_client(inspector_socket_t* inspector,
+ const char* msg,
+ size_t len,
+ uv_write_cb write_cb = write_request_cleanup) {
+#if DUMP_WRITES
+ printf("%s (%ld bytes):\n", __FUNCTION__, len);
+ dump_hex(msg, len);
+#endif
+
+ // Freed in write_request_cleanup
+ uv_buf_t* buf = reinterpret_cast<uv_buf_t*>(malloc(sizeof(uv_buf_t)));
+ uv_write_t* req = reinterpret_cast<uv_write_t*>(malloc(sizeof(uv_write_t)));
+ CHECK_NE(buf, nullptr);
+ CHECK_NE(req, nullptr);
+ memset(req, 0, sizeof(*req));
+ buf->base = reinterpret_cast<char*>(malloc(len));
+
+ CHECK_NE(buf->base, nullptr);
+
+ memcpy(buf->base, msg, len);
+ buf->len = len;
+ req->data = buf;
+
+ uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(&inspector->client);
+ return uv_write(req, stream, buf, 1, write_cb) < 0;
+}
+
+// Constants for hybi-10 frame format.
+
+typedef int OpCode;
+
+const OpCode kOpCodeContinuation = 0x0;
+const OpCode kOpCodeText = 0x1;
+const OpCode kOpCodeBinary = 0x2;
+const OpCode kOpCodeClose = 0x8;
+const OpCode kOpCodePing = 0x9;
+const OpCode kOpCodePong = 0xA;
+
+const unsigned char kFinalBit = 0x80;
+const unsigned char kReserved1Bit = 0x40;
+const unsigned char kReserved2Bit = 0x20;
+const unsigned char kReserved3Bit = 0x10;
+const unsigned char kOpCodeMask = 0xF;
+const unsigned char kMaskBit = 0x80;
+const unsigned char kPayloadLengthMask = 0x7F;
+
+const size_t kMaxSingleBytePayloadLength = 125;
+const size_t kTwoBytePayloadLengthField = 126;
+const size_t kEightBytePayloadLengthField = 127;
+const size_t kMaskingKeyWidthInBytes = 4;
+
+static std::vector<char> encode_frame_hybi17(const char* message,
+ size_t data_length) {
+ std::vector<char> frame;
+ OpCode op_code = kOpCodeText;
+ frame.push_back(kFinalBit | op_code);
+ if (data_length <= kMaxSingleBytePayloadLength) {
+ frame.push_back(static_cast<char>(data_length));
+ } else if (data_length <= 0xFFFF) {
+ frame.push_back(kTwoBytePayloadLengthField);
+ frame.push_back((data_length & 0xFF00) >> 8);
+ frame.push_back(data_length & 0xFF);
+ } else {
+ frame.push_back(kEightBytePayloadLengthField);
+ char extended_payload_length[8];
+ size_t remaining = data_length;
+ // Fill the length into extended_payload_length in the network byte order.
+ for (int i = 0; i < 8; ++i) {
+ extended_payload_length[7 - i] = remaining & 0xFF;
+ remaining >>= 8;
+ }
+ frame.insert(frame.end(), extended_payload_length,
+ extended_payload_length + 8);
+ ASSERT_EQ(0, remaining);
+ }
+ frame.insert(frame.end(), message, message + data_length);
+ return frame;
+}
+
+static ws_decode_result decode_frame_hybi17(const char* buffer_begin,
+ size_t data_length,
+ bool client_frame,
+ int* bytes_consumed,
+ std::vector<char>* output,
+ bool* compressed) {
+ *bytes_consumed = 0;
+ if (data_length < 2)
+ return FRAME_INCOMPLETE;
+
+ const char* p = buffer_begin;
+ const char* buffer_end = p + data_length;
+
+ unsigned char first_byte = *p++;
+ unsigned char second_byte = *p++;
+
+ bool final = (first_byte & kFinalBit) != 0;
+ bool reserved1 = (first_byte & kReserved1Bit) != 0;
+ bool reserved2 = (first_byte & kReserved2Bit) != 0;
+ bool reserved3 = (first_byte & kReserved3Bit) != 0;
+ int op_code = first_byte & kOpCodeMask;
+ bool masked = (second_byte & kMaskBit) != 0;
+ *compressed = reserved1;
+ if (!final || reserved2 || reserved3)
+ return FRAME_ERROR; // Only compression extension is supported.
+
+ bool closed = false;
+ switch (op_code) {
+ case kOpCodeClose:
+ closed = true;
+ break;
+ case kOpCodeText:
+ break;
+ case kOpCodeBinary: // We don't support binary frames yet.
+ case kOpCodeContinuation: // We don't support binary frames yet.
+ case kOpCodePing: // We don't support binary frames yet.
+ case kOpCodePong: // We don't support binary frames yet.
+ default:
+ return FRAME_ERROR;
+ }
+
+ // In Hybi-17 spec client MUST mask its frame.
+ if (client_frame && !masked) {
+ return FRAME_ERROR;
+ }
+
+ uint64_t payload_length64 = second_byte & kPayloadLengthMask;
+ if (payload_length64 > kMaxSingleBytePayloadLength) {
+ int extended_payload_length_size;
+ if (payload_length64 == kTwoBytePayloadLengthField) {
+ extended_payload_length_size = 2;
+ } else if (payload_length64 == kEightBytePayloadLengthField) {
+ extended_payload_length_size = 8;
+ } else {
+ return FRAME_ERROR;
+ }
+ if (buffer_end - p < extended_payload_length_size)
+ return FRAME_INCOMPLETE;
+ payload_length64 = 0;
+ for (int i = 0; i < extended_payload_length_size; ++i) {
+ payload_length64 <<= 8;
+ payload_length64 |= static_cast<unsigned char>(*p++);
+ }
+ }
+
+ static const uint64_t max_payload_length = 0x7FFFFFFFFFFFFFFFull;
+ static const size_t max_length = SIZE_MAX;
+ if (payload_length64 > max_payload_length ||
+ payload_length64 > max_length - kMaskingKeyWidthInBytes) {
+ // WebSocket frame length too large.
+ return FRAME_ERROR;
+ }
+ size_t payload_length = static_cast<size_t>(payload_length64);
+
+ if (data_length - kMaskingKeyWidthInBytes < payload_length)
+ return FRAME_INCOMPLETE;
+
+ const char* masking_key = p;
+ const char* payload = p + kMaskingKeyWidthInBytes;
+ for (size_t i = 0; i < payload_length; ++i) // Unmask the payload.
+ output->insert(output->end(),
+ payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes]);
+
+ size_t pos = p + kMaskingKeyWidthInBytes + payload_length - buffer_begin;
+ *bytes_consumed = pos;
+ return closed ? FRAME_CLOSE : FRAME_OK;
+}
+
+static void invoke_read_callback(inspector_socket_t* inspector,
+ int status, const uv_buf_t* buf) {
+ if (inspector->ws_state->read_cb) {
+ inspector->ws_state->read_cb(
+ reinterpret_cast<uv_stream_t*>(&inspector->client), status, buf);
+ }
+}
+
+static void shutdown_complete(inspector_socket_t* inspector) {
+ if (inspector->ws_state->close_cb) {
+ inspector->ws_state->close_cb(inspector, 0);
+ }
+ close_connection(inspector);
+}
+
+static void on_close_frame_written(uv_write_t* write, int status) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(write->handle->data);
+ write_request_cleanup(write, status);
+ inspector->ws_state->close_sent = true;
+ if (inspector->ws_state->received_close) {
+ shutdown_complete(inspector);
+ }
+}
+
+static void close_frame_received(inspector_socket_t* inspector) {
+ inspector->ws_state->received_close = true;
+ if (!inspector->ws_state->close_sent) {
+ invoke_read_callback(inspector, 0, 0);
+ write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
+ on_close_frame_written);
+ } else {
+ shutdown_complete(inspector);
+ }
+}
+
+static int parse_ws_frames(inspector_socket_t* inspector, size_t len) {
+ int bytes_consumed = 0;
+ std::vector<char> output;
+ bool compressed = false;
+
+ ws_decode_result r = decode_frame_hybi17(inspector->buffer,
+ len, true /* client_frame */,
+ &bytes_consumed, &output,
+ &compressed);
+ // Compressed frame means client is ignoring the headers and misbehaves
+ if (compressed || r == FRAME_ERROR) {
+ invoke_read_callback(inspector, UV_EPROTO, nullptr);
+ close_connection(inspector);
+ bytes_consumed = 0;
+ } else if (r == FRAME_CLOSE) {
+ close_frame_received(inspector);
+ bytes_consumed = 0;
+ } else if (r == FRAME_OK && inspector->ws_state->alloc_cb
+ && inspector->ws_state->read_cb) {
+ uv_buf_t buffer;
+ size_t len = output.size();
+ inspector->ws_state->alloc_cb(
+ reinterpret_cast<uv_handle_t*>(&inspector->client),
+ len, &buffer);
+ CHECK_GE(buffer.len, len);
+ memcpy(buffer.base, &output[0], len);
+ invoke_read_callback(inspector, len, &buffer);
+ }
+ return bytes_consumed;
+}
+
+static void prepare_buffer(uv_handle_t* stream, size_t len, uv_buf_t* buf) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+
+ if (len > (inspector->buffer_size - inspector->data_len)) {
+ int new_size = (inspector->data_len + len + BUFFER_GROWTH_CHUNK_SIZE - 1) /
+ BUFFER_GROWTH_CHUNK_SIZE *
+ BUFFER_GROWTH_CHUNK_SIZE;
+ inspector->buffer_size = new_size;
+ inspector->buffer = reinterpret_cast<char*>(realloc(inspector->buffer,
+ inspector->buffer_size));
+ ASSERT_NE(inspector->buffer, nullptr);
+ }
+ buf->base = inspector->buffer + inspector->data_len;
+ buf->len = len;
+ inspector->data_len += len;
+}
+
+static void websockets_data_cb(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(stream->data);
+ if (nread < 0 || nread == UV_EOF) {
+ inspector->connection_eof = true;
+ if (!inspector->shutting_down && inspector->ws_state->read_cb) {
+ inspector->ws_state->read_cb(stream, nread, nullptr);
+ }
+ } else {
+ #if DUMP_READS
+ printf("%s read %ld bytes\n", __FUNCTION__, nread);
+ if (nread > 0) {
+ dump_hex(buf->base, nread);
+ }
+ #endif
+ // 1. Move read bytes to continue the buffer
+ // Should be same as this is supposedly last buffer
+ ASSERT_EQ(buf->base + buf->len, inspector->buffer + inspector->data_len);
+
+ // Should be noop...
+ memmove(inspector->buffer + inspector->last_read_end, buf->base, nread);
+ inspector->last_read_end += nread;
+
+ // 2. Parse.
+ int processed = 0;
+ do {
+ processed = parse_ws_frames(inspector, inspector->last_read_end);
+ // 3. Fix the buffer size & length
+ if (processed > 0) {
+ memmove(inspector->buffer, inspector->buffer + processed,
+ inspector->last_read_end - processed);
+ inspector->last_read_end -= processed;
+ inspector->data_len = inspector->last_read_end;
+ }
+ } while (processed > 0 && inspector->data_len > 0);
+ }
+}
+
+int inspector_read_start(inspector_socket_t* inspector,
+ uv_alloc_cb alloc_cb, uv_read_cb read_cb) {
+ ASSERT(inspector->ws_mode);
+ ASSERT(!inspector->shutting_down || read_cb == nullptr);
+ inspector->ws_state->close_sent = false;
+ inspector->ws_state->alloc_cb = alloc_cb;
+ inspector->ws_state->read_cb = read_cb;
+ int err =
+ uv_read_start(reinterpret_cast<uv_stream_t*>(&inspector->client),
+ prepare_buffer,
+ websockets_data_cb);
+ if (err < 0) {
+ close_connection(inspector);
+ }
+ return err;
+}
+
+void inspector_read_stop(inspector_socket_t* inspector) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(&inspector->client));
+ inspector->ws_state->alloc_cb = nullptr;
+ inspector->ws_state->read_cb = nullptr;
+}
+
+static void generate_accept_string(const char* client_key, char* buffer) {
+ // Magic string from websockets spec.
+ const char ws_magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ size_t key_len = strlen(client_key);
+ size_t magic_len = sizeof(ws_magic) - 1;
+
+ char* buf = reinterpret_cast<char*>(malloc(key_len + magic_len));
+ CHECK_NE(buf, nullptr);
+ memcpy(buf, client_key, key_len);
+ memcpy(buf + key_len, ws_magic, magic_len);
+ char hash[20];
+ SHA1((unsigned char*) buf, key_len + magic_len, (unsigned char*) hash);
+ free(buf);
+ node::base64_encode(hash, 20, buffer, ACCEPT_KEY_LENGTH);
+ buffer[ACCEPT_KEY_LENGTH] = '\0';
+}
+
+static void append(char** value, const char* string, size_t length) {
+ const size_t INCREMENT = 500; // There should never be more then 1 chunk...
+
+ int current_len = *value ? strlen(*value) : 0;
+ int new_len = current_len + length;
+ int adjusted = (new_len / INCREMENT + 1) * INCREMENT;
+ *value = reinterpret_cast<char*>(realloc(*value, adjusted));
+ memcpy(*value + current_len, string, length);
+ (*value)[new_len] = '\0';
+}
+
+static int header_value_cb(http_parser* parser, const char* at, size_t length) {
+ char SEC_WEBSOCKET_KEY_HEADER[] = "Sec-WebSocket-Key";
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ state->parsing_value = true;
+ if (state->current_header && strncmp(state->current_header,
+ SEC_WEBSOCKET_KEY_HEADER,
+ sizeof(SEC_WEBSOCKET_KEY_HEADER)) == 0) {
+ append(&state->ws_key, at, length);
+ }
+ return 0;
+}
+
+static int header_field_cb(http_parser* parser, const char* at, size_t length) {
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ if (state->parsing_value) {
+ state->parsing_value = false;
+ if (state->current_header)
+ state->current_header[0] = '\0';
+ }
+ append(&state->current_header, at, length);
+ return 0;
+}
+
+static int path_cb(http_parser* parser, const char* at, size_t length) {
+ struct http_parsing_state_s* state = (struct http_parsing_state_s*)
+ (reinterpret_cast<inspector_socket_t*>(parser->data))->http_parsing_state;
+ append(&state->path, at, length);
+ return 0;
+}
+
+static void handshake_complete(inspector_socket_t* inspector) {
+ uv_read_stop(reinterpret_cast<uv_stream_t*>(&inspector->client));
+ handshake_cb callback = inspector->http_parsing_state->callback;
+ inspector->ws_state = (struct ws_state_s*) malloc(sizeof(struct ws_state_s));
+ ASSERT_NE(nullptr, inspector->ws_state);
+ memset(inspector->ws_state, 0, sizeof(struct ws_state_s));
+ inspector->last_read_end = 0;
+ inspector->ws_mode = true;
+ callback(inspector, kInspectorHandshakeUpgraded,
+ inspector->http_parsing_state->path);
+}
+
+static void cleanup_http_parsing_state(struct http_parsing_state_s* state) {
+ free(state->current_header);
+ free(state->path);
+ free(state->ws_key);
+ free(state);
+}
+
+static void handshake_failed(inspector_socket_t* inspector) {
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ const char HANDSHAKE_FAILED_RESPONSE[] =
+ "HTTP/1.0 400 Bad Request\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n"
+ "WebSockets request was expected\r\n";
+ write_to_client(inspector, HANDSHAKE_FAILED_RESPONSE,
+ sizeof(HANDSHAKE_FAILED_RESPONSE) - 1);
+ close_connection(inspector);
+ inspector->http_parsing_state = nullptr;
+ state->callback(inspector, kInspectorHandshakeFailed, state->path);
+}
+
+// init_handshake references message_complete_cb
+static void init_handshake(inspector_socket_t* inspector);
+
+static int message_complete_cb(http_parser* parser) {
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>(parser->data);
+ struct http_parsing_state_s* state =
+ (struct http_parsing_state_s*) inspector->http_parsing_state;
+ if (parser->method != HTTP_GET) {
+ handshake_failed(inspector);
+ } else if (!parser->upgrade) {
+ if (state->callback(inspector, kInspectorHandshakeHttpGet, state->path)) {
+ init_handshake(inspector);
+ } else {
+ handshake_failed(inspector);
+ }
+ } else if (!state->ws_key) {
+ handshake_failed(inspector);
+ } else if (state->callback(inspector, kInspectorHandshakeUpgrading,
+ state->path)) {
+ char accept_string[ACCEPT_KEY_LENGTH + 1];
+ generate_accept_string(state->ws_key, accept_string);
+
+ const char accept_ws_prefix[] = "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: ";
+ const char accept_ws_suffix[] = "\r\n\r\n";
+ // Format has two chars (%s) that are replaced with actual key
+ char accept_response[sizeof(accept_ws_prefix) - 1 +
+ sizeof(accept_ws_suffix) - 1 +
+ ACCEPT_KEY_LENGTH];
+ memcpy(accept_response, accept_ws_prefix, sizeof(accept_ws_prefix) - 1);
+ memcpy(accept_response + sizeof(accept_ws_prefix) - 1,
+ accept_string, ACCEPT_KEY_LENGTH);
+ memcpy(accept_response + sizeof(accept_ws_prefix) - 1 + ACCEPT_KEY_LENGTH,
+ accept_ws_suffix, sizeof(accept_ws_suffix) - 1);
+ int len = sizeof(accept_response);
+ if (write_to_client(inspector, accept_response, len) >= 0) {
+ handshake_complete(inspector);
+ } else {
+ state->callback(inspector, kInspectorHandshakeFailed, nullptr);
+ close_connection(inspector);
+ }
+ inspector->http_parsing_state = nullptr;
+ } else {
+ handshake_failed(inspector);
+ }
+ return 0;
+}
+
+static void data_received_cb(uv_stream_s* client, ssize_t nread,
+ const uv_buf_t* buf) {
+#if DUMP_READS
+ if (nread >= 0) {
+ printf("%s (%ld bytes)\n", __FUNCTION__, nread);
+ dump_hex(buf->base, nread);
+ } else {
+ printf("[%s:%d] %s\n", __FUNCTION__, __LINE__, uv_err_name(nread));
+ }
+#endif
+ inspector_socket_t* inspector =
+ reinterpret_cast<inspector_socket_t*>((client->data));
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ if (nread < 0 || nread == UV_EOF) {
+ inspector->http_parsing_state->callback(inspector,
+ kInspectorHandshakeFailed,
+ nullptr);
+ close_connection(inspector);
+ inspector->http_parsing_state = nullptr;
+ } else {
+ http_parser* parser = &state->parser;
+ ssize_t parsed = http_parser_execute(parser, &state->parser_settings,
+ inspector->buffer,
+ nread);
+ if (parsed < nread) {
+ handshake_failed(inspector);
+ }
+ inspector->data_len = 0;
+ }
+
+ if (inspector->http_parsing_state == nullptr) {
+ cleanup_http_parsing_state(state);
+ }
+}
+
+static void init_handshake(inspector_socket_t* inspector) {
+ http_parsing_state_s* state = inspector->http_parsing_state;
+ CHECK_NE(state, nullptr);
+ if (state->current_header) {
+ state->current_header[0] = '\0';
+ }
+ if (state->ws_key) {
+ state->ws_key[0] = '\0';
+ }
+ if (state->path) {
+ state->path[0] = '\0';
+ }
+ http_parser_init(&state->parser, HTTP_REQUEST);
+ state->parser.data = inspector;
+ http_parser_settings* settings = &state->parser_settings;
+ http_parser_settings_init(settings);
+ settings->on_header_field = header_field_cb;
+ settings->on_header_value = header_value_cb;
+ settings->on_message_complete = message_complete_cb;
+ settings->on_url = path_cb;
+}
+
+int inspector_accept(uv_stream_t* server, inspector_socket_t* inspector,
+ handshake_cb callback) {
+ ASSERT_NE(callback, nullptr);
+ // The only field that users should care about.
+ void* data = inspector->data;
+ memset(inspector, 0, sizeof(*inspector));
+ inspector->data = data;
+
+ inspector->http_parsing_state = (struct http_parsing_state_s*)
+ malloc(sizeof(struct http_parsing_state_s));
+ ASSERT_NE(nullptr, inspector->http_parsing_state);
+ memset(inspector->http_parsing_state, 0, sizeof(struct http_parsing_state_s));
+ uv_stream_t* client = reinterpret_cast<uv_stream_t*>(&inspector->client);
+ CHECK_NE(client, nullptr);
+ int err = uv_tcp_init(server->loop, &inspector->client);
+
+ if (err == 0) {
+ err = uv_accept(server, client);
+ }
+ if (err == 0) {
+ client->data = inspector;
+ init_handshake(inspector);
+ inspector->http_parsing_state->callback = callback;
+ err = uv_read_start(client, prepare_buffer,
+ data_received_cb);
+ }
+ if (err != 0) {
+ uv_close(reinterpret_cast<uv_handle_t*>(client), NULL);
+ }
+ return err;
+}
+
+void inspector_write(inspector_socket_t* inspector, const char* data,
+ size_t len) {
+ if (inspector->ws_mode) {
+ std::vector<char> output = encode_frame_hybi17(data, len);
+ write_to_client(inspector, &output[0], output.size());
+ } else {
+ write_to_client(inspector, data, len);
+ }
+}
+
+void inspector_close(inspector_socket_t* inspector,
+ inspector_cb callback) {
+ // libuv throws assertions when closing stream that's already closed - we
+ // need to do the same.
+ ASSERT(!uv_is_closing(reinterpret_cast<uv_handle_t*>(&inspector->client)));
+ ASSERT(!inspector->shutting_down);
+ inspector->shutting_down = true;
+ inspector->ws_state->close_cb = callback;
+ if (inspector->connection_eof) {
+ close_connection(inspector);
+ } else {
+ inspector_read_stop(inspector);
+ write_to_client(inspector, CLOSE_FRAME, sizeof(CLOSE_FRAME),
+ on_close_frame_written);
+ inspector_read_start(inspector, nullptr, nullptr);
+ }
+}
+
+bool inspector_is_active(const struct inspector_socket_s* inspector) {
+ const uv_handle_t* client =
+ reinterpret_cast<const uv_handle_t*>(&inspector->client);
+ return !inspector->shutting_down && !uv_is_closing(client);
+}
diff --git a/src/inspector_socket.h b/src/inspector_socket.h
new file mode 100644
index 0000000000..3e52762e71
--- /dev/null
+++ b/src/inspector_socket.h
@@ -0,0 +1,57 @@
+#ifndef SRC_INSPECTOR_SOCKET_H_
+#define SRC_INSPECTOR_SOCKET_H_
+
+#include "http_parser.h"
+#include "uv.h"
+
+enum inspector_handshake_event {
+ kInspectorHandshakeUpgrading,
+ kInspectorHandshakeUpgraded,
+ kInspectorHandshakeHttpGet,
+ kInspectorHandshakeFailed
+};
+
+struct inspector_socket_s;
+
+typedef void (*inspector_cb)(struct inspector_socket_s*, int);
+// Notifies as handshake is progressing. Returning false as a response to
+// kInspectorHandshakeUpgrading or kInspectorHandshakeHttpGet event will abort
+// the connection. inspector_write can be used from the callback.
+typedef bool (*handshake_cb)(struct inspector_socket_s*,
+ enum inspector_handshake_event state,
+ const char* path);
+
+struct http_parsing_state_s;
+struct ws_state_s;
+
+struct inspector_socket_s {
+ void* data;
+ struct http_parsing_state_s* http_parsing_state;
+ struct ws_state_s* ws_state;
+ char* buffer;
+ size_t buffer_size;
+ size_t data_len;
+ size_t last_read_end;
+ uv_tcp_t client;
+ bool ws_mode;
+ bool shutting_down;
+ bool connection_eof;
+};
+
+typedef struct inspector_socket_s inspector_socket_t;
+
+int inspector_accept(uv_stream_t* server, struct inspector_socket_s* inspector,
+ handshake_cb callback);
+
+void inspector_close(struct inspector_socket_s* inspector,
+ inspector_cb callback);
+
+// Callbacks will receive handles that has inspector in data field...
+int inspector_read_start(struct inspector_socket_s* inspector, uv_alloc_cb,
+ uv_read_cb);
+void inspector_read_stop(struct inspector_socket_s* inspector);
+void inspector_write(struct inspector_socket_s* inspector,
+ const char* data, size_t len);
+bool inspector_is_active(const struct inspector_socket_s* inspector);
+
+#endif // SRC_INSPECTOR_SOCKET_H_
diff --git a/src/node.cc b/src/node.cc
index cda2bac096..258ebb596a 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -137,6 +137,9 @@ static bool track_heap_objects = false;
static const char* eval_string = nullptr;
static unsigned int preload_module_count = 0;
static const char** preload_modules = nullptr;
+#if HAVE_INSPECTOR
+static bool use_inspector = false;
+#endif
static bool use_debug_agent = false;
static bool debug_wait_connect = false;
static int debug_port = 5858;
@@ -3412,6 +3415,22 @@ static bool ParseDebugOpt(const char* arg) {
port = arg + sizeof("--debug-brk=") - 1;
} else if (!strncmp(arg, "--debug-port=", sizeof("--debug-port=") - 1)) {
port = arg + sizeof("--debug-port=") - 1;
+#if HAVE_INSPECTOR
+ // Specifying both --inspect and --debug means debugging is on, using Chromium
+ // inspector.
+ } else if (!strcmp(arg, "--inspect")) {
+ use_debug_agent = true;
+ use_inspector = true;
+ } else if (!strncmp(arg, "--inspect=", sizeof("--inspect=") - 1)) {
+ use_debug_agent = true;
+ use_inspector = true;
+ port = arg + sizeof("--inspect=") - 1;
+#else
+ } else if (!strncmp(arg, "--inspect", sizeof("--inspect") - 1)) {
+ fprintf(stderr,
+ "Inspector support is not available with this Node.js build\n");
+ return false;
+#endif
} else {
return false;
}
@@ -3682,10 +3701,19 @@ static void DispatchMessagesDebugAgentCallback(Environment* env) {
static void StartDebug(Environment* env, bool wait) {
CHECK(!debugger_running);
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ env->inspector_agent()->Start(default_platform, debug_port, wait);
+ debugger_running = true;
+ } else {
+#endif
+ env->debugger_agent()->set_dispatch_handler(
+ DispatchMessagesDebugAgentCallback);
+ debugger_running = env->debugger_agent()->Start(debug_port, wait);
+#if HAVE_INSPECTOR
+ }
+#endif
- env->debugger_agent()->set_dispatch_handler(
- DispatchMessagesDebugAgentCallback);
- debugger_running = env->debugger_agent()->Start(debug_port, wait);
if (debugger_running == false) {
fprintf(stderr, "Starting debugger on port %d failed\n", debug_port);
fflush(stderr);
@@ -3697,6 +3725,11 @@ static void StartDebug(Environment* env, bool wait) {
// Called from the main thread.
static void EnableDebug(Environment* env) {
CHECK(debugger_running);
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ return;
+ }
+#endif
// Send message to enable debug in workers
HandleScope handle_scope(env->isolate());
@@ -3991,7 +4024,15 @@ static void DebugPause(const FunctionCallbackInfo<Value>& args) {
static void DebugEnd(const FunctionCallbackInfo<Value>& args) {
if (debugger_running) {
Environment* env = Environment::GetCurrent(args);
- env->debugger_agent()->Stop();
+#if HAVE_INSPECTOR
+ if (use_inspector) {
+ env->inspector_agent()->Stop();
+ } else {
+#endif
+ env->debugger_agent()->Stop();
+#if HAVE_INSPECTOR
+ }
+#endif
debugger_running = false;
}
}
@@ -4420,6 +4461,24 @@ static void StartNodeInstance(void* arg) {
instance_data->set_exit_code(exit_code);
RunAtExit(env);
+#if HAVE_INSPECTOR
+ if (env->inspector_agent()->connected()) {
+ // Restore signal dispositions, the app is done and is no longer
+ // capable of handling signals.
+#ifdef __POSIX__
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+ for (unsigned nr = 1; nr < 32; nr += 1) {
+ if (nr == SIGKILL || nr == SIGSTOP || nr == SIGPROF)
+ continue;
+ act.sa_handler = (nr == SIGPIPE) ? SIG_IGN : SIG_DFL;
+ CHECK_EQ(0, sigaction(nr, &act, nullptr));
+ }
+#endif
+ env->inspector_agent()->WaitForDisconnect();
+ }
+#endif
+
#if defined(LEAK_SANITIZER)
__lsan_do_leak_check();
#endif
diff --git a/src/node_internals.h b/src/node_internals.h
index 2875f5ac79..64134d9ab8 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -221,7 +221,7 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
// by clearing all callbacks that could handle the error.
void ClearFatalExceptionHandlers(Environment* env);
-enum NodeInstanceType { MAIN, WORKER };
+enum NodeInstanceType { MAIN, WORKER, REMOTE_DEBUG_SERVER };
class NodeInstanceData {
public:
@@ -265,6 +265,10 @@ class NodeInstanceData {
return node_instance_type_ == WORKER;
}
+ bool is_remote_debug_server() {
+ return node_instance_type_ == REMOTE_DEBUG_SERVER;
+ }
+
int argc() {
return argc_;
}
diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc
index 3ee0251f9b..8d31dbf623 100644
--- a/src/signal_wrap.cc
+++ b/src/signal_wrap.cc
@@ -65,6 +65,15 @@ class SignalWrap : public HandleWrap {
SignalWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
int signum = args[0]->Int32Value();
+#if defined(__POSIX__) && defined(HAVE_INSPECTOR)
+ if (signum == SIGPROF) {
+ Environment* env = Environment::GetCurrent(args);
+ if (env->inspector_agent()->IsStarted()) {
+ fprintf(stderr, "process.on(SIGPROF) is reserved while debugging\n");
+ return;
+ }
+ }
+#endif
int err = uv_signal_start(&wrap->handle_, OnSignal, signum);
args.GetReturnValue().Set(err);
}