blob: ab09b6d975ca497d9e7821cc3262c1325c886249 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "http/web_page_handler.h"
#include <stdlib.h>
#include <functional>
#include <memory>
#include "absl/strings/substitute.h"
#include "common/logging.h"
#include "common/status.h"
#include "http/ev_http_server.h"
#include "http/http_channel.h"
#include "http/http_headers.h"
#include "http/http_method.h"
#include "http/http_request.h"
#include "http/http_status.h"
#include "http/utils.h"
#include "io/fs/local_file_system.h"
#include "util/cpu_info.h"
#include "util/debug_util.h"
#include "util/disk_info.h"
#include "util/easy_json.h"
#include "util/mem_info.h"
#include "util/mustache/mustache.h"
namespace doris {
static std::string s_html_content_type = "text/html";
WebPageHandler::WebPageHandler(EvHttpServer* server, ExecEnv* exec_env)
: HttpHandlerWithAuth(exec_env), _http_server(server) {
_www_path = std::string(getenv("DORIS_HOME")) + "/www/";
// Make WebPageHandler to be static file handler, static files, e.g. css, png, will be handled by WebPageHandler.
_http_server->register_static_file_handler(this);
TemplatePageHandlerCallback root_callback =
std::bind<void>(std::mem_fn(&WebPageHandler::root_handler), this, std::placeholders::_1,
std::placeholders::_2);
register_template_page("/", "Home", root_callback, false /* is_on_nav_bar */);
}
WebPageHandler::~WebPageHandler() {
for (auto& handler : _page_map) {
delete handler.second;
}
}
void WebPageHandler::register_template_page(const std::string& path, const std::string& alias,
const TemplatePageHandlerCallback& callback,
bool is_on_nav_bar) {
// Relative path which will be used to find .mustache file in _www_path
std::string render_path = (path == "/") ? "/home" : path;
auto wrapped_cb = [callback, render_path, this](const ArgumentMap& args,
std::stringstream* output) {
EasyJson ej;
callback(args, &ej);
render(render_path, ej, true /* is_styled */, output);
};
register_page(path, alias, wrapped_cb, is_on_nav_bar);
}
void WebPageHandler::register_page(const std::string& path, const std::string& alias,
const PageHandlerCallback& callback, bool is_on_nav_bar) {
std::unique_lock lock(_map_lock);
CHECK(_page_map.find(path) == _page_map.end());
// first time, register this to web server
_http_server->register_handler(HttpMethod::GET, path, this);
_page_map[path] = new PathHandler(true /* is_styled */, is_on_nav_bar, alias, callback);
}
void WebPageHandler::handle(HttpRequest* req) {
VLOG_TRACE << req->debug_string();
PathHandler* handler = nullptr;
{
std::unique_lock lock(_map_lock);
auto iter = _page_map.find(req->raw_path());
if (iter != _page_map.end()) {
handler = iter->second;
}
}
if (handler == nullptr) {
// Try to handle static file request
do_file_response(_www_path + req->raw_path(), req);
// Has replied in do_file_response, so we return here.
return;
}
const auto& params = *req->params();
// Should we render with css styles?
bool use_style = (params.find("raw") == params.end());
std::stringstream content;
handler->callback()(params, &content);
std::string output;
if (use_style) {
std::stringstream oss;
render_main_template(content.str(), &oss);
output = oss.str();
} else {
output = content.str();
}
req->add_output_header(HttpHeaders::CONTENT_TYPE, s_html_content_type.c_str());
HttpChannel::send_reply(req, HttpStatus::OK, output);
}
static const std::string kMainTemplate = R"(
<!DOCTYPE html>
<html>
<head>
<title>Doris</title>
<meta charset='utf-8'/>
<link href='/Bootstrap-3.3.7/css/bootstrap.min.css' rel='stylesheet' media='screen' />
<link href='/Bootstrap-3.3.7/css/bootstrap-table.min.css' rel='stylesheet' media='screen' />
<script src='/jQuery-3.6.0/jquery-3.6.0.min.js'></script>
<script src='/Bootstrap-3.3.7/js/bootstrap.min.js' defer></script>
<script src='/Bootstrap-3.3.7/js/bootstrap-table.min.js' defer></script>
<script src='/doris.js' defer></script>
<link href='/doris.css' rel='stylesheet' />
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" style="padding-top: 5px;" href="/">
<img src="/logo.png" width='40' height='40' alt="Doris" />
</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{{#path_handlers}}
<li><a class="nav-link"href="{{path}}">{{alias}}</a></li>
{{/path_handlers}}
</ul>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</nav>
{{^static_pages_available}}
<div style="color: red">
<strong>Static pages not available. Make sure ${DORIS_HOME}/www/ exists and contains web static files.</strong>
</div>
{{/static_pages_available}}
{{{content}}}
</div>
{{#footer_html}}
<footer class="footer"><div class="container text-muted">
{{{.}}}
</div></footer>
{{/footer_html}}
</body>
</html>
)";
std::string WebPageHandler::mustache_partial_tag(const std::string& path) const {
return absl::Substitute("{{> $0.mustache}}", path);
}
bool WebPageHandler::static_pages_available() const {
bool is_dir = false;
return io::global_local_filesystem()->is_directory(_www_path, &is_dir).ok() && is_dir;
}
bool WebPageHandler::mustache_template_available(const std::string& path) const {
if (!static_pages_available()) {
return false;
}
bool exists;
return io::global_local_filesystem()
->exists(absl::Substitute("$0/$1.mustache", _www_path, path), &exists)
.ok() &&
exists;
}
void WebPageHandler::render_main_template(const std::string& content, std::stringstream* output) {
static const std::string& footer =
std::string("<pre>") + get_version_string(true) + std::string("</pre>");
EasyJson ej;
ej["static_pages_available"] = static_pages_available();
ej["content"] = content;
ej["footer_html"] = footer;
EasyJson path_handlers = ej.Set("path_handlers", EasyJson::kArray);
for (const auto& handler : _page_map) {
if (handler.second->is_on_nav_bar()) {
EasyJson path_handler = path_handlers.PushBack(EasyJson::kObject);
path_handler["path"] = handler.first;
path_handler["alias"] = handler.second->alias();
}
}
mustache::RenderTemplate(kMainTemplate, _www_path, ej.value(), output);
}
void WebPageHandler::render(const std::string& path, const EasyJson& ej, bool use_style,
std::stringstream* output) {
if (mustache_template_available(path)) {
mustache::RenderTemplate(mustache_partial_tag(path), _www_path, ej.value(), output);
} else if (use_style) {
(*output) << "<pre>" << ej.ToString() << "</pre>";
} else {
(*output) << ej.ToString();
}
}
void WebPageHandler::root_handler(const ArgumentMap& args, EasyJson* output) {
(*output)["version"] = get_version_string(false);
(*output)["cpuinfo"] = CpuInfo::debug_string();
(*output)["meminfo"] = MemInfo::debug_string();
(*output)["diskinfo"] = DiskInfo::debug_string();
}
} // namespace doris