#include #include #include #include #include "server/server.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "terminal_api_driver.h" #include "auth/resources.h" #include "auth/jwt.h" namespace ssl = boost::asio::ssl; // from static std::vector loadFile(const std::string& path) { std::ifstream is(path, std::ios::in | std::ios::binary); if (!is) { throw std::runtime_error("File not found"); } std::vector content; for (;;) { char buf[512]; auto len = is.read(buf, sizeof(buf)).gcount(); if (len <= 0) { break; } content.insert(content.end(), buf, buf + len); } return content; } namespace mime_types = http::server::mime_types; void init_logging() { namespace log = boost::log; namespace keywords = log::keywords; namespace expressions = log::expressions; namespace attributes = log::attributes; log::register_simple_formatter_factory("Severity"); // #ifdef USE_DEBUG // log::add_console_log(std::clog, keywords::format = "%TimeStamp%: [%Severity%] %Message% [%ThreadID%]"); // #else // log::add_file_log( // keywords::file_name = "/home/root/manager_orlik_%N.log", // keywords::rotation_size = 10 * 1024 * 1024, // keywords::time_based_rotation = log::sinks::file::rotation_at_time_point(0, 0, 0), // keywords::format = expressions::format("%1% [%2%] [%3%] <%4%> [%5%]") // % expressions::format_date_time("TimeStamp", "%Y-%m-%d, %H:%M:%S.%f") // % expressions::format_named_scope("Scope", keywords::format = "%n (%f:%l)") // % expressions::attr("Severity") // % expressions::message % expressions::attr("ThreadID"), // keywords::open_mode = std::ios_base::app, // keywords::auto_flush = true // ); // #endif log::add_console_log(std::clog, keywords::format = "%TimeStamp%: [%Severity%] %Message% [%ThreadID%]"); log::core::get()->set_filter(log::trivial::severity >= log::trivial::info); log::add_common_attributes(); } class ServerResources { std::unique_ptr sf; std::unique_ptr api; http::auth::AuthProvider auth{}; public: static constexpr const char* INDEX_HTML = "static/main.html"; static constexpr const char* LOGIN_HTML = "static/login.html"; // картинки, их даже можно кешировать static constexpr const char* FAVICON_ICO = "static/favicon.png"; static constexpr const char* KROKODIL_GIF = "static/krokodil.gif"; static constexpr const char* VUE_JS = "static/js/vue.js"; // это тоже можно кешировать // а эти стили нельзя кешировать в отладочной версии static constexpr const char* STYLE_CSS = "static/style.css"; static constexpr const char* FIELDS_CSS = "static/fields.css"; static constexpr const char* KB_MP4 = "static/video_2024-11-06_15-49-35.mp4"; ServerResources(const ServerResources&) = delete; ServerResources(): sf(std::make_unique()), api(std::make_unique()) { api->startDaemon(); auth.users.emplace_back(std::make_shared("admin")); sf->registerFile(FAVICON_ICO, mime_types::image_png, true); sf->registerFile(KROKODIL_GIF, mime_types::image_gif, true); sf->registerFile(VUE_JS, mime_types::javascript, true); sf->registerFile(KB_MP4, mime_types::video_mp4, true); #if USE_DEBUG constexpr bool allowCacheCss = false; #else constexpr bool allowCacheCss = true; #endif sf->registerFile(INDEX_HTML, mime_types::text_html, allowCacheCss); sf->registerFile(LOGIN_HTML, mime_types::text_html, allowCacheCss); sf->registerFile(STYLE_CSS, mime_types::text_css, allowCacheCss); sf->registerFile(FIELDS_CSS, mime_types::text_css, allowCacheCss); } void registerResources(http::server::Server& s) { s.resources.emplace_back(std::make_unique("/", [this](const auto& req, auto& rep) { auto user = auth.getSession(req); if (user == nullptr) { http::server::httpRedirect(rep, "/login"); } else { sf->serve(INDEX_HTML, rep); } })); s.resources.emplace_back(std::make_unique("/login", [this](const auto& req, auto& rep) { if (req.method == "GET") { auto user = auth.getSession(req); if (user == nullptr) { sf->serve(LOGIN_HTML, rep); } else { http::server::httpRedirect(rep, "/"); } } else if (req.method == "POST") { rep.status = http::server::ok; rep.headers.clear(); rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)}); try { std::istringstream is(std::string(req.payload.data(), req.payload.size())); boost::property_tree::ptree pt; read_json(is, pt); auto u = auth.doAuth(pt.get("username"), pt.get("password"), rep); if (u == nullptr) { throw std::runtime_error("invalid session"); } std::string result = R"({"redirect":"/"})"; rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); } catch (std::exception &e) { BOOST_LOG_TRIVIAL(error) << e.what() << std::endl; std::string result = R"({"error":"Неверный логин или пароль"})"; rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); } } else { http::server::stockReply(http::server::bad_request, rep); } })); s.resources.emplace_back(std::make_unique("/logout", [this](const auto& req, auto& rep) { if (req.method == "GET") { http::server::httpRedirect(rep, "/login"); rep.headers.push_back({.name = "Set-Cookie", .value = http::auth::jwt::EMPTY_AUTH_COOKIE}); } else { http::server::stockReply(http::server::bad_request, rep); } })); s.resources.emplace_back(std::make_unique("/favicon.ico", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FAVICON_ICO, rep); })); s.resources.emplace_back(std::make_unique("/images/krokodil_vzryvaetsya_hd.gif", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(KROKODIL_GIF, rep); })); s.resources.emplace_back(std::make_unique("/style.css", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(STYLE_CSS, rep); })); s.resources.emplace_back(std::make_unique("/fields.css", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FIELDS_CSS, rep); })); s.resources.emplace_back(std::make_unique("/js/vue.js", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(VUE_JS, rep); })); s.resources.emplace_back(std::make_unique("/vid/video_2024-11-06_15-49-35.mp4", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(KB_MP4, rep); })); s.resources.emplace_back(std::make_unique("/api/get/statistics", [this](const auto& req, auto& rep) { if (req.method != "GET") { http::server::stockReply(http::server::bad_request, rep); } rep.status = http::server::ok; rep.headers.clear(); rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)}); std::string result = R"({"mainState":)"; result += api->loadTerminalState(); result += "}"; rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); })); s.resources.emplace_back(std::make_unique("/api/get/settings", [this](const auto& req, auto& rep) { if (req.method != "GET") { http::server::stockReply(http::server::bad_request, rep); } rep.status = http::server::ok; rep.headers.clear(); rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)}); std::string result = R"({"settings":)"; result += api->loadSettings(); result += "}"; rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); })); s.resources.emplace_back(std::make_unique("/api/resetPacketStatistics", [this](const auto& req, auto& rep) { if (req.method != "POST") { http::server::stockReply(http::server::bad_request, rep); } api->resetPacketStatistics(); rep.status = http::server::ok; rep.headers.clear(); rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)}); const std::string result = R"({"status":"ok")"; rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); })); s.resources.emplace_back(std::make_unique("/api/set/qos", [this](const auto& req, auto& rep) { if (req.method != "POST") { http::server::stockReply(http::server::bad_request, rep); } rep.status = http::server::ok; rep.headers.clear(); rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)}); try { std::stringstream ss; ss.str(std::string(req.payload.begin(), req.payload.end())); boost::property_tree::ptree pt; read_json(ss, pt); api->setQosSettings(pt); std::string result = R"({"settings":)"; result += api->loadSettings(); result += "}"; rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); } catch (std::exception& e) { BOOST_LOG_TRIVIAL(error) << "WebHandle(/api/set/qos): Can't set QoS settings: " << e.what(); const std::string result = R"({"status":"error"})"; rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size()); } })); } ~ServerResources() = default; }; int main(int argc, char *argv[]) { try { prctl(PR_SET_NAME, "main", 0, 0, 0); // Check command line arguments. if (argc != 4 && argc != 5) { std::cerr << "Usage: http_server
[static files directory]\n"; std::cerr << " For IPv4, try:\n"; std::cerr << " receiver nossl 0.0.0.0 80\n"; std::cerr << " For IPv6, try:\n"; std::cerr << " receiver nossl 0::0 80\n"; return 1; } init_logging(); boost::log::core::get()->add_thread_attribute("Scope", boost::log::attributes::named_scope()); #ifdef USE_DEBUG BOOST_LOG_TRIVIAL(info) << "Starting DEBUG " << argv[0]; #else BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0]; #endif #ifdef USE_DEBUG http::auth::jwt::secretKey = "^}u'ZKyQ%;+:lnh^GS7!=G~nRK?7[{``"; BOOST_LOG_TRIVIAL(info) << "DEBUG build use pre-created key " << http::auth::jwt::secretKey; #else http::auth::jwt::generateSecretKey(); BOOST_LOG_TRIVIAL(info) << "Generated new secret key " << http::auth::jwt::secretKey; #endif ServerResources resources; // Initialise the server. std::unique_ptr s; if (strcmp(argv[1], "nossl") == 0) { s = std::make_unique(argv[2], argv[3]); resources.registerResources(*s); s->run(); } else if (strcmp(argv[1], "ssl") == 0) { const auto cert = loadFile("cert.pem"); const auto key = loadFile("key.pem"); const auto dh = loadFile("dh.pem"); auto ctx = std::make_shared(ssl::context::tlsv12); ctx->set_password_callback( [](std::size_t, ssl::context_base::password_purpose) { return "test"; }); ctx->set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use); ctx->use_certificate_chain(boost::asio::buffer(cert)); ctx->use_private_key(boost::asio::buffer(key), ssl::context::file_format::pem); ctx->use_tmp_dh(boost::asio::buffer(dh)); s = std::make_unique(argv[2], argv[3], ctx); resources.registerResources(*s); s->run(); } else { std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl; return 1; } } catch (std::exception &e) { BOOST_LOG_TRIVIAL(error) << e.what() << std::endl; return -1; } return 0; }