be/src/service/http/web_page_handler.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "service/http/web_page_handler.h" |
19 | | |
20 | | #include <stdlib.h> |
21 | | |
22 | | #include <functional> |
23 | | #include <memory> |
24 | | |
25 | | #include "absl/strings/substitute.h" |
26 | | #include "common/logging.h" |
27 | | #include "common/status.h" |
28 | | #include "io/fs/local_file_system.h" |
29 | | #include "service/http/ev_http_server.h" |
30 | | #include "service/http/http_channel.h" |
31 | | #include "service/http/http_headers.h" |
32 | | #include "service/http/http_method.h" |
33 | | #include "service/http/http_request.h" |
34 | | #include "service/http/http_status.h" |
35 | | #include "service/http/utils.h" |
36 | | #include "util/cpu_info.h" |
37 | | #include "util/debug_util.h" |
38 | | #include "util/disk_info.h" |
39 | | #include "util/easy_json.h" |
40 | | #include "util/mem_info.h" |
41 | | #include "util/mustache/mustache.h" |
42 | | |
43 | | namespace doris { |
44 | | |
45 | | static std::string s_html_content_type = "text/html"; |
46 | | |
47 | | WebPageHandler::WebPageHandler(EvHttpServer* server, ExecEnv* exec_env) |
48 | 8 | : HttpHandlerWithAuth(exec_env), _http_server(server) { |
49 | 8 | _www_path = std::string(getenv("DORIS_HOME")) + "/www/"; |
50 | | |
51 | | // Make WebPageHandler to be static file handler, static files, e.g. css, png, will be handled by WebPageHandler. |
52 | 8 | _http_server->register_static_file_handler(this); |
53 | | |
54 | 8 | TemplatePageHandlerCallback root_callback = |
55 | 8 | std::bind<void>(std::mem_fn(&WebPageHandler::root_handler), this, std::placeholders::_1, |
56 | 8 | std::placeholders::_2); |
57 | 8 | register_template_page("/", "Home", root_callback, false /* is_on_nav_bar */); |
58 | 8 | } |
59 | | |
60 | 4 | WebPageHandler::~WebPageHandler() { |
61 | 36 | for (auto& handler : _page_map) { |
62 | 36 | delete handler.second; |
63 | 36 | } |
64 | 4 | } |
65 | | |
66 | | void WebPageHandler::register_template_page(const std::string& path, const std::string& alias, |
67 | | const TemplatePageHandlerCallback& callback, |
68 | 24 | bool is_on_nav_bar) { |
69 | | // Relative path which will be used to find .mustache file in _www_path |
70 | 24 | std::string render_path = (path == "/") ? "/home" : path; |
71 | 24 | auto wrapped_cb = [callback, render_path, this](const ArgumentMap& args, |
72 | 24 | std::stringstream* output) { |
73 | 0 | EasyJson ej; |
74 | 0 | callback(args, &ej); |
75 | 0 | render(render_path, ej, true /* is_styled */, output); |
76 | 0 | }; |
77 | 24 | register_page(path, alias, wrapped_cb, is_on_nav_bar); |
78 | 24 | } |
79 | | |
80 | | void WebPageHandler::register_page(const std::string& path, const std::string& alias, |
81 | 72 | const PageHandlerCallback& callback, bool is_on_nav_bar) { |
82 | 72 | std::unique_lock lock(_map_lock); |
83 | 72 | CHECK(_page_map.find(path) == _page_map.end()); |
84 | | // first time, register this to web server |
85 | 72 | _http_server->register_handler(HttpMethod::GET, path, this); |
86 | 72 | _page_map[path] = new PathHandler(true /* is_styled */, is_on_nav_bar, alias, callback); |
87 | 72 | } |
88 | | |
89 | 0 | void WebPageHandler::handle(HttpRequest* req) { |
90 | 0 | VLOG_TRACE << req->debug_string(); |
91 | 0 | PathHandler* handler = nullptr; |
92 | 0 | { |
93 | 0 | std::unique_lock lock(_map_lock); |
94 | 0 | auto iter = _page_map.find(req->raw_path()); |
95 | 0 | if (iter != _page_map.end()) { |
96 | 0 | handler = iter->second; |
97 | 0 | } |
98 | 0 | } |
99 | |
|
100 | 0 | if (handler == nullptr) { |
101 | | // Try to handle static file request |
102 | 0 | do_file_response(_www_path + req->raw_path(), req); |
103 | | // Has replied in do_file_response, so we return here. |
104 | 0 | return; |
105 | 0 | } |
106 | | |
107 | 0 | const auto& params = *req->params(); |
108 | | |
109 | | // Should we render with css styles? |
110 | 0 | bool use_style = (params.find("raw") == params.end()); |
111 | |
|
112 | 0 | std::stringstream content; |
113 | 0 | handler->callback()(params, &content); |
114 | |
|
115 | 0 | std::string output; |
116 | 0 | if (use_style) { |
117 | 0 | std::stringstream oss; |
118 | 0 | render_main_template(content.str(), &oss); |
119 | 0 | output = oss.str(); |
120 | 0 | } else { |
121 | 0 | output = content.str(); |
122 | 0 | } |
123 | |
|
124 | 0 | req->add_output_header(HttpHeaders::CONTENT_TYPE, s_html_content_type.c_str()); |
125 | 0 | HttpChannel::send_reply(req, HttpStatus::OK, output); |
126 | 0 | } |
127 | | |
128 | | static const std::string kMainTemplate = R"( |
129 | | <!DOCTYPE html> |
130 | | <html> |
131 | | <head> |
132 | | <title>Doris</title> |
133 | | <meta charset='utf-8'/> |
134 | | <link href='/Bootstrap-3.3.7/css/bootstrap.min.css' rel='stylesheet' media='screen' /> |
135 | | <link href='/Bootstrap-3.3.7/css/bootstrap-table.min.css' rel='stylesheet' media='screen' /> |
136 | | <script src='/jQuery-3.6.0/jquery-3.6.0.min.js'></script> |
137 | | <script src='/Bootstrap-3.3.7/js/bootstrap.min.js' defer></script> |
138 | | <script src='/Bootstrap-3.3.7/js/bootstrap-table.min.js' defer></script> |
139 | | <script src='/doris.js' defer></script> |
140 | | <link href='/doris.css' rel='stylesheet' /> |
141 | | </head> |
142 | | <body> |
143 | | <nav class="navbar navbar-default"> |
144 | | <div class="container-fluid"> |
145 | | <div class="navbar-header"> |
146 | | <a class="navbar-brand" style="padding-top: 5px;" href="/"> |
147 | | <img src="/logo.png" width='40' height='40' alt="Doris" /> |
148 | | </a> |
149 | | </div> |
150 | | <div id="navbar" class="navbar-collapse collapse"> |
151 | | <ul class="nav navbar-nav"> |
152 | | {{#path_handlers}} |
153 | | <li><a class="nav-link"href="{{path}}">{{alias}}</a></li> |
154 | | {{/path_handlers}} |
155 | | </ul> |
156 | | </div><!--/.nav-collapse --> |
157 | | </div><!--/.container-fluid --> |
158 | | </nav> |
159 | | {{^static_pages_available}} |
160 | | <div style="color: red"> |
161 | | <strong>Static pages not available. Make sure ${DORIS_HOME}/www/ exists and contains web static files.</strong> |
162 | | </div> |
163 | | {{/static_pages_available}} |
164 | | {{{content}}} |
165 | | </div> |
166 | | {{#footer_html}} |
167 | | <footer class="footer"><div class="container text-muted"> |
168 | | {{{.}}} |
169 | | </div></footer> |
170 | | {{/footer_html}} |
171 | | </body> |
172 | | </html> |
173 | | )"; |
174 | | |
175 | 0 | std::string WebPageHandler::mustache_partial_tag(const std::string& path) const { |
176 | 0 | return absl::Substitute("{{> $0.mustache}}", path); |
177 | 0 | } |
178 | | |
179 | 0 | bool WebPageHandler::static_pages_available() const { |
180 | 0 | bool is_dir = false; |
181 | 0 | return io::global_local_filesystem()->is_directory(_www_path, &is_dir).ok() && is_dir; |
182 | 0 | } |
183 | | |
184 | 0 | bool WebPageHandler::mustache_template_available(const std::string& path) const { |
185 | 0 | if (!static_pages_available()) { |
186 | 0 | return false; |
187 | 0 | } |
188 | 0 | bool exists; |
189 | 0 | return io::global_local_filesystem() |
190 | 0 | ->exists(absl::Substitute("$0/$1.mustache", _www_path, path), &exists) |
191 | 0 | .ok() && |
192 | 0 | exists; |
193 | 0 | } |
194 | | |
195 | 0 | void WebPageHandler::render_main_template(const std::string& content, std::stringstream* output) { |
196 | 0 | static const std::string& footer = |
197 | 0 | std::string("<pre>") + get_version_string(true) + std::string("</pre>"); |
198 | |
|
199 | 0 | EasyJson ej; |
200 | 0 | ej["static_pages_available"] = static_pages_available(); |
201 | 0 | ej["content"] = content; |
202 | 0 | ej["footer_html"] = footer; |
203 | 0 | EasyJson path_handlers = ej.Set("path_handlers", EasyJson::kArray); |
204 | 0 | for (const auto& handler : _page_map) { |
205 | 0 | if (handler.second->is_on_nav_bar()) { |
206 | 0 | EasyJson path_handler = path_handlers.PushBack(EasyJson::kObject); |
207 | 0 | path_handler["path"] = handler.first; |
208 | 0 | path_handler["alias"] = handler.second->alias(); |
209 | 0 | } |
210 | 0 | } |
211 | 0 | mustache::RenderTemplate(kMainTemplate, _www_path, ej.value(), output); |
212 | 0 | } |
213 | | |
214 | | void WebPageHandler::render(const std::string& path, const EasyJson& ej, bool use_style, |
215 | 0 | std::stringstream* output) { |
216 | 0 | if (mustache_template_available(path)) { |
217 | 0 | mustache::RenderTemplate(mustache_partial_tag(path), _www_path, ej.value(), output); |
218 | 0 | } else if (use_style) { |
219 | 0 | (*output) << "<pre>" << ej.ToString() << "</pre>"; |
220 | 0 | } else { |
221 | 0 | (*output) << ej.ToString(); |
222 | 0 | } |
223 | 0 | } |
224 | | |
225 | 0 | void WebPageHandler::root_handler(const ArgumentMap& args, EasyJson* output) { |
226 | 0 | (*output)["version"] = get_version_string(false); |
227 | 0 | (*output)["cpuinfo"] = CpuInfo::debug_string(); |
228 | 0 | (*output)["meminfo"] = MemInfo::debug_string(); |
229 | 0 | (*output)["diskinfo"] = DiskInfo::debug_string(); |
230 | 0 | } |
231 | | |
232 | | } // namespace doris |