From 0b794fac407b6938dc89cee5341d3316f069eb59 Mon Sep 17 00:00:00 2001 From: Vladislav Ostapov Date: Mon, 4 Nov 2024 14:17:34 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=D1=85=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2,=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=83=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B5=20API=20=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20=D0=BA=D0=B5=D1=88?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/resources.cpp | 10 +++ src/auth/resources.h | 14 +++- src/main.cpp | 169 ++++++++++++++++++++++++++-------------- src/server/reply.cpp | 30 +++---- src/server/resource.cpp | 77 +++++++++++------- src/server/resource.h | 34 ++++---- 6 files changed, 217 insertions(+), 117 deletions(-) diff --git a/src/auth/resources.cpp b/src/auth/resources.cpp index a925565..7b2a57e 100644 --- a/src/auth/resources.cpp +++ b/src/auth/resources.cpp @@ -3,3 +3,13 @@ // #include "resources.h" + +#include + +// http::auth::AuentificationRequiredResource::AuentificationRequiredResource(const std::string &path, AuthProvider& provider, resource::respGenerator generator): BasicResource(path), generator_(std::move(generator)) { +// } +// +// void http::auth::AuentificationRequiredResource::handle(const server::Request &req, server::Reply &rep) { +// } +// +// http::auth::AuentificationRequiredResource::~AuentificationRequiredResource() = default; diff --git a/src/auth/resources.h b/src/auth/resources.h index d5f445e..f8fd052 100644 --- a/src/auth/resources.h +++ b/src/auth/resources.h @@ -67,10 +67,22 @@ namespace http::auth { */ bool doAuth(const std::string& username, const std::string& password, server::Reply& rep); + std::shared_ptr getSession(server::Request& req); + ~AuthProvider(); }; - class NeedAuentificationResource : public resource::BasicResource { + class AuentificationRequiredResource final: public resource::BasicResource { + public: + explicit AuentificationRequiredResource(const std::string& path, std::shared_ptr provider, resource::respGenerator generator); + + void handle(const server::Request &req, server::Reply &rep) override; + + ~AuentificationRequiredResource() override; + + private: + resource::respGenerator generator_; + std::shared_ptr provider_; }; } diff --git a/src/main.cpp b/src/main.cpp index 9896c63..89346e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ #include #include "terminal_api_driver.h" +#include "auth/resources.h" namespace ssl = boost::asio::ssl; // from @@ -72,70 +73,124 @@ void init_logging() { log::add_common_attributes(); } -static void initResources(http::server::Server& s, std::shared_ptr& api) { - s.resources.emplace_back(std::make_unique("/", "static/main.html", mime_types::text_html)); - s.resources.emplace_back(std::make_unique("/login", "static/login.html", mime_types::text_html)); +class ServerResources { + std::unique_ptr sf; + std::unique_ptr api; - s.resources.emplace_back(std::make_unique("/favicon.ico", "static/favicon.png", mime_types::image_png)); - s.resources.emplace_back(std::make_unique("/images/krokodil_vzryvaetsya_hd.gif", "static/krokodil.gif", mime_types::image_gif)); +public: + static constexpr const char* INDEX_HTML = "static/main.html"; + static constexpr const char* LOGIN_HTML = "static/login.html"; + static constexpr const char* LOGIN_FAILED_HTML = "static/login-failed.html"; - s.resources.emplace_back(std::make_unique("/style.css", "static/style.css", mime_types::text_css)); - s.resources.emplace_back(std::make_unique("/fields.css", "static/fields.css", mime_types::text_css)); - s.resources.emplace_back(std::make_unique("/js/vue.js", "static/js/vue.js", mime_types::javascript)); + // картинки, их даже можно кешировать + 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"; // это тоже можно кешировать - s.resources.emplace_back(std::make_unique("/api/statistics", [](const auto& req, auto& rep) { - if (req.method != "GET") { - http::server::stockReply(http::server::bad_request, rep); - } + // а эти стили нельзя кешировать в отладочной версии + static constexpr const char* STYLE_CSS = "static/style.css"; + static constexpr const char* FIELDS_CSS = "static/fields.css"; - rep.status = http::server::ok; - rep.headers.clear(); - rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)}); - const char* json = R"({"key":"value"})"; - rep.content.insert(rep.content.end(), json, json + strlen(json)); - })); + ServerResources(const ServerResources&) = delete; - s.resources.emplace_back(std::make_unique("/api/get/statistics", [api](const auto& req, auto& rep) { - if (req.method != "GET") { - http::server::stockReply(http::server::bad_request, rep); - } + ServerResources(): sf(std::make_unique()), api(std::make_unique()) { + sf->registerFile(INDEX_HTML, mime_types::text_html, false); + sf->registerFile(LOGIN_HTML, mime_types::text_html, false); + sf->registerFile(LOGIN_FAILED_HTML, mime_types::text_html, false); - 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()); - })); + 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); - s.resources.emplace_back(std::make_unique("/api/resetPacketStatistics", [api](const auto& req, auto& rep) { - if (req.method != "POST") { - http::server::stockReply(http::server::bad_request, rep); - } - api->resetPacketStatistics(); +#if USE_DEBUG + constexpr bool allowCacheCss = false; +#else + constexpr bool allowCacheCss = true; +#endif - 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()); - })); + sf->registerFile(STYLE_CSS, mime_types::text_css, allowCacheCss); + sf->registerFile(FIELDS_CSS, mime_types::text_css, allowCacheCss); + } - s.resources.emplace_back(std::make_unique("/api/get/settings", [api](const auto& req, auto& rep) { - if (req.method != "GET") { - http::server::stockReply(http::server::bad_request, rep); - } + void registerResources(http::server::Server& s) { + s.resources.emplace_back(std::make_unique("/", [this](const auto& req, auto& rep) { + boost::ignore_unused(req); + sf->serve(INDEX_HTML, rep); + })); + + s.resources.emplace_back(std::make_unique("/login", [this](const auto& req, auto& rep) { + if (req.method == "GET") { + sf->serve(LOGIN_HTML, rep); + } else if (req.method == "POST") { + sf->serve(LOGIN_FAILED_HTML, rep); + } 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("/api/statistics", [](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)}); + const char* json = R"({"key":"value"})"; + rep.content.insert(rep.content.end(), json, json + strlen(json)); + })); + + 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/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/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()); + })); + } + + ~ServerResources() = default; +}; - 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()); - })); -} int main(int argc, char *argv[]) { try { @@ -159,14 +214,14 @@ int main(int argc, char *argv[]) { BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0]; #endif - auto api = std::make_shared(); + ServerResources resources; // Initialise the server. std::unique_ptr s; if (strcmp(argv[1], "nossl") == 0) { s = std::make_unique(argv[2], argv[3]); - initResources(*s, api); + resources.registerResources(*s); s->run(); } else if (strcmp(argv[1], "ssl") == 0) { @@ -187,7 +242,7 @@ int main(int argc, char *argv[]) { ctx->use_tmp_dh(boost::asio::buffer(dh)); s = std::make_unique(argv[2], argv[3], ctx); - initResources(*s, api); + resources.registerResources(*s); s->run(); } else { std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl; diff --git a/src/server/reply.cpp b/src/server/reply.cpp index a259770..66e2cff 100644 --- a/src/server/reply.cpp +++ b/src/server/reply.cpp @@ -85,77 +85,77 @@ namespace http::server { namespace stock_replies { constexpr char ok[] = ""; constexpr char created[] = - "" + "\n" "Created" "

