Coverage Report

Created: 2026-03-12 17:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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
7
        : HttpHandlerWithAuth(exec_env), _http_server(server) {
49
7
    _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
7
    _http_server->register_static_file_handler(this);
53
54
7
    TemplatePageHandlerCallback root_callback =
55
7
            std::bind<void>(std::mem_fn(&WebPageHandler::root_handler), this, std::placeholders::_1,
56
7
                            std::placeholders::_2);
57
7
    register_template_page("/", "Home", root_callback, false /* is_on_nav_bar */);
58
7
}
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
21
                                            bool is_on_nav_bar) {
69
    // Relative path which will be used to find .mustache file in _www_path
70
21
    std::string render_path = (path == "/") ? "/home" : path;
71
21
    auto wrapped_cb = [callback, render_path, this](const ArgumentMap& args,
72
21
                                                    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
21
    register_page(path, alias, wrapped_cb, is_on_nav_bar);
78
21
}
79
80
void WebPageHandler::register_page(const std::string& path, const std::string& alias,
81
63
                                   const PageHandlerCallback& callback, bool is_on_nav_bar) {
82
63
    std::unique_lock lock(_map_lock);
83
63
    CHECK(_page_map.find(path) == _page_map.end());
84
    // first time, register this to web server
85
63
    _http_server->register_handler(HttpMethod::GET, path, this);
86
63
    _page_map[path] = new PathHandler(true /* is_styled */, is_on_nav_bar, alias, callback);
87
63
}
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