diff --git a/modules/drogon/SCsub b/modules/drogon/SCsub index dfd89c4..bff34a3 100644 --- a/modules/drogon/SCsub +++ b/modules/drogon/SCsub @@ -5,6 +5,7 @@ Import("env") env_mod.core_sources = [] +env_mod.add_source_files(env_mod.core_sources, "*.cpp") env_mod.add_source_files(env_mod.core_sources, "drogon/lib/src/*.cc") env_mod.add_source_files(env_mod.core_sources, "drogon/lib/inc/http/*.cc") env_mod.add_source_files(env_mod.core_sources, "drogon/lib/src/ssl_funcs/*.cc") diff --git a/modules/drogon/handler_instance.cpp b/modules/drogon/handler_instance.cpp new file mode 100644 index 0000000..77b6a7c --- /dev/null +++ b/modules/drogon/handler_instance.cpp @@ -0,0 +1,13 @@ +#include "handler_instance.h" + +#include "request.h" +#include "core/object.h" + +DHandlerInstance::DHandlerInstance() { + instance = nullptr; +} + +DHandlerInstance::DHandlerInstance(std::function p_handler_func, Object *p_instance) { + handler_func = p_handler_func; + instance = p_instance; +} \ No newline at end of file diff --git a/modules/drogon/handler_instance.h b/modules/drogon/handler_instance.h new file mode 100644 index 0000000..54ae810 --- /dev/null +++ b/modules/drogon/handler_instance.h @@ -0,0 +1,17 @@ +#ifndef DHANDLER_INSTANCE_H +#define DHANDLER_INSTANCE_H + +#include + +class Object; +class DRequest; + +struct DHandlerInstance { + std::function handler_func; + Object *instance; + + DHandlerInstance(); + DHandlerInstance(std::function p_handler_func, Object *p_instance = nullptr); +}; + +#endif \ No newline at end of file diff --git a/modules/drogon/request.cpp b/modules/drogon/request.cpp new file mode 100644 index 0000000..2c43b3b --- /dev/null +++ b/modules/drogon/request.cpp @@ -0,0 +1,309 @@ +#include "request.h" + +#include "web_application.h" + +void DRequest::compile_body() { + compiled_body.reserve(body.size() + head.size() + 13 + 14 + 15); + + //13 + compiled_body += "" + ""; + + compiled_body += head; + + //14 + compiled_body += "" + ""; + + compiled_body += body; + compiled_body += footer; + + //15 + compiled_body += "" + ""; + + response->setBody(compiled_body); +} + +void DRequest::compile_and_send_body() { + compile_body(); + send(); +} + +void DRequest::next_stage() { + if (current_middleware_index == (*middleware_stack).size()) { + handler_instance.handler_func(handler_instance.instance, this); + return; + } + + const DHandlerInstance &hi = (*middleware_stack)[current_middleware_index++]; + + hi.handler_func(hi.instance, this); +} + +void DRequest::send() { + //if (connection_closed) { + // DRequestPool::return_request(this); + // return; + //} + + if (http_parser->isKeepAlive()) { + response->addHeadValue("Connection", "Keep-Alive"); + + std::string result = response->getResult(); + + session->send(result.c_str(), result.size()); + } else { + response->addHeadValue("Connection", "Close"); + + std::string result = response->getResult(); + + HttpSession::Ptr lsession = session; + + session->send(result.c_str(), result.size(), [lsession]() { lsession->postShutdown(); }); + } + + DRequestPool::return_request(this); +} + +void DRequest::send_file(const std::string &p_file_path) { + //if (connection_closed) { + // DRequestPool::return_request(this); + // return; + //} + + file_path = p_file_path; + + FILE *f = fopen(file_path.c_str(), "rb"); + + if (!f) { + printf("send_file: Error: Download: file doesn't exists! %s\n", file_path.c_str()); + + return; + } + + fseek(f, 0, SEEK_END); + file_size = ftell(f); + fclose(f); + + response->addHeadValue("Connection", "Close"); + std::string result = "HTTP/1.1 200 OK\r\nConnection: Close\r\n\r\n"; + + application->register_request_update(this); + + session->send(result.c_str(), result.size(), [this]() { this->_file_chunk_sent(); }); +} + +void DRequest::send_error(int error_code) { + application->send_error(error_code, this); +} + +void DRequest::reset() { + application = nullptr; + http_parser = nullptr; + session = nullptr; + current_middleware_index = 0; + middleware_stack = nullptr; + _path_stack.clear(); + _path_stack_pointer = 0; + file_size = 0; + current_file_progress = 0; + connection_closed = false; + + head.clear(); + body.clear(); + footer.clear(); + compiled_body.clear(); + + if (response) + delete response; + + response = new HttpResponse(); +} + +void DRequest::setup_url_stack() { + std::string path = http_parser->getPath(); + + size_t pos = 0; + std::string st; + while ((pos = path.find("/")) != std::string::npos) { + st = path.substr(0, pos); + + if (st.size() != 0) + _path_stack.push_back(st); + + path.erase(0, pos + 1); + } + + if (path.size() != 0) + _path_stack.push_back(path); +} + +std::string DRequest::get_path() const { + std::string path = ""; + + for (uint32_t i = _path_stack_pointer; i < _path_stack.size(); ++i) { + path += _path_stack[i]; + path += "/"; + } + + return path; +} + +const std::string &DRequest::get_path_full() const { + return http_parser->getPath(); +} + +const std::string &DRequest::get_path_segment(const uint32_t i) const { + return _path_stack[i]; +} + +const std::string &DRequest::get_current_path_segment() const { + if (_path_stack_pointer >= _path_stack.size()) { + //for convenience + static const std::string e_str = ""; + return e_str; + } + + return _path_stack[_path_stack_pointer]; +} + +uint32_t DRequest::get_path_segment_count() const { + return _path_stack.size(); +} + +uint32_t DRequest::get_current_segment_index() const { + return _path_stack_pointer; +} + +uint32_t DRequest::get_remaining_segment_count() const { + if (_path_stack_pointer > _path_stack.size()) { + return 0; + } + + return _path_stack.size() - _path_stack_pointer; +} + +void DRequest::pop_path() { + _path_stack_pointer -= 1; +} + +void DRequest::push_path() { + _path_stack_pointer += 1; +} + +void DRequest::update() { + if (file_next) { + file_next = false; + _progress_send_file(); + } +} + +DRequest::DRequest() { + response = nullptr; + + //This value will need benchmarks, 2 MB seems to be just as fast for me as 4 MB, but 1MB is slower + //It is a tradeoff on server memory though, as every active download will consume this amount of memory + //where the file is bigger than this number + file_chunk_size = 1 << 21; //2MB + + reset(); +} + +DRequest::~DRequest() { + delete response; +} + +void DRequest::_progress_send_file() { + if (connection_closed) { + DRequestPool::return_request(this); + return; + } + + if (current_file_progress >= file_size) { + session->postShutdown(); + + DRequestPool::return_request(this); + + return; + } + + FILE *f = fopen(file_path.c_str(), "rb"); + + if (!f) { + printf("Error: Download: In progress file doesn't exists anymore! %s\n", file_path.c_str()); + + application->unregister_request_update(this); + + session->postShutdown(); + + DRequestPool::return_request(this); + + return; + } + + fseek(f, current_file_progress, SEEK_SET); + + long nfp = current_file_progress + file_chunk_size; + + long csize = file_chunk_size; + if (nfp >= file_size) { + csize = (file_size - current_file_progress); + } + + body.resize(csize); + + fread(&body[0], 1, csize, f); + fclose(f); + + current_file_progress = nfp; + + session->send(body.c_str(), body.size(), [this]() { this->_file_chunk_sent(); }); +} + +void DRequest::_file_chunk_sent() { + file_next = true; +} + +DRequest *DRequestPool::get_request() { + _mutex.lock(); + + DRequest *request; + + if (_requests.size() == 0) { + _mutex.unlock(); + + request = new DRequest(); + + return request; + } + + request = _requests[_requests.size() - 1]; + _requests.pop_back(); + + _mutex.unlock(); + + request->reset(); + + return request; +} + +void DRequestPool::return_request(DRequest *request) { + _mutex.lock(); + _requests.push_back(request); + _mutex.unlock(); +} + +DRequestPool::DRequestPool() { +} + +DRequestPool::~DRequestPool() { + for (uint32_t i = 0; i < _requests.size(); ++i) { + delete _requests[i]; + } + + _requests.clear(); +} + +std::mutex DRequestPool::_mutex; +std::vector DRequestPool::_requests; diff --git a/modules/drogon/request.h b/modules/drogon/request.h new file mode 100644 index 0000000..f2d53b5 --- /dev/null +++ b/modules/drogon/request.h @@ -0,0 +1,83 @@ +#ifndef DREQUEST_H +#define REQUEST_H + +#include +#include + +#include +#include + +#include "handler_instance.h" + +class DWebApplication; + +class DRequest { +public: + HTTPParser::Ptr http_parser; + HttpSession::Ptr session; + HttpResponse *response; + DWebApplication *application; + + uint32_t current_middleware_index; + DHandlerInstance handler_instance; + std::vector *middleware_stack; + + std::string head; + std::string body; + std::string footer; + std::string compiled_body; + + std::string file_path; + long file_size; + long current_file_progress; + long file_chunk_size; + bool file_next; + + bool connection_closed; + + void compile_body(); + void compile_and_send_body(); + void next_stage(); + void send(); + void send_file(const std::string &p_file_path); + void send_error(int error_code); + void reset(); + + void setup_url_stack(); + std::string get_path() const; + const std::string &get_path_full() const; + const std::string &get_path_segment(const uint32_t i) const; + const std::string &get_current_path_segment() const; + uint32_t get_path_segment_count() const; + uint32_t get_current_segment_index() const; + uint32_t get_remaining_segment_count() const; + void pop_path(); + void push_path(); + + void update(); + + DRequest(); + ~DRequest(); + +protected: + void _progress_send_file(); + void _file_chunk_sent(); + + std::vector _path_stack; + uint32_t _path_stack_pointer; +}; + +class DRequestPool { +public: + static DRequest *get_request(); + static void return_request(DRequest *request); + + DRequestPool(); + ~DRequestPool(); + +protected: + static std::mutex _mutex; + static std::vector _requests; +}; + +#endif \ No newline at end of file diff --git a/modules/drogon/web_application.cpp b/modules/drogon/web_application.cpp new file mode 100644 index 0000000..b34ed54 --- /dev/null +++ b/modules/drogon/web_application.cpp @@ -0,0 +1,140 @@ +#include "web_application.h" + +#include +#include + +#include "request.h" + +#include "core/file_cache.h" + +#include + +#include +#include + +void DWebApplication::load_settings() { +} + +void DWebApplication::setup_routes() { + default_error_handler_func = DWebApplication::default_fallback_error_handler; + + error_handler_map[404] = DWebApplication::default_404_error_handler; +} + +void DWebApplication::setup_middleware() { + middlewares.push_back(DHandlerInstance([this](Object *instance, DRequest *request){ this->default_routing_middleware(instance, request); })); +} + +void DWebApplication::default_routing_middleware(Object *instance, DRequest *request) { + std::string path = request->http_parser->getPath(); + + if (FileCache::get_singleton()->wwwroot_has_file(path)) { + send_file(path, request); + + return; + } + + DHandlerInstance handler_data; + + //std::function func; + + //if (path == "/") { + if (request->get_path_segment_count() == 0) { + //quick shortcut + handler_data = index_func; + } else { + const std::string main_route = request->get_current_path_segment(); + + handler_data = main_route_map[main_route]; + + request->push_path(); + } + + if (!handler_data.handler_func) { + send_error(404, request); + + return; + } + + request->handler_instance = handler_data; + request->next_stage(); +} + +void DWebApplication::default_fallback_error_handler(int error_code, DRequest *request) { + request->response->setBody(default_generic_error_body); + request->send(); +} + +void DWebApplication::default_404_error_handler(int error_code, DRequest *request) { + request->response->setBody(default_error_404_body); + request->send(); +} + +void DWebApplication::handle_request(DRequest *request) { + request->middleware_stack = &middlewares; + + //note that middlewares handle the routing -> DWebApplication::default_routing_middleware by default + request->next_stage(); +} + +void DWebApplication::send_error(int error_code, DRequest *request) { + std::function func = error_handler_map[error_code]; + + if (!func) { + default_error_handler_func(error_code, request); + return; + } + + func(error_code, request); +} + +void DWebApplication::send_file(const std::string &path, DRequest *request) { + std::string fp = FileCache::get_singleton()->wwwroot + path; + + request->send_file(fp); +} + +void DWebApplication::migrate() { +} + +void DWebApplication::register_request_update(DRequest *request) { + std::lock_guard lock(_update_registered_requests_mutex); + + _update_registered_requests.push_back(request); +} +void DWebApplication::unregister_request_update(DRequest *request) { + std::lock_guard lock(_update_registered_requests_mutex); + + std::size_t s = _update_registered_requests.size(); + for (std::size_t i = 0; i < s; ++i) { + DRequest *r = _update_registered_requests[i]; + + if (r == request) { + _update_registered_requests[i] = _update_registered_requests[s - 1]; + + _update_registered_requests.pop_back(); + + return; + } + } +} + +void DWebApplication::update() { + for (std::size_t i = 0; i < _update_registered_requests.size(); ++i) { + DRequest *r = _update_registered_requests[i]; + + r->update(); + } +} + +DWebApplication::DWebApplication() { +} + +DWebApplication::~DWebApplication() { + main_route_map.clear(); + error_handler_map.clear(); + middlewares.clear(); +} + +std::string DWebApplication::default_error_404_body = "404 :("; +std::string DWebApplication::default_generic_error_body = "Internal server error! :("; diff --git a/modules/drogon/web_application.h b/modules/drogon/web_application.h new file mode 100644 index 0000000..dd3bcc8 --- /dev/null +++ b/modules/drogon/web_application.h @@ -0,0 +1,56 @@ +#ifndef DWEB_APPLICATION_H +#define WEB_APPLICATION_H + +#include "core/object.h" +#include +#include +#include +#include + +#include + +#include "handler_instance.h" + +class DRequest; + +class DWebApplication { +public: + static std::string default_error_404_body; + static std::string default_generic_error_body; + + void handle_request(DRequest *request); + void send_error(int error_code, DRequest *request); + void send_file(const std::string &path, DRequest *request); + + static void default_fallback_error_handler(int error_code, DRequest *request); + static void default_404_error_handler(int error_code, DRequest *request); + + virtual void load_settings(); + virtual void setup_routes(); + virtual void setup_middleware(); + + void default_routing_middleware(Object *instance, DRequest *request); + + virtual void migrate(); + + void register_request_update(DRequest *request); + void unregister_request_update(DRequest *request); + void update(); + + DWebApplication(); + virtual ~DWebApplication(); + +public: + DHandlerInstance index_func; + std::map main_route_map; + std::vector middlewares; + + std::map > error_handler_map; + std::function default_error_handler_func; + +protected: + std::mutex _update_registered_requests_mutex; + std::vector _update_registered_requests; +}; + +#endif \ No newline at end of file