201 Created

" ""; constexpr char accepted[] = - "" + "\n" "Accepted" "

202 Accepted

" ""; constexpr char no_content[] = - "" + "\n" "No Content" "

204 Content

" ""; constexpr char multiple_choices[] = - "" + "\n" "Multiple Choices" "

300 Multiple Choices

" ""; constexpr char moved_permanently[] = - "" + "\n" "Moved Permanently" "

301 Moved Permanently

" ""; constexpr char moved_temporarily[] = - "" + "\n" "Moved Temporarily" "

302 Moved Temporarily

" ""; constexpr char not_modified[] = - "" + "\n" "Not Modified" "

304 Not Modified

" ""; constexpr char bad_request[] = - "" + "\n" "Bad Request" "

400 Bad Request

" ""; constexpr char unauthorized[] = - "" + "\n" "Unauthorized" "

401 Unauthorized

" ""; constexpr char forbidden[] = - "" + "\n" "Forbidden" "

403 Forbidden

" ""; constexpr char not_found[] = - "" + "\n" "Not Found" "

404 Not Found

" ""; constexpr char internal_server_error[] = - "" + "\n" "Internal Server Error" "

500 Internal Server Error

" ""; constexpr char not_implemented[] = - "" + "\n" "Not Implemented" "

501 Not Implemented

" ""; constexpr char bad_gateway[] = - "" + "\n" "Bad Gateway" "

502 Bad Gateway

" ""; constexpr char service_unavailable[] = - "" + "\n" "Service Unavailable" "

503 Service Unavailable

