commit 88cb77935c84141abc1ed45a42b55a175f2ca685 Author: chatlanin Date: Tue Jul 9 11:07:45 2024 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..1a1b803 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build +.cache +subprojects/* +!subprojects/hack.wrap +!subprojects/httplib.wrap +!subprojects/jwt-cpp.wrap +!subprojects/nlohmann_json.wrap diff --git a/README.md b/README.md new file mode 100755 index 0000000..8b168c9 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Базовая реализация типового сервера для разнообразных проектов + +run + diff --git a/meson.build b/meson.build new file mode 100755 index 0000000..4c66fce --- /dev/null +++ b/meson.build @@ -0,0 +1,53 @@ +project( + meson.current_source_dir().split('/').get(-1), + 'cpp', + version : '1.0.0', + default_options : [ + 'warning_level=1', + 'optimization=3', + 'cpp_std=c++20', +]) + +add_project_arguments ( + '-Wpedantic', + '-Wno-shadow', + '-Wno-unused-but-set-variable', + '-Wno-comment', + '-Wno-unused-parameter', + '-Wno-unused-value', + '-Wno-unused-header', + '-Wno-missing-field-initializers', + '-Wno-narrowing', + '-Wno-deprecated-enum-enum-conversion', + '-Wno-volatile', + '-Wno-format-security', + '-Wno-switch', + '-Wno-ignored-attributes', + '-Wno-unused-variable', + '-Wno-deprecated-enum-enum-conversion', + language: 'cpp' +) + +############################################################# + +#args = ['-lglfw', '-ldl', '-lGL', '-lpthread', '-lX11', '-lXxf86vm', '-lXrandr', '-lXi'] +args = [] +deps = [] +inc = [] + +deps = [ + dependency('uuid'), + dependency('threads'), + dependency('libpqxx'), + subproject('hack').get_variable('hack_dep'), + subproject('nlohmann_json').get_variable('nlohmann_json_dep'), + subproject('jwt-cpp').get_variable('jwt_dep'), + subproject('httplib').get_variable('cpp_httplib_dep'), +] + +subdir('src') +subdir('tests') + +message('==================================================================================================================') +message('=== т.к. мы не включаем зависимости в проект, котрый пушм на гит, то их нужно все подключить в deps (см. выше) ===') +message('==================================================================================================================') diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..47ec7fa --- /dev/null +++ b/run.sh @@ -0,0 +1,22 @@ +#!/bin/zsh + +PROJECT_NAME=$(basename $PWD) + +run() { + command meson compile -C build + if [[ -z "$1" ]]; then + cd build + ./tests/$PROJECT_NAME + cd .. + else + meson test $1 -C build + fi +} + +if [ -d "build" ]; then + run +else + command meson setup build + run +fi + diff --git a/src/meson.build b/src/meson.build new file mode 100755 index 0000000..e2b5499 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,32 @@ +inc += include_directories('.') +headers = [ + 'trs/trs.hpp', + 'trs/helpers/execute.hpp', + 'trs/helpers/verification.hpp', + + 'trs/libs/database.hpp', + 'trs/libs/function_manager.hpp', + 'trs/libs/transaction.hpp', + 'trs/libs/inspector.hpp', + + 'trs/utils/define.hpp', + 'trs/utils/using.hpp', + 'trs/utils/var.hpp', +] + +sources = [ ] + +lib = library( + meson.project_name(), + include_directories : inc, + sources: [headers, sources], + dependencies : deps, + cpp_args: args +) + +trs_dep = declare_dependency( + include_directories: inc, + link_with: lib, +) + +deps += trs_dep diff --git a/src/trs/helpers/execute.hpp b/src/trs/helpers/execute.hpp new file mode 100644 index 0000000..6a2a5d5 --- /dev/null +++ b/src/trs/helpers/execute.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "hack/exception/exception.hpp" +#include "trs/utils/var.hpp" + +namespace trs +{ + template + void execute(FunctionManager& fm, Transaction& tr) + { + try + { + fm.execute(tr); + } + catch (hack::exception& ex) + { + throw ex; + } + catch (std::exception& e) + { + hack::exception ex; + ex.description("it can be very dangerous. this error when we execute function"); + ex.message(var::EXECUTE_ERROR); + ex.system_error(e); + throw ex; + } + catch (...) + { + hack::exception ex; + ex.description(var::ALIEN_SYSTEM_ERROR); + ex.message(var::EXECUTE_ERROR); + throw ex; + } + } +} diff --git a/src/trs/helpers/verification.hpp b/src/trs/helpers/verification.hpp new file mode 100644 index 0000000..8ad4301 --- /dev/null +++ b/src/trs/helpers/verification.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "hack/exception/exception.hpp" +#include "trs/utils/var.hpp" + +namespace trs +{ + template + void verification(Inspector& ins, FunctionManager& fm, Transaction& tr) + { + try + { + fm.valid(tr.m_passport.m_func_name); + } + catch (hack::exception& ex) + { + throw ex; + } + + try + { + ins.valid(tr); + } + catch (hack::exception& ex) + { + throw ex; + } + catch (std::exception& e) + { + hack::exception ex; + ex.description("this payload failed verification"); + ex.system_error(e); + throw ex; + } + catch (...) + { + hack::exception ex; + ex.description(var::ALIEN_SYSTEM_ERROR + " : payload failed verification"); + throw ex; + } + } +} diff --git a/src/trs/libs/database.hpp b/src/trs/libs/database.hpp new file mode 100644 index 0000000..009f0d6 --- /dev/null +++ b/src/trs/libs/database.hpp @@ -0,0 +1,154 @@ +#pragma once + +#include + +#include "hack/utils/func_query.hpp" +#include "hack/utils/singleton.hpp" +#include "hack/logger/logger.hpp" +#include "hack/exception/exception.hpp" + +#include "trs/utils/var.hpp" +#include "trs/utils/using.hpp" + +namespace trs +{ + class database : public hack::utils::singleton + { + friend hack::utils::singleton; + + public: + ~database() = default; + + private: + database() = default; + std::mutex m_mutex; + + std::string DB_APP_CONNECTION; + std::string DB_LOG_CONNECTION; + + public: + void set_connection(std::string app, std::string log) + { + DB_APP_CONNECTION = app; + DB_LOG_CONNECTION = log; + } + + template + JSON execute(const std::string func_name, const Args... args) + { + std::string query; + JSON result; + + try + { + query = hack::utils::make_query(func_name, args...); + result = execute(func_name, query); + } + catch(const hack::exception& ex) + { + throw ex; + } + catch (const std::exception& e) + { + hack::exception ex; + ex.description("database dont create query from args"); + ex.system_error(e); + ex.params("query", query); + ex.variadic_params(args...); + throw ex; + } + + return result; + } + + private: + JSON execute(const std::string& func_name, const std::string& query) + { + if (DB_APP_CONNECTION.empty() || DB_LOG_CONNECTION.empty()) + { + hack::error()("DB_LOG_CONNECTION or DB_LOG_CONNECTION is empty"); + hack::log("")("DB_APP_CONNECTION = ", DB_APP_CONNECTION); + hack::log("")("DB_LOG_CONNECTION = ", DB_LOG_CONNECTION); + std::terminate(); + } + + JSON result; + pqxx::connection* app_con; + pqxx::connection* log_con; + + try + { + app_con = new pqxx::connection(DB_APP_CONNECTION); + log_con = new pqxx::connection(DB_LOG_CONNECTION); + } + catch (const std::exception& e) + { + hack::error()("Dont create connection"); + hack::error()(e.what()); + std::terminate(); + } + catch(...) + { + hack::error()("Dont create connection and no get error in description"); + hack::error()("Good luck my friend! ..."); + std::terminate(); + } + + pqxx::result r; + pqxx::nontransaction work { func_name == var::FUNC_LOGGER ? *log_con : *app_con }; + + try + { + std::lock_guard lock(m_mutex); + r = work.exec(query); + std::string r_str; + for (auto row : r) r_str = row[func_name].c_str(); + work.commit(); + result = JSON::parse(r_str); + } + catch (const std::exception& e) + { + if (func_name != var::FUNC_LOGGER) + { + hack::exception ex; + ex.description("No exec query to database"); + ex.message(var::EXECUTE_ERROR); + ex.system_error(e); + ex.params("query", query); + ex.params("result", result); + throw ex; + } + else + { + hack::error()("WARNING!!! ERROR LOG TO DATABASE"); + hack::error()("query", query); + hack::error()(e.what()); + std::terminate(); + } + } + catch (...) + { + if (func_name != var::FUNC_LOGGER) + { + hack::exception ex; + ex.description(var::ALIEN_SYSTEM_ERROR); + ex.message(var::EXECUTE_ERROR); + ex.params("query", query); + ex.params("result", result); + throw ex; + } + else + { + hack::error()("WARNING!!! ERROR LOG TO DATABASE"); + hack::error()("query", query); + std::terminate(); + } + } + + delete app_con; + delete log_con; + + return result; + } + }; +} diff --git a/src/trs/libs/function_manager.hpp b/src/trs/libs/function_manager.hpp new file mode 100644 index 0000000..e5bae1b --- /dev/null +++ b/src/trs/libs/function_manager.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include "hack/exception/exception.hpp" + +namespace trs +{ + template + class function_manager + { + using functions = std::map>; + + public: + function_manager() = default; + ~function_manager() = default; + + private: + functions m_functions; + + public: + void valid(std::string func_name) const + { + if (m_functions.count(func_name) == 0) + { + hack::exception ex; + ex.description("this function is not register in function manager"); + throw ex; + } + } + + template + void register_function(const Key k, const Function f) noexcept + { + m_functions[k] = f; + } + + void execute(Transaction& tr) + { + m_functions[tr.m_passport.m_func_name](tr); + } + }; +} diff --git a/src/trs/libs/inspector.hpp b/src/trs/libs/inspector.hpp new file mode 100644 index 0000000..23ac23e --- /dev/null +++ b/src/trs/libs/inspector.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +#include "hack/exception/exception.hpp" + +namespace trs +{ + template + class inspector + { + using functions = std::map>; + + public: + inspector() = default; + ~inspector() = default; + + public: + template + void register_function(const Key k, const Function f) noexcept + { + m_functions[k] = f; + } + + void valid(Transaction& tr) + { + if (!valid(tr.m_passport.m_func_name)) + { + hack::exception ex; + ex.description("Function is not register in inspector. Please do it"); + throw ex; + } + + m_functions[tr.m_passport.m_func_name](tr); + } + + private: + bool valid(std::string func_name) const noexcept + { + return m_functions.count(func_name) > 0; + } + + private: + functions m_functions; + }; +} diff --git a/src/trs/libs/transaction.hpp b/src/trs/libs/transaction.hpp new file mode 100644 index 0000000..04047f8 --- /dev/null +++ b/src/trs/libs/transaction.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "hack/security/uuid.hpp" +#include "hack/exception/exception.hpp" +#include "hack/utils/json_converter.hpp" + +#include "trs/utils/using.hpp" +#include "trs/utils/var.hpp" + +namespace trs +{ + class transaction : public hack::utils::json_converter + { + public: + transaction() : m_transaction_id { hack::security::generate_uuid() } { }; + ~transaction() = default; + + public: + std::string m_transaction_id; + + struct passport + { + int m_id = -1000; // Эта сущность, которая зашифрована в токене. Например id организации + std::string m_func_name; + std::string m_token = "no token"; + } m_passport; + + struct data + { + JSON m_payload; + JSON m_result; + } m_data; + + public: + template + void set_data(const Request& req) + { + set_function(req); + set_token(req); + set_payload(req); + } + + JSON convert_to_json() override + { + JSON j; + j["transaction_id"] = m_transaction_id; + j["func_name"] = m_passport.m_func_name; + j["token"] = m_passport.m_token; + j["payload"] = m_data.m_payload; + j["result"] = m_data.m_result; + j["id"] = m_passport.m_id; + + return j; + } + + private: + template + void set_function(const Request& req) + { + try + { + m_passport.m_func_name = req.get_header_value("x-server-function"); + if (m_passport.m_func_name.empty()) throw std::invalid_argument{ var::NO_VALID_DATA }; + } + catch(std::exception& e) + { + } + catch(...) + { + hack::exception ex; + ex.description(var::ALIEN_SYSTEM_ERROR); + throw ex; + } + } + + template + void set_token(const Request &req) + { + try + { + m_passport.m_token = req.get_header_value("x-server-token"); + if (m_passport.m_token.empty()) throw std::invalid_argument{ var::NO_VALID_DATA }; + } + catch(std::exception& e) + { + hack::exception ex; + ex.description("dont get token from headers, because it system error, or maybe is field be empty"); + ex.system_error(e); + throw ex; + } + catch(...) + { + hack::exception ex; + ex.description(var::ALIEN_SYSTEM_ERROR); + throw ex; + } + } + + template + void set_payload(const Request &req) + { + if (req.body.empty()) return; + try + { + m_data.m_payload = JSON::parse(req.body); + } + catch(const std::exception& e) + { + hack::exception ex; + ex.description("Dont parser body from string"); + ex.system_error(e); + throw ex; + } + catch(...) + { + hack::exception ex; + ex.description(var::ALIEN_SYSTEM_ERROR); + throw ex; + } + } + }; +} diff --git a/src/trs/trs.hpp b/src/trs/trs.hpp new file mode 100644 index 0000000..41781ce --- /dev/null +++ b/src/trs/trs.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include + +#include "httplib.h" +#include "hack/logger/logger.hpp" + +#include "trs/utils/var.hpp" +#include "trs/libs/transaction.hpp" +#include "trs/libs/function_manager.hpp" +#include "trs/libs/inspector.hpp" +#include "trs/libs/database.hpp" +#include "trs/helpers/verification.hpp" +#include "trs/helpers/execute.hpp" + +namespace trs +{ + class http : public httplib::Server + { + public: + http() = default; + ~http() = default; + + public: + void init(std::string app_connection, std::string log_connection) + { + set_read_timeout(5, 0); + set_write_timeout(5, 0); + set_idle_interval(0, 1'000'000); + set_payload_max_length(1024 * 1024 * 512); // 512MB + set_CORS(); + database::instance().set_connection(app_connection, log_connection); + } + + template + void register_function(Key func_name, Function func) { m_function_manager.register_function(func_name, func); } + + template + void register_inspector(Key func_name, Function func) { m_inspector.register_function(func_name, func); } + + void run() + { + // HERE + // может в init перенести ??? + set_post(); + + try + { + hack::log(" ")("server listen:", var::HOST, var::PORT); + listen(var::HOST, var::PORT); + } + catch(std::exception& e) + { + hack::error(" ")("error server run:", e.what()); + } + catch(...) + { + hack::error()("SUPPER ERROR!!! GOOD LUCK MY FRIEND!!! :)"); + } + } + + private: + function_manager m_function_manager; + inspector m_inspector; + + private: + void set_CORS() + { + Options(R"(\*)", [](const auto& req, auto& res) { res.set_header("Allow", "POST, HEAD, OPTIONS"); }); + Options(var::API_URL, [](const auto& req, auto& res) + { + res.set_header("Access-Control-Allow-Origin", "*"); + res.set_header("Allow", "POST, HEAD, OPTIONS"); + res.set_header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept, Origin, Authorization, X-server-function, X-token-auth"); + res.set_header("Access-Control-Allow-Methods", "OPTIONS, HEAD, POST"); + }); + } + + void set_post() + { + Post(var::API_URL, [&](const httplib::Request& req, httplib::Response& res) { + res.set_header("Access-Control-Allow-Origin", "*"); + res.set_header("Access-Control-Allow-Headers", "*"); + res.set_header("Access-Control-Allow-Methods", "POST"); + res.set_header("Access-Control-Allow-Credentials", "false"); + + transaction tr; + + try + { + tr.set_data(req); + verification(m_inspector, m_function_manager, tr); + execute(m_function_manager, tr); + } + catch(hack::exception& ex) + { + ex.transaction(tr); + ex.commit(); + ex.commit(); + } + + res.set_content(nlohmann::to_string(tr.m_data.m_result), var::HEADER_FLAG_JSON); + }); + } + }; +} + + diff --git a/src/trs/utils/define.hpp b/src/trs/utils/define.hpp new file mode 100644 index 0000000..1ecba95 --- /dev/null +++ b/src/trs/utils/define.hpp @@ -0,0 +1,22 @@ +#pragma once + +// // ERRORS DEFINED +// #define ALIEN_SYSTEM_ERROR() \ +// try_exception try_ex; \ +// try_ex.set_description(var::ALIEN_SYSTEM_ERROR); \ +// try_ex.set_msg_to_front(var::status::ERROR); \ +// throw try_ex +// +// #define STL_ERROR(msg) \ +// try_exception try_ex; \ +// try_ex.set_description(msg); \ +// try_ex.set_system_error(e); \ +// try_ex.set_msg_to_front(var::status::ERROR); \ +// throw try_ex + +// HERE +// для малоли что. можно использовать в сию-минутном логировании без боязни забыть +// можно перенести это как дополнение в логер в hack +#define TRS_LOG(...) if (trs::var::status::DEBUG) hack::log(": ")(__VA_ARGS__) +#define TRS_ERROR(...) if (trs::var::status::DEBUG) hack::error(": ")(__VA_ARGS__) + diff --git a/src/trs/utils/using.hpp b/src/trs/utils/using.hpp new file mode 100644 index 0000000..63ecaff --- /dev/null +++ b/src/trs/utils/using.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "nlohmann/json.hpp" + +namespace trs +{ + using JSON = nlohmann::json; +} diff --git a/src/trs/utils/var.hpp b/src/trs/utils/var.hpp new file mode 100644 index 0000000..81b4347 --- /dev/null +++ b/src/trs/utils/var.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace trs::var +{ + inline const int PORT = 5000; + inline const std::string HOST = "0.0.0.0"; + inline const std::string API_URL = "/"; + + inline const std::string ALIEN_SYSTEM_ERROR = "is alien system error it can be very dangerous!!! good luck my friend!"; + inline const std::string NO_VALID_DATA = "no valid data"; + inline const std::string EXECUTE_ERROR = "execute error"; + inline const std::string HEADER_FLAG_JSON = "application/json"; + + inline const std::string FUNC_LOGGER = "logger"; +} diff --git a/subprojects/hack.wrap b/subprojects/hack.wrap new file mode 100755 index 0000000..8b65299 --- /dev/null +++ b/subprojects/hack.wrap @@ -0,0 +1,6 @@ +[wrap-git] +url = https://gitcast.ru/chatlanin/hack.git +revision = master + +[provide] +hack = hack_dep diff --git a/subprojects/httplib.wrap b/subprojects/httplib.wrap new file mode 100644 index 0000000..bef14aa --- /dev/null +++ b/subprojects/httplib.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://github.com/yhirose/cpp-httplib.git +revision = master + diff --git a/subprojects/jwt-cpp.wrap b/subprojects/jwt-cpp.wrap new file mode 100644 index 0000000..cab1491 --- /dev/null +++ b/subprojects/jwt-cpp.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://gitcast.ru/chatlanin/jwt-cpp.git +revision = master + diff --git a/subprojects/nlohmann_json.wrap b/subprojects/nlohmann_json.wrap new file mode 100644 index 0000000..bf5d700 --- /dev/null +++ b/subprojects/nlohmann_json.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = nlohmann_json-3.11.2 +lead_directory_missing = true +source_url = https://github.com/nlohmann/json/releases/download/v3.11.2/include.zip +source_filename = nlohmann_json-3.11.2.zip +source_hash = e5c7a9f49a16814be27e4ed0ee900ecd0092bfb7dbfca65b5a421b774dccaaed +wrapdb_version = 3.11.2-1 + +[provide] +nlohmann_json = nlohmann_json_dep diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..1dfc6a4 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,36 @@ +#include "hack/exception/exception.hpp" + +#include "trs/trs.hpp" +#include "trs/utils/var.hpp" + +namespace worckspaces +{ + inline void provider(trs::transaction& tr) + { + hack::log()("provider", tr.m_data.m_payload); + tr.m_data.m_result["status"] = "ok"; + tr.m_data.m_result["result"] = "super ok"; + } + + inline void inspector(trs::transaction& tr) + { + if (tr.m_data.m_payload.size() != 0) + { + hack::exception ex; + ex.description("transaction payload size > 0 but payload must be empty"); + ex.message(trs::var::NO_VALID_DATA); + ex.commit(); + throw ex; + } + } +} + +auto main(int argc, char* args[]) -> int +{ + trs::http app; + app.register_function("healthcheck", worckspaces::provider); + app.register_inspector("healthcheck", worckspaces::inspector); + + app.init("app_connection", "log_connection"); + app.run(); +} diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..8d55723 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,8 @@ +executable( + meson.project_name(), + 'main.cpp', + dependencies : deps, + cpp_args: args +) + + diff --git a/tmp_/meson.build b/tmp_/meson.build new file mode 100755 index 0000000..97213f1 --- /dev/null +++ b/tmp_/meson.build @@ -0,0 +1,33 @@ +inc += include_directories('.') + +headers = [ + 'trs.hpp', + 'helpers/execute.hpp', + 'helpers/verification.hpp', + + 'libs/database.hpp', + 'libs/function_manager.hpp', + 'libs/transaction.hpp', + 'libs/inspector.hpp', + + 'utils/define.hpp', + 'utils/using.hpp', + 'utils/var.hpp', +] + +sources = [ ] + +lib = library( + meson.project_name(), + include_directories : inc, + sources: [headers, sources], + dependencies : deps, + cpp_args: args +) + +trs_dep = declare_dependency( + include_directories: inc, + link_with: lib, +) + +deps += trs_dep