From 5163f3c7728d412ab9dc3a1f37e26941995b9d68 Mon Sep 17 00:00:00 2001 From: liuweili Date: Sat, 11 Jan 2025 15:09:00 +0800 Subject: [PATCH 1/3] add inspector_socket_server Signed-off-by: liuweili --- src/inspector/inspector_socket_server.cpp | 620 ++++++++++++++++++++++ src/inspector/inspector_socket_server.h | 118 ++++ 2 files changed, 738 insertions(+) create mode 100644 src/inspector/inspector_socket_server.cpp create mode 100644 src/inspector/inspector_socket_server.h diff --git a/src/inspector/inspector_socket_server.cpp b/src/inspector/inspector_socket_server.cpp new file mode 100644 index 0000000..86405df --- /dev/null +++ b/src/inspector/inspector_socket_server.cpp @@ -0,0 +1,620 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "inspector_socket_server.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jsvm_version.h" +#include "uv.h" +#include "v8_inspector_protocol_json.h" +#include "zlib.h" + +namespace jsvm { +namespace inspector { + +// Function is declared in inspector_io.h so the rest of the node does not +// depend on inspector_socket_server.h +std::string FormatWsAddress(const std::string& host, int port, const std::string& targetId, bool includeProtocol); +namespace { +void Escape(std::string* string) +{ + for (char& c : *string) { + c = (c == '\"' || c == '\\') ? '_' : c; + } +} + +std::string FormatHostPort(const std::string& host, int port) +{ + // Host is valid (socket was bound) so colon means it's a v6 IP address + bool v6 = host.find(':') != std::string::npos; + std::ostringstream url; + if (v6) { + url << '['; + } + url << host; + if (v6) { + url << ']'; + } + url << ':' << port; + return url.str(); +} + +std::string FormatAddress(const std::string& host, const std::string& targetId, bool includeProtocol) +{ + std::ostringstream url; + if (includeProtocol) { + url << "ws://"; + } + url << host << '/' << targetId; + return url.str(); +} + +std::string MapToString(const std::map& object) +{ + bool first = true; + std::ostringstream json; + json << "{\n"; + for (const auto& nameValue : object) { + if (!first) { + json << ",\n"; + } + first = false; + json << " \"" << nameValue.first << "\": \""; + json << nameValue.second << "\""; + } + json << "\n} "; + return json.str(); +} + +std::string MapsToString(const std::vector>& 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 SendHttpResponse(InspectorSocket* socket, 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" + "Cache-Control: no-cache\r\n" + "Content-Length: %zu\r\n" + "\r\n"; + char header[sizeof(headers) + 20]; + int headerLen = snprintf(header, sizeof(header), headers, code, response.size()); + socket->Write(header, headerLen); + socket->Write(response.data(), response.size()); +} + +void SendVersionResponse(InspectorSocket* socket) +{ + std::map response; + response["Browser"] = "jsvm/" JSVM_VERSION_STRING; + response["Protocol-Version"] = "1.1"; + SendHttpResponse(socket, MapToString(response), 200); +} + +void SendHttpNotFound(InspectorSocket* socket) +{ + SendHttpResponse(socket, "", 404); +} + +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 K_DECOMPRESSED_SIZE = + PROTOCOL_JSON[0] * 0x10000u + PROTOCOL_JSON[1] * 0x100u + PROTOCOL_JSON[2]; + strm.next_in = const_cast(PROTOCOL_JSON + 3); + strm.avail_in = sizeof(PROTOCOL_JSON) - 3; + std::string data(K_DECOMPRESSED_SIZE, '\0'); + strm.next_out = reinterpret_cast(data.data()); + 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, 200); +} +} // namespace + +std::string FormatWsAddress(const std::string& host, int port, const std::string& targetId, bool includeProtocol) +{ + return FormatAddress(FormatHostPort(host, port), targetId, includeProtocol); +} + +class SocketSession { +public: + SocketSession(InspectorSocketServer* server, int id, int serverPort); + void Close() + { + wsSocket.reset(); + } + void Send(const std::string& message); + void Own(InspectorSocket::Pointer wsSocketParam) + { + wsSocket = std::move(wsSocketParam); + } + int GetId() const + { + return id; + } + int ServerPort() + { + return serverPort; + } + InspectorSocket* GetWsSocket() + { + return wsSocket.get(); + } + void Accept(const std::string& wsKey) + { + wsSocket->AcceptUpgrade(wsKey); + } + void Decline() + { + wsSocket->CancelHandshake(); + } + + class Delegate : public InspectorSocket::Delegate { + public: + Delegate(InspectorSocketServer* server, int sessionId) : server(server), usessionId(sessionId) {} + ~Delegate() override + { + server->SessionTerminated(usessionId); + } + void OnHttpGet(const std::string& host, const std::string& path) override; + void OnSocketUpgrade(const std::string& host, const std::string& path, const std::string& wsKey) override; + void OnWsFrame(const std::vector& data) override; + + private: + SocketSession* Session() + { + return server->Session(usessionId); + } + + InspectorSocketServer* server; + int usessionId; + }; + +private: + const int id; + InspectorSocket::Pointer wsSocket; + const int serverPort; +}; + +class ServerSocket { +public: + explicit ServerSocket(InspectorSocketServer* server) + : tcpSocket(uv_tcp_t()), server(server), unixSocket(uv_pipe_t()) + {} + int Listen(sockaddr* addr, uv_loop_t* loop, int pid = -1); + void Close() + { + uv_close(reinterpret_cast(&tcpSocket), FreeOnCloseCallback); + } + void CloseUnix() + { + if (unixSocketOn) { + uv_close(reinterpret_cast(&unixSocket), nullptr); + unixSocketOn = false; + } + } + int GetPort() const + { + return port; + } + +private: + template + static ServerSocket* FromTcpSocket(UvHandle* socket) + { + return jsvm::inspector::ContainerOf(&ServerSocket::tcpSocket, reinterpret_cast(socket)); + } + static void SocketConnectedCallback(uv_stream_t* tcpSocket, int status); + static void UnixSocketConnectedCallback(uv_stream_t* unixSocket, int status); + static void FreeOnCloseCallback(uv_handle_t* tcpSocket) + { + delete FromTcpSocket(tcpSocket); + } + int DetectPort(uv_loop_t* loop, int pid); + ~ServerSocket() = default; + + uv_tcp_t tcpSocket; + InspectorSocketServer* server; + uv_pipe_t unixSocket; + int port = -1; + bool unixSocketOn = false; +}; + +void PrintDebuggerReadyMessage(const std::string& host, + const std::vector& serverSockets, + const std::vector& ids, + const char* verb, + bool publishUidStderr, + FILE* out) +{ + if (!publishUidStderr || out == nullptr) { + return; + } + for (const auto& serverSocket : serverSockets) { + for (const std::string& id : ids) { + fprintf(out, "Debugger %s on %s\n", verb, FormatWsAddress(host, serverSocket->GetPort(), id, true).c_str()); + } + } + fprintf(out, "For help, see: %s\n", "https://nodejs.org/en/docs/inspector"); + fflush(out); +} + +InspectorSocketServer::InspectorSocketServer(std::unique_ptr delegateParam, + uv_loop_t* loop, + const std::string& host, + int port, + const InspectPublishUid& inspectPublishUid, + FILE* out, + int pid) + : loop(loop), delegate(std::move(delegateParam)), host(host), port(port), inspectPublishUid(inspectPublishUid), + nextSessionId(0), out(out), pid(pid) +{ + delegate->AssignServer(this); + state = ServerState::kNew; +} + +InspectorSocketServer::~InspectorSocketServer() = default; + +SocketSession* InspectorSocketServer::Session(int sessionId) +{ + auto it = connectedSessions.find(sessionId); + return it == connectedSessions.end() ? nullptr : it->second.second.get(); +} + +void InspectorSocketServer::SessionStarted(int sessionId, const std::string& targetId, const std::string& wsKey) +{ + SocketSession* session = Session(sessionId); + if (!TargetExists(targetId)) { + session->Decline(); + return; + } + connectedSessions[sessionId].first = targetId; + session->Accept(wsKey); + delegate->StartSession(sessionId, targetId); +} + +void InspectorSocketServer::SessionTerminated(int sessionId) +{ + if (Session(sessionId) == nullptr) { + return; + } + bool wasAttached = connectedSessions[sessionId].first != ""; + if (wasAttached) { + delegate->EndSession(sessionId); + } + connectedSessions.erase(sessionId); + if (connectedSessions.empty()) { + if (wasAttached && state == ServerState::kRunning && !serverSockets.empty()) { + PrintDebuggerReadyMessage(host, serverSockets, delegate->GetTargetIds(), "ending", + inspectPublishUid.console, out); + } + if (state == ServerState::kStopped) { + delegate.reset(); + } + } +} + +bool InspectorSocketServer::HandleGetRequest(int sessionId, const std::string& hostName, const std::string& path) +{ + SocketSession* session = Session(sessionId); + InspectorSocket* socket = session->GetWsSocket(); + if (!inspectPublishUid.http) { + SendHttpNotFound(socket); + return true; + } + const char* command = MatchPathSegment(path.c_str(), "/json"); + if (command == nullptr) { + return false; + } + + if (MatchPathSegment(command, "list") || command[0] == '\0') { + SendListResponse(socket, hostName, session); + return true; + } else if (MatchPathSegment(command, "protocol")) { + SendProtocolJson(socket); + return true; + } else if (MatchPathSegment(command, "version")) { + SendVersionResponse(socket); + return true; + } + return false; +} + +void InspectorSocketServer::SendListResponse(InspectorSocket* socket, + const std::string& hostName, + SocketSession* session) +{ + std::vector> response; + for (const std::string& id : delegate->GetTargetIds()) { + response.push_back(std::map()); + std::map& targetMap = response.back(); + targetMap["description"] = "jsvm instance"; + targetMap["id"] = id; + targetMap["title"] = delegate->GetTargetTitle(id); + Escape(&targetMap["title"]); + targetMap["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. + targetMap["url"] = delegate->GetTargetUrl(id); + Escape(&targetMap["url"]); + + std::string detectedHost = hostName; + if (detectedHost.empty()) { + detectedHost = FormatHostPort(socket->GetHost(), session->ServerPort()); + } + std::string formattedAddress = FormatAddress(detectedHost, id, false); + targetMap["devtoolsFrontendUrl"] = GetFrontendURL(false, formattedAddress); + // The compat URL is for Chrome browsers older than 66.0.3345.0 + targetMap["devtoolsFrontendUrlCompat"] = GetFrontendURL(true, formattedAddress); + targetMap["webSocketDebuggerUrl"] = FormatAddress(detectedHost, id, true); + } + SendHttpResponse(socket, MapsToString(response), 200); +} + +std::string InspectorSocketServer::GetFrontendURL(bool isCompat, const std::string& formattedAddress) +{ + std::ostringstream frontendUrl; + frontendUrl << "devtools://devtools/bundled/"; + frontendUrl << (isCompat ? "inspector" : "js_app"); + frontendUrl << ".html?v8only=true&ws="; + frontendUrl << formattedAddress; + return frontendUrl.str(); +} + +bool InspectorSocketServer::Start() +{ + CHECK_NOT_NULL(delegate); + CHECK_EQ(state, ServerState::kNew); + std::unique_ptr delegateHolder; + // We will return it if startup is successful + delegate.swap(delegateHolder); + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICSERV; + hints.ai_socktype = SOCK_STREAM; + uv_getaddrinfo_t req; + const std::string port_string = std::to_string(port); + int err = uv_getaddrinfo(loop, &req, nullptr, host.c_str(), port_string.c_str(), &hints); + if (err < 0) { + if (out != nullptr) { + fprintf(out, "Unable to resolve \"%s\": %s\n", host.c_str(), uv_strerror(err)); + } + return false; + } + for (addrinfo* address = req.addrinfo; address != nullptr; address = address->ai_next) { + auto serverSocket = ServerSocketPtr(new ServerSocket(this)); + err = serverSocket->Listen(address->ai_addr, loop, pid); + if (err == 0) { + serverSockets.push_back(std::move(serverSocket)); + } + } + uv_freeaddrinfo(req.addrinfo); + + // We only show error if we failed to start server on all addresses. We only + // show one error, for the last address. + if (serverSockets.empty()) { + if (out != nullptr) { + fprintf(out, "Starting inspector on %s:%d failed: %s\n", host.c_str(), port, uv_strerror(err)); + fflush(out); + } + return false; + } + delegate.swap(delegateHolder); + state = ServerState::kRunning; + PrintDebuggerReadyMessage(host, serverSockets, delegate->GetTargetIds(), "listening", inspectPublishUid.console, + out); + return true; +} + +void InspectorSocketServer::Stop() +{ + if (state == ServerState::kStopped) { + return; + } + CHECK_EQ(state, ServerState::kRunning); + state = ServerState::kStopped; + serverSockets.clear(); + if (done()) { + delegate.reset(); + } +} + +void InspectorSocketServer::TerminateConnections() +{ + for (const auto& keyValue : connectedSessions) { + keyValue.second.second->Close(); + } +} + +bool InspectorSocketServer::TargetExists(const std::string& id) +{ + const std::vector& targetIds = delegate->GetTargetIds(); + const auto& found = std::find(targetIds.begin(), targetIds.end(), id); + return found != targetIds.end(); +} + +int InspectorSocketServer::GetPort() const +{ + if (!serverSockets.empty()) { + return serverSockets[0]->GetPort(); + } + return port; +} + +void InspectorSocketServer::Accept(int serverPort, uv_stream_t* serverSocket) +{ + std::unique_ptr session(new SocketSession(this, nextSessionId++, serverPort)); + + InspectorSocket::DelegatePointer delegatePointer = + InspectorSocket::DelegatePointer(new SocketSession::Delegate(this, session->GetId())); + + InspectorSocket::Pointer inspector = InspectorSocket::Accept(serverSocket, std::move(delegatePointer)); + if (inspector) { + session->Own(std::move(inspector)); + connectedSessions[session->GetId()].second = std::move(session); + } +} + +void InspectorSocketServer::Send(int sessionId, const std::string& message) +{ + SocketSession* session = Session(sessionId); + if (session != nullptr) { + session->Send(message); + } +} + +void InspectorSocketServer::CloseServerSocket(ServerSocket* server) +{ + server->Close(); + server->CloseUnix(); +} + +// InspectorSession tracking +SocketSession::SocketSession(InspectorSocketServer* server, int id, int serverPort) : id(id), serverPort(serverPort) {} + +void SocketSession::Send(const std::string& message) +{ + wsSocket->Write(message.data(), message.length()); +} + +void SocketSession::Delegate::OnHttpGet(const std::string& host, const std::string& path) +{ + if (!server->HandleGetRequest(usessionId, host, path)) { + Session()->GetWsSocket()->CancelHandshake(); + } +} + +void SocketSession::Delegate::OnSocketUpgrade(const std::string& host, + const std::string& path, + const std::string& wsKey) +{ + std::string id = path.empty() ? path : path.substr(1); + server->SessionStarted(usessionId, id, wsKey); +} + +void SocketSession::Delegate::OnWsFrame(const std::vector& data) +{ + server->MessageReceived(usessionId, std::string(data.data(), data.size())); +} + +// ServerSocket implementation +int ServerSocket::DetectPort(uv_loop_t* loop, int pid) +{ + sockaddr_storage addr; + int len = sizeof(addr); + int err = uv_tcp_getsockname(&tcpSocket, reinterpret_cast(&addr), &len); + if (err != 0) { + return err; + } + int portNum; + if (addr.ss_family == AF_INET6) { + portNum = reinterpret_cast(&addr)->sin6_port; + } else { + portNum = reinterpret_cast(&addr)->sin_port; + } + port = ntohs(portNum); + if (!unixSocketOn && pid != -1) { + auto unixDomainSocketPath = "jsvm_devtools_remote_" + std::to_string(port) + "_" + std::to_string(pid); + auto* abstract = new char[unixDomainSocketPath.length() + 2]; + abstract[0] = '\0'; + strcpy(abstract + 1, unixDomainSocketPath.c_str()); + auto status = uv_pipe_init(loop, &unixSocket, 0); + if (status == 0) { + status = uv_pipe_bind2(&unixSocket, abstract, unixDomainSocketPath.length() + 1, 0); + } + if (status == 0) { + constexpr int unixBacklog = 128; + status = uv_listen(reinterpret_cast(&unixSocket), unixBacklog, + ServerSocket::UnixSocketConnectedCallback); + } + unixSocketOn = status == 0; + delete[] abstract; + } + return err; +} + +int ServerSocket::Listen(sockaddr* addr, uv_loop_t* loop, int pid) +{ + uv_tcp_t* server = &tcpSocket; + CHECK_EQ(0, uv_tcp_init(loop, server)); + int err = uv_tcp_bind(server, addr, 0); + if (err == 0) { + // 511 is the value used by a 'net' module by default + err = uv_listen(reinterpret_cast(server), 511, ServerSocket::SocketConnectedCallback); + } + if (err == 0) { + err = DetectPort(loop, pid); + } + return err; +} + +// static +void ServerSocket::SocketConnectedCallback(uv_stream_t* tcpSocket, int status) +{ + if (status == 0) { + ServerSocket* serverSocket = ServerSocket::FromTcpSocket(tcpSocket); + // Memory is freed when the socket closes. + serverSocket->server->Accept(serverSocket->port, tcpSocket); + } +} + +void ServerSocket::UnixSocketConnectedCallback(uv_stream_t* unixSocket, int status) +{ + if (status == 0) { + (void)unixSocket; + } +} +} // namespace inspector +} // namespace jsvm diff --git a/src/inspector/inspector_socket_server.h b/src/inspector/inspector_socket_server.h new file mode 100644 index 0000000..89f1c40 --- /dev/null +++ b/src/inspector/inspector_socket_server.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SRC_INSPECTOR_SOCKET_SERVER_H_ +#define SRC_INSPECTOR_SOCKET_SERVER_H_ + +#include +#include +#include + +#include "inspector_socket.h" +#include "jsvm_host_port.h" +#include "uv.h" + +#ifndef ENABLE_INSPECTOR +#error("This header can only be used when inspector is enabled") +#endif + +namespace jsvm { +namespace inspector { + +std::string FormatWsAddress(const std::string& host, int port, const std::string& targetId, bool includeProtocol); + +class InspectorSocketServer; +class SocketSession; +class ServerSocket; + +class SocketServerDelegate { +public: + virtual void AssignServer(InspectorSocketServer* server) = 0; + virtual void StartSession(int sessionId, const std::string& targetId) = 0; + virtual void EndSession(int sessionId) = 0; + virtual void MessageReceived(int sessionId, const std::string& message) = 0; + virtual std::vector GetTargetIds() = 0; + virtual std::string GetTargetTitle(const std::string& id) = 0; + virtual std::string GetTargetUrl(const std::string& id) = 0; + virtual ~SocketServerDelegate() = default; +}; + +// HTTP Server, writes messages requested as TransportActions, and responds +// to HTTP requests and WS upgrades. + +class InspectorSocketServer { +public: + InspectorSocketServer(std::unique_ptr delegateParam, + uv_loop_t* loop, + const std::string& host, + int port, + const InspectPublishUid& inspectPublishUid, + FILE* out = stderr, + int pid = -1); + ~InspectorSocketServer(); + + // Start listening on host/port + bool Start(); + + // Called by the TransportAction sent with InspectorIo::Write(): + // kKill and kStop + void Stop(); + // kSendMessage + void Send(int sessionId, const std::string& message); + // kKill + void TerminateConnections(); + int GetPort() const; + + // Session connection lifecycle + void Accept(int serverPort, uv_stream_t* serverSocket); + bool HandleGetRequest(int sessionId, const std::string& hostName, const std::string& path); + void SessionStarted(int sessionId, const std::string& targetId, const std::string& wsKey); + void SessionTerminated(int sessionId); + void MessageReceived(int sessionId, const std::string& message) + { + delegate->MessageReceived(sessionId, message); + } + SocketSession* Session(int sessionId); + bool done() const + { + return serverSockets.empty() && connectedSessions.empty(); + } + + static void CloseServerSocket(ServerSocket* server); + using ServerSocketPtr = DeleteFnPtr; + +private: + void SendListResponse(InspectorSocket* socket, const std::string& hostName, SocketSession* session); + std::string GetFrontendURL(bool isCompat, const std::string& formattedAddress); + bool TargetExists(const std::string& id); + + enum class ServerState { kNew, kRunning, kStopped }; + uv_loop_t* loop; + std::unique_ptr delegate; + const std::string host; + int port; + InspectPublishUid inspectPublishUid; + std::vector serverSockets; + std::map>> connectedSessions; + int nextSessionId; + FILE* out; + ServerState state; + int pid; +}; + +} // namespace inspector +} // namespace jsvm + +#endif // SRC_INSPECTOR_SOCKET_SERVER_H_ -- Gitee From 38c2b0eb36d1197b8f038d8b1cc612f6d11a82eb Mon Sep 17 00:00:00 2001 From: liuweili Date: Sat, 11 Jan 2025 15:39:29 +0800 Subject: [PATCH 2/3] fix code check Signed-off-by: liuweili --- src/inspector/inspector_socket_server.cpp | 131 ++++++++++++---------- src/inspector/inspector_socket_server.h | 29 +++-- 2 files changed, 96 insertions(+), 64 deletions(-) diff --git a/src/inspector/inspector_socket_server.cpp b/src/inspector/inspector_socket_server.cpp index 86405df..a32a6a5 100644 --- a/src/inspector/inspector_socket_server.cpp +++ b/src/inspector/inspector_socket_server.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "jsvm_version.h" #include "uv.h" @@ -36,13 +37,6 @@ namespace inspector { // depend on inspector_socket_server.h std::string FormatWsAddress(const std::string& host, int port, const std::string& targetId, bool includeProtocol); namespace { -void Escape(std::string* string) -{ - for (char& c : *string) { - c = (c == '\"' || c == '\\') ? '_' : c; - } -} - std::string FormatHostPort(const std::string& host, int port) { // Host is valid (socket was bound) so colon means it's a v6 IP address @@ -59,6 +53,13 @@ std::string FormatHostPort(const std::string& host, int port) return url.str(); } +void Escape(std::string* string) +{ + for (char& c : *string) { + c = (c == '\"' || c == '\\') ? '_' : c; + } +} + std::string FormatAddress(const std::string& host, const std::string& targetId, bool includeProtocol) { std::ostringstream url; @@ -88,20 +89,34 @@ std::string MapToString(const std::map& object) std::string MapsToString(const std::vector>& array) { - bool first = true; + bool isFirst = true; std::ostringstream json; json << "[ "; for (const auto& object : array) { - if (!first) { + if (!isFirst) { json << ", "; } - first = false; + isFirst = false; json << MapToString(object); } json << "]\n\n"; return json.str(); } +void SendHttpResponse(InspectorSocket* socket, 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" + "Cache-Control: no-cache\r\n" + "Content-Length: %zu\r\n" + "\r\n"; + char header[sizeof(headers) + 20]; + int headerLen = snprintf_s(header, sizeof(header), sizeof(header) - 1, headers, code, response.size()); + CHECK(headerLen >= 0); + socket->Write(header, headerLen); + socket->Write(response.data(), response.size()); +} + const char* MatchPathSegment(const char* path, const char* expected) { size_t len = strlen(expected); @@ -116,49 +131,36 @@ const char* MatchPathSegment(const char* path, const char* expected) return nullptr; } -void SendHttpResponse(InspectorSocket* socket, const std::string& response, int code) +void SendHttpNotFound(InspectorSocket* socket) { - const char headers[] = "HTTP/1.0 %d 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 headerLen = snprintf(header, sizeof(header), headers, code, response.size()); - socket->Write(header, headerLen); - socket->Write(response.data(), response.size()); + SendHttpResponse(socket, "", 404); } void SendVersionResponse(InspectorSocket* socket) { std::map response; - response["Browser"] = "jsvm/" JSVM_VERSION_STRING; response["Protocol-Version"] = "1.1"; + response["Browser"] = "jsvm/" JSVM_VERSION_STRING; SendHttpResponse(socket, MapToString(response), 200); } -void SendHttpNotFound(InspectorSocket* socket) -{ - SendHttpResponse(socket, "", 404); -} - 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 K_DECOMPRESSED_SIZE = + CHECK_EQ(inflateInit(&strm), Z_OK); + static const size_t decompressedSize = PROTOCOL_JSON[0] * 0x10000u + PROTOCOL_JSON[1] * 0x100u + PROTOCOL_JSON[2]; strm.next_in = const_cast(PROTOCOL_JSON + 3); strm.avail_in = sizeof(PROTOCOL_JSON) - 3; - std::string data(K_DECOMPRESSED_SIZE, '\0'); + std::string data(decompressedSize, '\0'); strm.next_out = reinterpret_cast(data.data()); 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)); + CHECK_EQ(inflate(&strm, Z_FINISH), Z_STREAM_END); + CHECK_EQ(strm.avail_out, 0); + CHECK_EQ(inflateEnd(&strm), Z_OK); SendHttpResponse(socket, data, 200); } } // namespace @@ -284,11 +286,18 @@ void PrintDebuggerReadyMessage(const std::string& host, } for (const auto& serverSocket : serverSockets) { for (const std::string& id : ids) { - fprintf(out, "Debugger %s on %s\n", verb, FormatWsAddress(host, serverSocket->GetPort(), id, true).c_str()); + if (fprintf(out, "Debugger %s on %s\n", verb, + FormatWsAddress(host, serverSocket->GetPort(), id, true).c_str()) < 0) { + return; + } } } - fprintf(out, "For help, see: %s\n", "https://nodejs.org/en/docs/inspector"); - fflush(out); + if (fprintf(out, "For help, see: %s\n", "https://nodejs.org/en/docs/inspector") < 0) { + return; + } + if (fflush(out) != 0) { + return; + } } InspectorSocketServer::InspectorSocketServer(std::unique_ptr delegateParam, @@ -302,7 +311,7 @@ InspectorSocketServer::InspectorSocketServer(std::unique_ptrAssignServer(this); - state = ServerState::kNew; + state = ServerState::NEW; } InspectorSocketServer::~InspectorSocketServer() = default; @@ -336,11 +345,11 @@ void InspectorSocketServer::SessionTerminated(int sessionId) } connectedSessions.erase(sessionId); if (connectedSessions.empty()) { - if (wasAttached && state == ServerState::kRunning && !serverSockets.empty()) { + if (wasAttached && state == ServerState::RUNNING && !serverSockets.empty()) { PrintDebuggerReadyMessage(host, serverSockets, delegate->GetTargetIds(), "ending", inspectPublishUid.console, out); } - if (state == ServerState::kStopped) { + if (state == ServerState::STOPPED) { delegate.reset(); } } @@ -355,21 +364,22 @@ bool InspectorSocketServer::HandleGetRequest(int sessionId, const std::string& h return true; } const char* command = MatchPathSegment(path.c_str(), "/json"); - if (command == nullptr) { + if (!command) { return false; } + bool hasHandled = false; if (MatchPathSegment(command, "list") || command[0] == '\0') { SendListResponse(socket, hostName, session); - return true; + hasHandled = true; } else if (MatchPathSegment(command, "protocol")) { SendProtocolJson(socket); - return true; + hasHandled = true; } else if (MatchPathSegment(command, "version")) { SendVersionResponse(socket); - return true; + hasHandled = true; } - return false; + return hasHandled; } void InspectorSocketServer::SendListResponse(InspectorSocket* socket, @@ -416,20 +426,22 @@ std::string InspectorSocketServer::GetFrontendURL(bool isCompat, const std::stri bool InspectorSocketServer::Start() { CHECK_NOT_NULL(delegate); - CHECK_EQ(state, ServerState::kNew); + CHECK_EQ(state, ServerState::NEW); std::unique_ptr delegateHolder; // We will return it if startup is successful delegate.swap(delegateHolder); struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); + memset_s(&hints, sizeof(hints), 0, sizeof(hints)); hints.ai_flags = AI_NUMERICSERV; hints.ai_socktype = SOCK_STREAM; uv_getaddrinfo_t req; - const std::string port_string = std::to_string(port); - int err = uv_getaddrinfo(loop, &req, nullptr, host.c_str(), port_string.c_str(), &hints); + const std::string portString = std::to_string(port); + int err = uv_getaddrinfo(loop, &req, nullptr, host.c_str(), portString.c_str(), &hints); if (err < 0) { if (out != nullptr) { - fprintf(out, "Unable to resolve \"%s\": %s\n", host.c_str(), uv_strerror(err)); + if (fprintf(out, "Unable to resolve \"%s\": %s\n", host.c_str(), uv_strerror(err)) < 0) { + return false; + } } return false; } @@ -446,13 +458,17 @@ bool InspectorSocketServer::Start() // show one error, for the last address. if (serverSockets.empty()) { if (out != nullptr) { - fprintf(out, "Starting inspector on %s:%d failed: %s\n", host.c_str(), port, uv_strerror(err)); - fflush(out); + if (fprintf(out, "Starting inspector on %s:%d failed: %s\n", host.c_str(), port, uv_strerror(err)) < 0) { + return false; + } + if (fflush(out) != 0) { + return false; + } } return false; } delegate.swap(delegateHolder); - state = ServerState::kRunning; + state = ServerState::RUNNING; PrintDebuggerReadyMessage(host, serverSockets, delegate->GetTargetIds(), "listening", inspectPublishUid.console, out); return true; @@ -460,13 +476,13 @@ bool InspectorSocketServer::Start() void InspectorSocketServer::Stop() { - if (state == ServerState::kStopped) { + if (state == ServerState::STOPPED) { return; } - CHECK_EQ(state, ServerState::kRunning); - state = ServerState::kStopped; + CHECK_EQ(state, ServerState::RUNNING); + state = ServerState::STOPPED; serverSockets.clear(); - if (done()) { + if (Done()) { delegate.reset(); } } @@ -495,7 +511,7 @@ int InspectorSocketServer::GetPort() const void InspectorSocketServer::Accept(int serverPort, uv_stream_t* serverSocket) { - std::unique_ptr session(new SocketSession(this, nextSessionId++, serverPort)); + std::unique_ptr session = std::make_unique(this, nextSessionId++, serverPort); InspectorSocket::DelegatePointer delegatePointer = InspectorSocket::DelegatePointer(new SocketSession::Delegate(this, session->GetId())); @@ -569,7 +585,8 @@ int ServerSocket::DetectPort(uv_loop_t* loop, int pid) auto unixDomainSocketPath = "jsvm_devtools_remote_" + std::to_string(port) + "_" + std::to_string(pid); auto* abstract = new char[unixDomainSocketPath.length() + 2]; abstract[0] = '\0'; - strcpy(abstract + 1, unixDomainSocketPath.c_str()); + int ret = strcpy_s(abstract + 1, unixDomainSocketPath.length() + 2, unixDomainSocketPath.c_str()); + CHECK(ret == 0); auto status = uv_pipe_init(loop, &unixSocket, 0); if (status == 0) { status = uv_pipe_bind2(&unixSocket, abstract, unixDomainSocketPath.length() + 1, 0); diff --git a/src/inspector/inspector_socket_server.h b/src/inspector/inspector_socket_server.h index 89f1c40..41c369e 100644 --- a/src/inspector/inspector_socket_server.h +++ b/src/inspector/inspector_socket_server.h @@ -39,18 +39,24 @@ class ServerSocket; class SocketServerDelegate { public: + virtual ~SocketServerDelegate() = default; + virtual void AssignServer(InspectorSocketServer* server) = 0; + virtual void StartSession(int sessionId, const std::string& targetId) = 0; + virtual void EndSession(int sessionId) = 0; + virtual void MessageReceived(int sessionId, const std::string& message) = 0; + virtual std::vector GetTargetIds() = 0; + virtual std::string GetTargetTitle(const std::string& id) = 0; + virtual std::string GetTargetUrl(const std::string& id) = 0; - virtual ~SocketServerDelegate() = default; }; -// HTTP Server, writes messages requested as TransportActions, and responds -// to HTTP requests and WS upgrades. +// HTTP Server, writes messages requested as TransportActions, and responds to HTTP requests and WS upgrades. class InspectorSocketServer { public: @@ -61,31 +67,40 @@ public: const InspectPublishUid& inspectPublishUid, FILE* out = stderr, int pid = -1); + ~InspectorSocketServer(); // Start listening on host/port bool Start(); - // Called by the TransportAction sent with InspectorIo::Write(): - // kKill and kStop + // Called by the TransportAction sent with InspectorIo::Write(): kKill and kStop void Stop(); + // kSendMessage void Send(int sessionId, const std::string& message); + // kKill void TerminateConnections(); + int GetPort() const; // Session connection lifecycle void Accept(int serverPort, uv_stream_t* serverSocket); + bool HandleGetRequest(int sessionId, const std::string& hostName, const std::string& path); + void SessionStarted(int sessionId, const std::string& targetId, const std::string& wsKey); + void SessionTerminated(int sessionId); + void MessageReceived(int sessionId, const std::string& message) { delegate->MessageReceived(sessionId, message); } + SocketSession* Session(int sessionId); - bool done() const + + bool Done() const { return serverSockets.empty() && connectedSessions.empty(); } @@ -98,7 +113,7 @@ private: std::string GetFrontendURL(bool isCompat, const std::string& formattedAddress); bool TargetExists(const std::string& id); - enum class ServerState { kNew, kRunning, kStopped }; + enum class ServerState { NEW, RUNNING, STOPPED }; uv_loop_t* loop; std::unique_ptr delegate; const std::string host; -- Gitee From 3e2fe03ecd51a4e43a45fd6c50a3d62420c53854 Mon Sep 17 00:00:00 2001 From: wangyimin Date: Sat, 11 Jan 2025 18:08:31 +0800 Subject: [PATCH 3/3] fix codecheck Signed-off-by: wangyimin --- src/inspector/inspector_socket_server.cpp | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/inspector/inspector_socket_server.cpp b/src/inspector/inspector_socket_server.cpp index a32a6a5..5f0133a 100644 --- a/src/inspector/inspector_socket_server.cpp +++ b/src/inspector/inspector_socket_server.cpp @@ -19,11 +19,11 @@ #include #include #include +#include #include #include #include #include -#include #include "jsvm_version.h" #include "uv.h" @@ -131,9 +131,14 @@ const char* MatchPathSegment(const char* path, const char* expected) return nullptr; } +enum HttpStatusCode : int { + HTTP_OK = 200, + HTTP_NOT_FOUND = 404, +}; + void SendHttpNotFound(InspectorSocket* socket) { - SendHttpResponse(socket, "", 404); + SendHttpResponse(socket, "", HttpStatusCode::HTTP_NOT_FOUND); } void SendVersionResponse(InspectorSocket* socket) @@ -141,7 +146,7 @@ void SendVersionResponse(InspectorSocket* socket) std::map response; response["Protocol-Version"] = "1.1"; response["Browser"] = "jsvm/" JSVM_VERSION_STRING; - SendHttpResponse(socket, MapToString(response), 200); + SendHttpResponse(socket, MapToString(response), HttpStatusCode::HTTP_OK); } void SendProtocolJson(InspectorSocket* socket) @@ -151,17 +156,16 @@ void SendProtocolJson(InspectorSocket* socket) strm.zfree = Z_NULL; strm.opaque = Z_NULL; CHECK_EQ(inflateInit(&strm), Z_OK); - static const size_t decompressedSize = - PROTOCOL_JSON[0] * 0x10000u + PROTOCOL_JSON[1] * 0x100u + PROTOCOL_JSON[2]; - strm.next_in = const_cast(PROTOCOL_JSON + 3); - strm.avail_in = sizeof(PROTOCOL_JSON) - 3; + static const size_t decompressedSize = PROTOCOL_JSON[0] * 0x10000u + PROTOCOL_JSON[1] * 0x100u + PROTOCOL_JSON[2]; + strm.next_in = const_cast(PROTOCOL_JSON + ByteOffset::BYTE_3); + strm.avail_in = sizeof(PROTOCOL_JSON) - ByteOffset::BYTE_3; std::string data(decompressedSize, '\0'); strm.next_out = reinterpret_cast(data.data()); strm.avail_out = data.size(); CHECK_EQ(inflate(&strm, Z_FINISH), Z_STREAM_END); CHECK_EQ(strm.avail_out, 0); CHECK_EQ(inflateEnd(&strm), Z_OK); - SendHttpResponse(socket, data, 200); + SendHttpResponse(socket, data, HttpStatusCode::HTTP_OK); } } // namespace @@ -287,7 +291,7 @@ void PrintDebuggerReadyMessage(const std::string& host, for (const auto& serverSocket : serverSockets) { for (const std::string& id : ids) { if (fprintf(out, "Debugger %s on %s\n", verb, - FormatWsAddress(host, serverSocket->GetPort(), id, true).c_str()) < 0) { + FormatWsAddress(host, serverSocket->GetPort(), id, true).c_str()) < 0) { return; } } @@ -410,7 +414,7 @@ void InspectorSocketServer::SendListResponse(InspectorSocket* socket, targetMap["devtoolsFrontendUrlCompat"] = GetFrontendURL(true, formattedAddress); targetMap["webSocketDebuggerUrl"] = FormatAddress(detectedHost, id, true); } - SendHttpResponse(socket, MapsToString(response), 200); + SendHttpResponse(socket, MapsToString(response), HttpStatusCode::HTTP_OK); } std::string InspectorSocketServer::GetFrontendURL(bool isCompat, const std::string& formattedAddress) -- Gitee