// 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 #include "common/config.h" #include "env/env.h" #include "gutil/stl_util.h" #include "gutil/strings/substitute.h" #include "http/ev_http_server.h" #include "http/http_channel.h" #include "http/http_headers.h" #include "http/http_request.h" #include "http/http_response.h" #include "http/http_status.h" #include "http/utils.h" #include "olap/file_helper.h" #include "util/cpu_info.h" #include "util/debug_util.h" #include "util/disk_info.h" #include "util/mem_info.h" #include "util/mustache/mustache.h" using strings::Substitute; namespace doris { static std::string s_html_content_type = "text/html"; WebPageHandler::WebPageHandler(EvHttpServer* server) : _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(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() { STLDeleteValues(&_page_map); } void WebPageHandler::register_template_page(const std::string& path, const 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 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"( Doris {{^static_pages_available}}
Static pages not available. Make sure ${DORIS_HOME}/www/ exists and contains web static files.
{{/static_pages_available}} {{{content}}} {{#footer_html}}
{{{.}}}
{{/footer_html}} )"; std::string WebPageHandler::mustache_partial_tag(const std::string& path) const { return strings::Substitute("{{> $0.mustache}}", path); } bool WebPageHandler::static_pages_available() const { bool is_dir = false; return Env::Default()->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; } return Env::Default()->path_exists(strings::Substitute("$0/$1.mustache", _www_path, path)).ok(); } void WebPageHandler::render_main_template(const std::string& content, std::stringstream* output) { static const std::string& footer = std::string("
") + get_version_string(true) + std::string("
"); 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 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) << "
" << ej.ToString() << "
"; } 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