diff --git a/.gitignore b/.gitignore index a4cb815..4b34173 100644 --- a/.gitignore +++ b/.gitignore @@ -68,8 +68,9 @@ m4/lt~obsolete.m4 /drogon_dashboard /DrogonCMS -vendors/drogon/local-api-server/local-drogon-api-server -vendors/drogon/local-api-server/test/local-server_test +vendors/local-api-server/local-drogon-api-server +vendors/local-api-server/test/local-server_test +vendors/simple-reverse-proxy/simple-reverse-proxy # Generated Makefile # (meta build system like autotools, diff --git a/CMakeLists.txt b/CMakeLists.txt index e3c5717..deb9daf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ cmake_minimum_required(VERSION 3.1...3.28) # If you set any CMAKE_ variables, that can go here. # (But usually don't do this, except maybe for C++ standard) -set(CMAKE_PROJECT_DESCRIPTION "A GTK / Drogon CMS") +set(CMAKE_PROJECT_DESCRIPTION "A GTK Drogon CMS") set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/odinzu/drogoncms") set(CMAKE_C_STANDARD 11) @@ -116,7 +116,8 @@ add_definitions(${GTK4_CFLAGS_OTHER} ${ADW_CFLAGS_OTHER}) add_executable(DrogonCMS src/base.c) # Include drogon source code locally -add_subdirectory(vendors/drogon/local-api-server/) +add_subdirectory(vendors/local-api-server/) +add_subdirectory(vendors/simple-reverse-proxy/) # This is a "default" library, and will match the *** variable setting. # Other common choices are STATIC, SHARED, and MODULE diff --git a/vendors/drogon/local-api-server/build/README.md b/vendors/drogon/local-api-server/build/README.md deleted file mode 100644 index e68d7de..0000000 --- a/vendors/drogon/local-api-server/build/README.md +++ /dev/null @@ -1,53 +0,0 @@ -## A Drogon API + optional Proxy Local Web Server - This software creates a secure localalized Drogon API application. - -### License -* GPL-3.0 - -### Developer Dependency Requirements -* CLI Experience - -*Note: This has been built and tested on Arch Linux* - -### Manual Steps to build Drogon API from Source code - -0. `nano ./config.yaml` -1. The `build/` folder is where the server will be compiled and served. `cd build/` -2. `cmake ..` -3. `make` -4. `./local-drogon-api-server` -5. Goto Web Browser and view the local API web server -`localhost:5555/` - -## Development & Contributing - -### Requirements - -* A Computer -* CMake -* Update the ./config.yaml or .json file - -### Recommendations - -* A VPS Provider - -## Authors - -* SharpeTronics, LLC -* oDinZu WenKi(Charles) - -## Financial Support & Donations - -oDinZu WenKi(Charles) https://liberapay.com/oDinZu/ - -* Liberapay is a recurrent donations platform -* Run by a non-profit organization -* Source code is public -* No commission fee -* ~5% payment processing fee - -## Learning Resources -* CMake Documentation https://cmake.org/cmake/help/book/mastering-cmake/index.html -* Drogon Web Server https://github.com/drogonframework/drogon - -*Notice: We focus implementation with the C programming language and only add various features of C ++ code.* diff --git a/vendors/drogon/local-api-server/.gitignore b/vendors/local-api-server/.gitignore similarity index 100% rename from vendors/drogon/local-api-server/.gitignore rename to vendors/local-api-server/.gitignore diff --git a/vendors/drogon/local-api-server/CMakeLists.txt b/vendors/local-api-server/CMakeLists.txt similarity index 100% rename from vendors/drogon/local-api-server/CMakeLists.txt rename to vendors/local-api-server/CMakeLists.txt diff --git a/vendors/drogon/local-api-server/README.md b/vendors/local-api-server/README.md similarity index 100% rename from vendors/drogon/local-api-server/README.md rename to vendors/local-api-server/README.md diff --git a/vendors/drogon/local-api-server/config.yaml b/vendors/local-api-server/config.yaml similarity index 100% rename from vendors/drogon/local-api-server/config.yaml rename to vendors/local-api-server/config.yaml diff --git a/vendors/drogon/local-api-server/controllers/README.md b/vendors/local-api-server/controllers/README.md similarity index 100% rename from vendors/drogon/local-api-server/controllers/README.md rename to vendors/local-api-server/controllers/README.md diff --git a/vendors/drogon/local-api-server/example-config.json b/vendors/local-api-server/example-config.json similarity index 100% rename from vendors/drogon/local-api-server/example-config.json rename to vendors/local-api-server/example-config.json diff --git a/vendors/drogon/local-api-server/filters/README.md b/vendors/local-api-server/filters/README.md similarity index 100% rename from vendors/drogon/local-api-server/filters/README.md rename to vendors/local-api-server/filters/README.md diff --git a/vendors/drogon/local-api-server/index.html b/vendors/local-api-server/index.html similarity index 100% rename from vendors/drogon/local-api-server/index.html rename to vendors/local-api-server/index.html diff --git a/vendors/drogon/local-api-server/main.cc b/vendors/local-api-server/main.cc similarity index 98% rename from vendors/drogon/local-api-server/main.cc rename to vendors/local-api-server/main.cc index 61babcc..06bdad0 100644 --- a/vendors/drogon/local-api-server/main.cc +++ b/vendors/local-api-server/main.cc @@ -1,3 +1,4 @@ +// https://github.com/drogonframework/drogon/blob/master/examples/jsonstore/README.md #include using namespace drogon; diff --git a/vendors/drogon/local-api-server/models/model.json b/vendors/local-api-server/models/model.json similarity index 100% rename from vendors/drogon/local-api-server/models/model.json rename to vendors/local-api-server/models/model.json diff --git a/vendors/drogon/local-api-server/plugins/README.md b/vendors/local-api-server/plugins/README.md similarity index 100% rename from vendors/drogon/local-api-server/plugins/README.md rename to vendors/local-api-server/plugins/README.md diff --git a/vendors/drogon/local-api-server/test/CMakeLists.txt b/vendors/local-api-server/test/CMakeLists.txt similarity index 100% rename from vendors/drogon/local-api-server/test/CMakeLists.txt rename to vendors/local-api-server/test/CMakeLists.txt diff --git a/vendors/drogon/local-api-server/test/test_main.cc b/vendors/local-api-server/test/test_main.cc similarity index 100% rename from vendors/drogon/local-api-server/test/test_main.cc rename to vendors/local-api-server/test/test_main.cc diff --git a/vendors/drogon/local-api-server/views/README.md b/vendors/local-api-server/views/README.md similarity index 100% rename from vendors/drogon/local-api-server/views/README.md rename to vendors/local-api-server/views/README.md diff --git a/vendors/simple-reverse-proxy/.gitignore b/vendors/simple-reverse-proxy/.gitignore new file mode 100644 index 0000000..49953a9 --- /dev/null +++ b/vendors/simple-reverse-proxy/.gitignore @@ -0,0 +1,36 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +build +cmake-build-debug +.idea diff --git a/vendors/simple-reverse-proxy/CMakeLists.txt b/vendors/simple-reverse-proxy/CMakeLists.txt new file mode 100644 index 0000000..b78c56c --- /dev/null +++ b/vendors/simple-reverse-proxy/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required (VERSION 3.5) +project(simple_reverse_proxy CXX) + +include(CheckIncludeFileCXX) + +check_include_file_cxx(any HAS_ANY) +check_include_file_cxx(string_view HAS_STRING_VIEW) +if(HAS_ANY AND HAS_STRING_VIEW) + set(CMAKE_CXX_STANDARD 17) +else() + set(CMAKE_CXX_STANDARD 14) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_executable(simple_reverse_proxy main.cc) + +########## +# If you include the drogon source code locally in your project, use this method to add drogon +# add_subdirectory(drogon) +# target_link_libraries(simple_reverse_proxy PRIVATE drogon) +########## + +find_package(Drogon CONFIG REQUIRED) +target_link_libraries(simple_reverse_proxy PRIVATE Drogon::Drogon) + +if(CMAKE_CXX_STANDARD LESS 17) +#With C++14, use boost to support any and string_view + message(STATUS "use c++14") + find_package(Boost 1.61.0 REQUIRED) + target_include_directories(simple_reverse_proxy PRIVATE ${Boost_INCLUDE_DIRS}) +else() + message(STATUS "use c++17") +endif() + +aux_source_directory(controllers CTL_SRC) +aux_source_directory(filters FILTER_SRC) +aux_source_directory(plugins PLUGIN_SRC) +aux_source_directory(models MODEL_SRC) + +file(GLOB SCP_LIST ${CMAKE_CURRENT_SOURCE_DIR}/views/*.csp) +foreach(cspFile ${SCP_LIST}) + message(STATUS "cspFile:" ${cspFile}) + EXEC_PROGRAM(basename ARGS "${cspFile} .csp" OUTPUT_VARIABLE classname) + message(STATUS "view classname:" ${classname}) + ADD_CUSTOM_COMMAND(OUTPUT ${classname}.h ${classname}.cc + COMMAND drogon_ctl + ARGS create view ${cspFile} + DEPENDS ${cspFile} + VERBATIM ) + set(VIEWSRC ${VIEWSRC} ${classname}.cc) +endforeach() + +target_include_directories(simple_reverse_proxy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models) +target_sources(simple_reverse_proxy PRIVATE ${SRC_DIR} ${CTL_SRC} ${FILTER_SRC} ${VIEWSRC} ${PLUGIN_SRC} ${MODEL_SRC}) diff --git a/vendors/simple-reverse-proxy/README.md b/vendors/simple-reverse-proxy/README.md new file mode 100644 index 0000000..cb35d14 --- /dev/null +++ b/vendors/simple-reverse-proxy/README.md @@ -0,0 +1,3 @@ +### An example that shows how to use drogon as an http reverse proxy with a simple round robin. + +This project is created with the drogon_ctl command, please compile it after installing drogon. \ No newline at end of file diff --git a/vendors/simple-reverse-proxy/config.json b/vendors/simple-reverse-proxy/config.json new file mode 100644 index 0000000..f06d810 --- /dev/null +++ b/vendors/simple-reverse-proxy/config.json @@ -0,0 +1,160 @@ +/* This is a JSON format configuration file + */ +{ + /* + //ssl: The global ssl files setting + "ssl": { + "cert": "../../trantor/trantor/tests/server.crt", + "key": "../../trantor/trantor/tests/server.key" + },*/ + "listeners": [{ + //address: Ip address,0.0.0.0 by default + "address": "0.0.0.0", + //port: Port number + "port": 8088, + //https: If true, use https for security, false by default + "https": false + }], + "app": { + //threads_num: The number of IO threads, 1 by default, if the value is set to 0, the number of threads + //is the number of CPU cores + "threads_num": 16, + //enable_session: False by default + "enable_session": false, + "session_timeout": 0, + //session_cookie_key: The cookie key of the session, "JSESSIONID" by default + "session_cookie_key": "JSESSIONID", + //session_max_age: The max age of the session cookie, -1 by default + "session_max_age": -1, + //document_root: Root path of HTTP document, default path is ./ + "document_root": "./", + //home_page: Set the HTML file of the home page, the default value is "index.html" + //If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response + //to the request for "/". + "home_page": "index.html", + //static_file_headers: Headers for static files + /*"static_file_headers": [ + { + "name": "field-name", + "value": "field-value" + } + ],*/ + //upload_path: The path to save the uploaded file. "uploads" by default. + //If the path isn't prefixed with /, ./ or ../, + //it is relative path of document_root path + "upload_path": "uploads", + /* file_types: + * HTTP download file types, the file types supported by drogon + * by default are "html", "js", "css", "xml", "xsl", "txt", "svg", + * "ttf", "otf", "woff2", "woff" , "eot", "png", "jpg", "jpeg", + * "gif", "bmp", "ico", "icns", etc. */ + "file_types": [ + "gif", + "png", + "jpg", + "js", + "css", + "html", + "ico", + "swf", + "xap", + "apk", + "cur", + "xml" + ], + //max_connections: maximum connections number, 100000 by default + "max_connections": 100000, + //max_connections_per_ip: maximum connections number per client,0 by default which means no limit + "max_connections_per_ip": 0, + //Load_dynamic_views: False by default, when set to true, drogon + //compiles and loads dynamically "CSP View Files" in directories defined + //by "dynamic_views_path" + "load_dynamic_views": false, + //dynamic_views_path: If the path isn't prefixed with /, ./ or ../, + //it is relative path of document_root path + "dynamic_views_path": [ + "./views" + ], + //log: Set log output, drogon output logs to stdout by default + "log": { + //log_path: Log file path,empty by default,in which case,logs are output to the stdout + //"log_path": "./", + //logfile_base_name: Log file base name,empty by default which means drogon names logfile as + //drogon.log ... + "logfile_base_name": "", + //log_size_limit: 100000000 bytes by default, + //When the log file size reaches "log_size_limit", the log file is switched. + "log_size_limit": 100000000, + //log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN" + //The TRACE level is only valid when built in DEBUG mode. + "log_level": "DEBUG" + }, + //run_as_daemon: False by default + "run_as_daemon": false, + //handle_sig_term: True by default + "handle_sig_term": true, + //relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting; + "relaunch_on_error": false, + //use_sendfile: True by default, if true, the program + //uses sendfile() system-call to send static files to clients; + "use_sendfile": true, + //use_gzip: True by default, use gzip to compress the response body's content; + "use_gzip": true, + //static_files_cache_time: 5 (seconds) by default, the time in which the static file response is cached, + //0 means cache forever, the negative value means no cache + "static_files_cache_time": 5, + //idle_connection_timeout: Defaults to 60 seconds, the lifetime + //of the connection without read or write + "idle_connection_timeout": 60, + //server_header_field: Set the 'Server' header field in each response sent by drogon, + //empty string by default with which the 'Server' header field is set to "Server: drogon/version string\r\n" + "server_header_field": "", + //enable_server_header: Set true to force drogon to add a 'Server' header to each HTTP response. The default + //value is true. + "enable_server_header": false, + //enable_date_header: Set true to force drogon to add a 'Date' header to each HTTP response. The default + //value is true. + "enable_date_header": false, + //keepalive_requests: Set the maximum number of requests that can be served through one keep-alive connection. + //After the maximum number of requests are made, the connection is closed. + //The default value of 0 means no limit. + "keepalive_requests": 0, + //pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer. + //After the maximum number of requests are made, the connection is closed. + //The default value of 0 means no limit. + "pipelining_requests": 0, + //gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed + //file with the extension ".gz" in the same path and send the compressed file to the client. + //The default value of gzip_static is true. + "gzip_static": true, + //client_max_body_size: Set the maximum body size of HTTP requests received by drogon. The default value is "1M". + //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit. + "client_max_body_size": "1M", + //max_memory_body_size: Set the maximum body size in memory of HTTP requests received by drogon. The default value is "64K" bytes. + //If the body size of a HTTP request exceeds this limit, the body is stored to a temporary file for processing. + //Setting it to "" means no limit. + "client_max_memory_body_size": "64K", + //client_max_websocket_message_size: Set the maximum size of messages sent by WebSocket client. The default value is "128K". + //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit. + "client_max_websocket_message_size": "128K" + }, + //plugins: Define all plugins running in the application + "plugins": [{ + //name: The class name of the plugin + "name": "my_plugin::SimpleReverseProxy", + //dependencies: Plugins that the plugin depends on. It can be commented out + "dependencies": [], + //config: The configuration of the plugin. This json object is the parameter to initialize the plugin. + //It can be commented out + "config": { + // The pipelining depth of HTTP clients. + "pipelining": 32, + "backends": ["http://127.0.0.1:8848"], + "same_client_to_same_backend": false, + //The number of connections created by proxy for each backend in very event loop (IO thread). + "connection_factor": 1 + } + }], + //custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method. + "custom_config": {} +} diff --git a/vendors/simple-reverse-proxy/main.cc b/vendors/simple-reverse-proxy/main.cc new file mode 100644 index 0000000..409e74e --- /dev/null +++ b/vendors/simple-reverse-proxy/main.cc @@ -0,0 +1,10 @@ +#include + +int main() +{ + // Set HTTP listener address and port + drogon::app().loadConfigFile("config.json"); + // Run HTTP framework, the method will block in the internal event loop + drogon::app().run(); + return 0; +} diff --git a/vendors/simple-reverse-proxy/plugins/SimpleReverseProxy.cc b/vendors/simple-reverse-proxy/plugins/SimpleReverseProxy.cc new file mode 100644 index 0000000..26ca89b --- /dev/null +++ b/vendors/simple-reverse-proxy/plugins/SimpleReverseProxy.cc @@ -0,0 +1,100 @@ +/** + * + * @file SimpleReverseProxy.cc + * + */ + +#include "SimpleReverseProxy.h" + +using namespace drogon; +using namespace my_plugin; + +void SimpleReverseProxy::initAndStart(const Json::Value &config) +{ + /// Initialize and start the plugin + if (config.isMember("backends") && config["backends"].isArray()) + { + for (auto &backend : config["backends"]) + { + backendAddrs_.emplace_back(backend.asString()); + } + if (backendAddrs_.empty()) + { + LOG_ERROR << "You must set at least one backend"; + abort(); + } + } + else + { + LOG_ERROR << "Error in configuration"; + abort(); + } + pipeliningDepth_ = config.get("pipelining", 0).asInt(); + sameClientToSameBackend_ = + config.get("same_client_to_same_backend", false).asBool(); + connectionFactor_ = config.get("connection_factor", 1).asInt(); + if (connectionFactor_ == 0 || connectionFactor_ > 100) + { + LOG_ERROR << "invalid number of connection factor"; + abort(); + } + clients_.init( + [this](std::vector &clients, size_t ioLoopIndex) { + clients.resize(backendAddrs_.size() * connectionFactor_); + }); + clientIndex_.init( + [this](size_t &index, size_t ioLoopIndex) { index = ioLoopIndex; }); + drogon::app().registerPreRoutingAdvice([this](const HttpRequestPtr &req, + AdviceCallback &&callback, + AdviceChainCallback &&pass) { + preRouting(req, std::move(callback), std::move(pass)); + }); +} + +void SimpleReverseProxy::shutdown() +{ +} + +void SimpleReverseProxy::preRouting(const HttpRequestPtr &req, + AdviceCallback &&callback, + AdviceChainCallback &&) +{ + size_t index; + auto &clientsVector = *clients_; + if (sameClientToSameBackend_) + { + index = std::hash{}(req->getPeerAddr().ipNetEndian()) % + clientsVector.size(); + index = (index + (++(*clientIndex_)) * backendAddrs_.size()) % + clientsVector.size(); + } + else + { + index = ++(*clientIndex_) % clientsVector.size(); + } + auto &clientPtr = clientsVector[index]; + if (!clientPtr) + { + auto &addr = backendAddrs_[index % backendAddrs_.size()]; + clientPtr = HttpClient::newHttpClient( + addr, trantor::EventLoop::getEventLoopOfCurrentThread()); + clientPtr->setPipeliningDepth(pipeliningDepth_); + } + req->setPassThrough(true); + clientPtr->sendRequest( + req, + [callback = std::move(callback)](ReqResult result, + const HttpResponsePtr &resp) { + if (result == ReqResult::Ok) + { + resp->setPassThrough(true); + callback(resp); + } + else + { + auto errResp = HttpResponse::newHttpResponse(); + errResp->setStatusCode(k500InternalServerError); + callback(errResp); + } + }); +} \ No newline at end of file diff --git a/vendors/simple-reverse-proxy/plugins/SimpleReverseProxy.h b/vendors/simple-reverse-proxy/plugins/SimpleReverseProxy.h new file mode 100644 index 0000000..fd42790 --- /dev/null +++ b/vendors/simple-reverse-proxy/plugins/SimpleReverseProxy.h @@ -0,0 +1,44 @@ +/** + * + * @file SimpleReverseProxy.h + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace my_plugin +{ +class SimpleReverseProxy : public drogon::Plugin +{ + public: + SimpleReverseProxy() + { + } + + /// This method must be called by drogon to initialize and start the plugin. + /// It must be implemented by the user. + void initAndStart(const Json::Value &config) override; + + /// This method must be called by drogon to shutdown the plugin. + /// It must be implemented by the user. + void shutdown() override; + + private: + // Create 'connectionFactor_' HTTP clients for every backend in every IO + // event loop. + drogon::IOThreadStorage> clients_; + drogon::IOThreadStorage clientIndex_{0}; + std::vector backendAddrs_; + bool sameClientToSameBackend_{false}; + size_t pipeliningDepth_{0}; + void preRouting(const drogon::HttpRequestPtr &, + drogon::AdviceCallback &&, + drogon::AdviceChainCallback &&); + size_t connectionFactor_{1}; +}; +} // namespace my_plugin