1 /**
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "connection/asio/asio_server.h"
17
18 #include <memory>
19 #include <sstream>
20 #include <system_error>
21
22 #include "websocketpp/close.hpp"
23 #include "websocketpp/http/constants.hpp"
24 #include "websocketpp/uri.hpp"
25
26 #include "utils/json_builder.h"
27 #include "utils/logger.h"
28
29 #include "connection/asio/asio_config.h"
30
31 #define CONFIG AsioConfig // NOLINT(cppcoreguidelines-macro-usage)
32 #include "server_endpoint-inl.h"
33 #undef CONFIG
34
35 namespace ark::tooling::inspector {
36 template <typename ConnectionPtr>
HandleHttpRequest(ConnectionPtr conn)37 static void HandleHttpRequest(ConnectionPtr conn)
38 {
39 static constexpr std::string_view GET = "GET";
40 static constexpr std::string_view JSON_URI = "/json";
41 static constexpr std::string_view JSON_LIST_URI = "/json/list";
42 static constexpr std::string_view JSON_VERSION_URI = "/json/version";
43
44 const auto &req = conn->get_request();
45 const auto &uri = req.get_uri();
46 if (req.get_method() != GET) {
47 CloseConnection(conn);
48 return;
49 }
50
51 if (uri == JSON_URI || uri == JSON_LIST_URI) {
52 HandleJsonList(conn);
53 } else if (uri == JSON_VERSION_URI) {
54 HandleJsonVersion(conn);
55 } else {
56 CloseConnection(conn);
57 return;
58 }
59 conn->append_header("Content-Type", "application/json; charset=UTF-8");
60 conn->append_header("Cache-Control", "no-cache");
61 conn->set_status(websocketpp::http::status_code::value::ok);
62 }
63
64 template <typename ConnectionPtr>
HandleJsonVersion(ConnectionPtr conn)65 static void HandleJsonVersion(ConnectionPtr conn)
66 {
67 JsonObjectBuilder builder;
68 builder.AddProperty("browser", "ArkTS");
69 builder.AddProperty("protocol-version", "1.1");
70 AddWebSocketDebuggerUrl(conn, builder);
71 conn->set_body(std::move(builder).Build());
72 }
73
74 template <typename ConnectionPtr>
HandleJsonList(ConnectionPtr conn)75 static void HandleJsonList(ConnectionPtr conn)
76 {
77 auto buildJson = [conn](JsonObjectBuilder &builder) {
78 builder.AddProperty("description", "ArkTS");
79 // Empty "id" corresponds to constructed URLs
80 builder.AddProperty("id", "");
81 builder.AddProperty("type", "node");
82 // Do not add "url" fields as it dummy value might confuse some clients
83 AddWebSocketDebuggerUrl(conn, builder);
84 std::stringstream ss;
85 // CC-OFFNXT(WordsTool.74) fixed protocol url
86 ss << "devtools://devtools/bundled/devtools_app.html?ws=" << conn->get_host() << ':'
87 << static_cast<int>(conn->get_port());
88 builder.AddProperty("devToolsFrontendUrl", ss.str());
89 };
90 JsonArrayBuilder arrayBuilder;
91 arrayBuilder.Add(std::move(buildJson));
92 conn->set_body(std::move(arrayBuilder).Build());
93 }
94
95 template <typename ConnectionPtr>
CloseConnection(ConnectionPtr conn)96 static void CloseConnection(ConnectionPtr conn)
97 {
98 std::error_code ec;
99 conn->close(websocketpp::close::status::protocol_error, "", ec);
100 if (ec) {
101 LOG(WARNING, DEBUGGER) << "Failed to close invalid HTTP connection";
102 }
103 }
104
105 template <typename ConnectionPtr>
AddWebSocketDebuggerUrl(ConnectionPtr conn,JsonObjectBuilder & builder)106 static void AddWebSocketDebuggerUrl(ConnectionPtr conn, JsonObjectBuilder &builder)
107 {
108 std::stringstream ss;
109 ss << "ws://" << conn->get_host() << ':' << conn->get_port();
110 builder.AddProperty("webSocketDebuggerUrl", ss.str());
111 }
112
Initialize()113 bool AsioServer::Initialize()
114 {
115 if (initialized_) {
116 return true;
117 }
118
119 // Do JSON handshake, as it is expected by some debugger clients
120 endpoint_.set_http_handler([this](auto hdl) { HandleHttpRequest(endpoint_.get_con_from_hdl(hdl)); });
121
122 std::error_code ec;
123 endpoint_.init_asio(ec);
124 if (ec) {
125 LOG(ERROR, DEBUGGER) << "Failed to initialize endpoint";
126 return false;
127 }
128
129 endpoint_.set_reuse_addr(true);
130 initialized_ = true;
131 return true;
132 }
133
Start(uint32_t port)134 websocketpp::uri_ptr AsioServer::Start(uint32_t port)
135 {
136 if (!Initialize()) {
137 return nullptr;
138 }
139
140 std::error_code ec;
141
142 endpoint_.listen(port, ec);
143 if (ec) {
144 LOG(ERROR, DEBUGGER) << "Failed to bind Inspector server on port " << port;
145 return nullptr;
146 }
147
148 endpoint_.start_accept(ec);
149
150 if (!ec) {
151 auto ep = endpoint_.get_local_endpoint(ec);
152
153 if (!ec) {
154 LOG(INFO, DEBUGGER) << "Inspector server listening on " << ep;
155 return std::make_shared<websocketpp::uri>(false, ep.address().to_string(), ep.port(), "/");
156 }
157
158 LOG(ERROR, DEBUGGER) << "Failed to get the TCP endpoint";
159 } else {
160 LOG(ERROR, DEBUGGER) << "Failed to start Inspector connection acceptance loop";
161 }
162
163 endpoint_.stop_listening(ec);
164 return nullptr;
165 }
166
Stop()167 bool AsioServer::Stop()
168 {
169 if (!Initialize()) {
170 return false;
171 }
172
173 // Stop accepting new connections.
174 Kill();
175 // Close the current connection.
176 Close();
177
178 std::error_code ec;
179 endpoint_.stop_listening(ec);
180 return !ec;
181 }
182 } // namespace ark::tooling::inspector
183