" ""; diff --git a/src/server/resource.cpp b/src/server/resource.cpp index f7e0e5c..7fa27e8 100644 --- a/src/server/resource.cpp +++ b/src/server/resource.cpp @@ -1,6 +1,9 @@ #include "resource.h" #include #include +#include + +#include "../../dependencies/control_system/common/protocol_commands.h" static void loadFile(const std::string& path, std::vector& content) { std::ifstream is(path, std::ios::in | std::ios::binary); @@ -19,43 +22,59 @@ static void loadFile(const std::string& path, std::vector& content) { } } -http::resource::StaticFileResource::StaticFileResource(const std::string &path, const std::string &filePath, server::mime_types::Mime type): type(type) { - this->path = path; -#ifdef USE_DEBUG - BOOST_LOG_TRIVIAL(info) << "Skip loading file " << filePath << " (http path: " << path << ")"; - this->filePath = filePath; -#else - BOOST_LOG_TRIVIAL(info) << "Load file " << filePath << " (http path: " << path << ")"; - loadFile(filePath, this->content); -#endif -} +http::resource::BasicResource::BasicResource(std::string path): path(std::move(path)) {} -void http::resource::StaticFileResource::handle(const server::Request &req, server::Reply &rep) { - if (req.method != "GET") { - stockReply(server::bad_request, rep); - return; +http::resource::StaticFileFactory::StaticFileDef::StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache): path(std::move(path)), type(type), allowCache(allowCache) { +#ifdef USE_DEBUG + if (allowCache) { + BOOST_LOG_TRIVIAL(info) << "Load static file " << this->path; + loadFile(this->path, this->content); + } else { + BOOST_LOG_TRIVIAL(info) << "Skip loading static file " << this->path; } - - // TODO сделать поддержку range -#ifdef USE_DEBUG - BOOST_LOG_TRIVIAL(debug) << "Reload file " << filePath << " (http path: " << path << ")"; - loadFile(this->filePath, rep.content); #else - rep.content.clear(); - rep.content.insert(rep.content.end(), this->content.begin(), this->content.end()); + BOOST_LOG_TRIVIAL(info) << "Load static file " << this->path; + loadFile(this->path, this->content); #endif - rep.status = server::ok; - rep.headers.clear(); - // TODO сделать cache control - rep.headers.push_back({.name = "Content-Type", .value = toString(this->type)}); +} +http::resource::StaticFileFactory::StaticFileDef::~StaticFileDef() = default; + +http::resource::StaticFileFactory::StaticFileFactory() = default; + +void http::resource::StaticFileFactory::registerFile(const std::string &path, server::mime_types::Mime type, bool allowCache) { + this->files.emplace_back(path, type, allowCache); } -http::resource::StaticFileResource::~StaticFileResource() = default; - -http::resource::GenericResource::GenericResource(const std::string &path, const respGenerator &generator): generator_(generator) { - this->path = path; +void http::resource::StaticFileFactory::serve(const std::string &path, server::Reply &rep) { + for (auto& f: this->files) { + if (f.path == path) { +#ifdef USE_DEBUG + if (f.allowCache) { + rep.content.clear(); + rep.content.insert(rep.content.end(), f.content.begin(), f.content.end()); + } else { + BOOST_LOG_TRIVIAL(debug) << "Reload file " << path << " (http path: " << path << ")"; + loadFile(f.path, rep.content); + } +#else + rep.content.clear(); + rep.content.insert(rep.content.end(), f.content.begin(), f.content.end()); +#endif + rep.status = server::ok; + // rep.headers.clear(); + rep.headers.push_back({.name = "Content-Type", .value = server::mime_types::toString(f.type)}); + if (f.allowCache) { + rep.headers.push_back({.name = "Cache-Control", .value = "max-age=86400"}); // сутки, думаю хватит + } + return; + } + } } +http::resource::StaticFileFactory::~StaticFileFactory() = default; + +http::resource::GenericResource::GenericResource(const std::string &path, respGenerator generator): BasicResource(path), generator_(std::move(generator)) {} + void http::resource::GenericResource::handle(const server::Request &req, server::Reply &rep) { this->generator_(req, rep); } diff --git a/src/server/resource.h b/src/server/resource.h index 8545a93..0f9e4d5 100644 --- a/src/server/resource.h +++ b/src/server/resource.h @@ -12,6 +12,7 @@ namespace http::resource { */ class BasicResource { public: + BasicResource(std::string path); std::string path; virtual void handle(const server::Request &req, server::Reply &rep) = 0; @@ -19,24 +20,27 @@ namespace http::resource { virtual ~BasicResource() = default; }; - /** - * Класс ресурса статического файла - */ - class StaticFileResource final : public BasicResource { - private: - server::mime_types::Mime type; -#ifdef USE_DEBUG - std::string filePath; -#else - std::vector content; -#endif + class StaticFileFactory { + class StaticFileDef { + public: + StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache = true); + std::string path; + server::mime_types::Mime type; + bool allowCache; + std::vector content; + + ~StaticFileDef(); + }; + std::vector files; public: - StaticFileResource(const std::string& path, const std::string& filePath, server::mime_types::Mime type); + StaticFileFactory(); - void handle(const server::Request &req, server::Reply &rep) override; + void registerFile(const std::string& path, server::mime_types::Mime type, bool allowCache = true); - ~StaticFileResource() override; + void serve(const std::string& path, server::Reply& rep); + + ~StaticFileFactory(); }; using respGenerator = std::function; @@ -49,7 +53,7 @@ namespace http::resource { respGenerator generator_; public: - GenericResource(const std::string& path, const respGenerator& generator); + GenericResource(const std::string& path, respGenerator generator); void handle(const server::Request &req, server::Reply &rep) override;