From 54d82b8c663dc47c7d6cba453760f1420ba4f916 Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Tue, 4 Feb 2025 02:03:27 -0500 Subject: [PATCH] cleanup of source + build system * seperated most parts into headers & source files * seperated anthracite into libanthracite and anthracite-bin for modules * built demo webAPI module * rewrote some code to do things in the * changed cmakelists to not build in src directory --- src/.gitignore | 1 + src/CMakeLists.txt | 24 +++- src/anthracite_main.cpp | 81 ++++++++++++++ src/anthracite_main.hpp | 6 + src/api_main.cpp | 105 ++++++++++++++++++ src/backends/{backend.cpp => backend.hpp} | 13 ++- src/backends/file_backend.cpp | 60 ++++++---- src/backends/file_backend.hpp | 22 ++++ src/{build => build_supp}/error_gen.py | 2 +- src/build_supp/version.cpp | 3 + src/{build => build_supp}/version.sh | 0 src/{build => build_supp}/version.txt | 0 src/datastructures/smart_map.cpp | 91 --------------- src/file_main.cpp | 10 ++ src/http/{constants.cpp => constants.hpp} | 99 +++++++++-------- .../{header_query.cpp => header_query.hpp} | 11 +- src/http/http.hpp | 8 -- src/http/http_response.cpp | 61 ---------- src/http/{http_request.cpp => request.cpp} | 58 ++++------ src/http/request.hpp | 39 +++++++ src/http/response.cpp | 58 ++++++++++ src/http/response.hpp | 31 ++++++ src/log/log.cpp | 24 ++++ src/log/log.hpp | 53 +++++++++ src/main.cpp | 79 ------------- src/{socket.cpp => socket.hpp} | 46 ++++---- 26 files changed, 607 insertions(+), 378 deletions(-) create mode 100644 src/anthracite_main.cpp create mode 100644 src/anthracite_main.hpp create mode 100644 src/api_main.cpp rename src/backends/{backend.cpp => backend.hpp} (57%) create mode 100644 src/backends/file_backend.hpp rename src/{build => build_supp}/error_gen.py (98%) create mode 100644 src/build_supp/version.cpp rename src/{build => build_supp}/version.sh (100%) rename src/{build => build_supp}/version.txt (100%) delete mode 100644 src/datastructures/smart_map.cpp create mode 100644 src/file_main.cpp rename src/http/{constants.cpp => constants.hpp} (72%) rename src/http/{header_query.cpp => header_query.hpp} (88%) delete mode 100644 src/http/http.hpp delete mode 100644 src/http/http_response.cpp rename src/http/{http_request.cpp => request.cpp} (71%) create mode 100644 src/http/request.hpp create mode 100644 src/http/response.cpp create mode 100644 src/http/response.hpp create mode 100644 src/log/log.cpp create mode 100644 src/log/log.hpp delete mode 100644 src/main.cpp rename src/{socket.cpp => socket.hpp} (52%) diff --git a/src/.gitignore b/src/.gitignore index 74fec20..7211b0b 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -10,3 +10,4 @@ install_manifest.txt compile_commands.json CTestTestfile.cmake _deps +build/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7bdd378..719ff21 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,15 @@ cmake_minimum_required(VERSION 3.10) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED True) -set(CMAKE_CXX_FLAGS_RELEASE "-O3") - project(anthracite) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + + add_custom_target(build-supplemental - COMMAND cd ./build && ./version.sh && python3 ./error_gen.py + COMMAND cd ../build_supp && ./version.sh && python3 ./error_gen.py + COMMAND cp -r ../www . ) add_custom_target(lint @@ -32,5 +34,15 @@ add_custom_target(run WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} ) -add_executable(anthracite-bin main.cpp) +FILE(GLOB SOURCES **/*.cpp) + +add_library(anthracite anthracite_main.cpp ${SOURCES}) + +add_executable(anthracite-bin file_main.cpp) +target_link_libraries(anthracite-bin anthracite) + +add_executable(anthracite-api-bin api_main.cpp) +target_link_libraries(anthracite-api-bin anthracite) + add_dependencies(anthracite-bin build-supplemental) +add_dependencies(anthracite-bin anthracite) diff --git a/src/anthracite_main.cpp b/src/anthracite_main.cpp new file mode 100644 index 0000000..d13daf0 --- /dev/null +++ b/src/anthracite_main.cpp @@ -0,0 +1,81 @@ +#include "backends/file_backend.hpp" +#include "./anthracite_main.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "./log/log.hpp" +#include "./socket.hpp" + +using namespace anthracite; + +void log_request_and_response(http::request& req, std::unique_ptr& resp); + +constexpr int default_port = 80; +constexpr int max_worker_threads = 128; + +void handle_client(socket::anthracite_socket s, backends::backend& b, backends::file_backend& fb, std::mutex& thread_wait_mutex, std::condition_variable& thread_wait_condvar, int& active_threads) +{ + while (true) { + std::string raw_request = s.recv_message(http::HEADER_BYTES); + if (raw_request == "") { + break; + } + http::request req(raw_request, s.get_client_ip()); + std::unique_ptr resp = req.is_supported_version() ? b.handle_request(req) : fb.handle_error(http::status_codes::HTTP_VERSION_NOT_SUPPORTED); + log_request_and_response(req, resp); + std::string header = resp->header_to_string(); + s.send_message(header); + s.send_message(resp->content()); + resp.reset(); + if (req.close_connection()) { + break; + } + } + s.close_conn(); + { + std::lock_guard lock(thread_wait_mutex); + active_threads--; + } + thread_wait_condvar.notify_one(); +} + +//int main(int argc, char** argv) +int anthracite_main(int argc, char** argv, backends::backend& be) +{ + log::logger.initialize(log::LOG_LEVEL_INFO); + auto args = std::span(argv, size_t(argc)); + int port_number = default_port; + + if (argc > 1) { + port_number = atoi(args[1]); + } + log::verbose << "Initializing Anthracite" << std::endl; + socket::anthracite_socket s(port_number); + backends::file_backend fb(argc > 2 ? args[2] : "./www"); + log::verbose << "Initialization Complete" << std::endl; + log::info << "Listening for HTTP connections on port " << port_number << std::endl; + + int active_threads = 0; + std::mutex thread_wait_mutex; + std::condition_variable thread_wait_condvar; + + while (true) { + s.wait_for_conn(); + std::unique_lock lock(thread_wait_mutex); + thread_wait_condvar.wait(lock, [active_threads] { return active_threads < max_worker_threads; }); + active_threads++; + std::thread(handle_client, s, std::ref(be), std::ref(fb), std::ref(thread_wait_mutex), std::ref(thread_wait_condvar), std::ref(active_threads)).detach(); + } + + exit(0); +} + +void log_request_and_response(http::request& req, std::unique_ptr& resp) +{ + log::info << "[" << resp->status_code() << " " + http::status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http::reverse_method_map.find(req.get_method())->second + " " + req.path() << std::endl; +} diff --git a/src/anthracite_main.hpp b/src/anthracite_main.hpp new file mode 100644 index 0000000..a5c2aba --- /dev/null +++ b/src/anthracite_main.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "backends/backend.hpp" + +using namespace anthracite; +int anthracite_main(int argc, char** argv, backends::backend& be); diff --git a/src/api_main.cpp b/src/api_main.cpp new file mode 100644 index 0000000..6fdc284 --- /dev/null +++ b/src/api_main.cpp @@ -0,0 +1,105 @@ +#include "./anthracite_main.hpp" +#include "backends/backend.hpp" +#include "http/constants.hpp" +#include +#include +#include +#include +#include +#include + +using namespace anthracite; + + using CallbackType = std::unique_ptr (*)(http::request&); +class api_backend : public backends::backend { + + class RouteNode { + public: + + std::optional callback; + + RouteNode() : callback(std::nullopt) {} + std::unordered_map routes; + }; + + RouteNode root; + + std::unique_ptr default_route(http::request& req) { + std::unique_ptr resp = std::make_unique(); + + resp->add_body("Not Found"); + resp->add_header(http::header("Content-Type", "application/json")); + resp->add_status(http::status_codes::NOT_FOUND); + + return resp; + } + + std::unique_ptr find_handler(http::request& req) { + std::string filename = req.path().substr(1); + std::vector result; + std::stringstream ss (filename); + std::string item; + + RouteNode* cur = &root; + while (getline (ss, item, '/')) { + if (cur->routes.find(item) == cur->routes.end()) { + if (cur->routes.find("*") == cur->routes.end()) { + break; + } else { + cur = &cur->routes["*"]; + } + } else { + cur = &cur->routes[item]; + } + } + + if (cur->callback.has_value()) { + return cur->callback.value()(req); + } else { + return default_route(req); + } + + + } + + std::unique_ptr handle_request(http::request& req) override { + return find_handler(req); + } + + public: + + api_backend() { + root.routes = std::unordered_map(); + } + + void register_endpoint(std::string pathspec, CallbackType callback) { + std::vector result; + std::stringstream ss (pathspec); + std::string item; + + RouteNode* cur = &root; + while (getline (ss, item, '/')) { + cur->routes[item] = RouteNode{}; + cur = &cur->routes[item]; + } + + cur->callback = callback; + } +}; + +std::unique_ptr handle_request(http::request& req) { + std::unique_ptr resp = std::make_unique(); + + resp->add_body(R"({"user": "endpoint"}")"); + resp->add_header(http::header("Content-Type", "application/json")); + resp->add_status(http::status_codes::OK); + + return resp; +} + +int main(int argc, char** argv) { + auto args = std::span(argv, size_t(argc)); + api_backend ab; + ab.register_endpoint("users/*", handle_request); + anthracite_main(argc, argv, ab); +} diff --git a/src/backends/backend.cpp b/src/backends/backend.hpp similarity index 57% rename from src/backends/backend.cpp rename to src/backends/backend.hpp index 3864a1b..96d344d 100644 --- a/src/backends/backend.cpp +++ b/src/backends/backend.hpp @@ -1,7 +1,10 @@ -#include +#pragma once -#include "../http/http_request.cpp" -#include "../http/http_response.cpp" +#include +#include "../http/request.hpp" +#include "../http/response.hpp" + +namespace anthracite::backends { class backend { public: @@ -11,5 +14,7 @@ public: backend& operator = (backend const&) = delete; backend(backend&&) = delete; backend& operator=(backend&&) = delete; - virtual std::unique_ptr handle_request(http_request& req) = 0; + virtual std::unique_ptr handle_request(http::request& req) = 0; +}; + }; diff --git a/src/backends/file_backend.cpp b/src/backends/file_backend.cpp index e20ae8c..5166a05 100644 --- a/src/backends/file_backend.cpp +++ b/src/backends/file_backend.cpp @@ -1,26 +1,40 @@ +#include "./file_backend.hpp" +#include "../log/log.hpp" +#include #include -#include -#include "backend.cpp" -class file_backend : public backend { -private: - std::unordered_map file_cache; - std::string file_dir; + +namespace anthracite::backends { + - std::unique_ptr handle_request_cache(http_request& req) { + std::unique_ptr file_backend::handle_request_cache(http::request& req) { std::string filename = req.path() == "/" ? "/index.html" : req.path(); filename = file_dir + filename; auto file_info = file_cache.find(filename); - int status = http_status_codes::OK; + int status = http::status_codes::OK; if (file_info == file_cache.end()) { - return handle_error(http_status_codes::NOT_FOUND); + return handle_error(http::status_codes::NOT_FOUND); } - return std::make_unique(file_info->second, filename, status); + std::unique_ptr resp = std::make_unique(); + + std::string file_extension = file_info->first.substr(file_info->first.rfind('.') + 1); + std::string content_type = "text/html"; + + auto mime_type = http::mime_types.find(file_extension); + if (mime_type != http::mime_types.end()) { + content_type = mime_type->second; + } + + resp->add_body_ref(&file_info->second); + resp->add_status(http::status_codes::OK); + resp->add_header(http::header("Content-Type", content_type), false); + + return resp; } - void populate_cache_dir(std::string dir) { + void file_backend::populate_cache_dir(std::string dir) { std::filesystem::recursive_directory_iterator cur = begin(std::filesystem::recursive_directory_iterator(dir)); std::filesystem::recursive_directory_iterator fin = end(std::filesystem::recursive_directory_iterator(dir)); @@ -31,37 +45,43 @@ private: std::ifstream stream(filename); buffer << stream.rdbuf(); file_cache[filename] = buffer.str(); - std::cout << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl; + log::verbose << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl; ++cur; } } - void populate_cache() { + void file_backend::populate_cache() { populate_cache_dir(file_dir); populate_cache_dir("./error_pages/"); } -public: - file_backend(std::string dir = "./www") : file_dir(std::move(dir)) { + file_backend::file_backend(std::string dir) : file_dir(std::move(dir)) { populate_cache(); } - std::unique_ptr handle_request(http_request& req) override { + std::unique_ptr file_backend::handle_request(http::request& req) { return handle_request_cache(req); } - std::unique_ptr handle_error(const http_status_codes& error) { + std::unique_ptr file_backend::handle_error(const http::status_codes& error) { std::string filename = "./error_pages/" + std::to_string(error) + ".html"; auto file_info = file_cache.find(filename); - http_status_codes status = error; + http::status_codes status = error; if (file_info == file_cache.end()) { - status = http_status_codes::NOT_FOUND; + status = http::status_codes::NOT_FOUND; filename = "./error_pages/404.html"; file_info = file_cache.find(filename); } - return std::make_unique(file_info->second, filename, status); + std::unique_ptr resp = std::make_unique(); + + resp->add_body_ref(&file_info->second); + resp->add_status(error); + resp->add_header(http::header("Content-Type", "text/html"), false); + + return resp; } + }; diff --git a/src/backends/file_backend.hpp b/src/backends/file_backend.hpp new file mode 100644 index 0000000..955d394 --- /dev/null +++ b/src/backends/file_backend.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "backend.hpp" + +namespace anthracite::backends { + +class file_backend : public backend { +private: + std::unordered_map file_cache; + std::string file_dir; + + std::unique_ptr handle_request_cache(http::request& req); + void populate_cache_dir(std::string dir); + void populate_cache(); +public: + file_backend(std::string dir = "./www") ; + + std::unique_ptr handle_request(http::request& req) override; + std::unique_ptr handle_error(const http::status_codes& error); +}; + +}; diff --git a/src/build/error_gen.py b/src/build_supp/error_gen.py similarity index 98% rename from src/build/error_gen.py rename to src/build_supp/error_gen.py index e1ece89..4c56751 100644 --- a/src/build/error_gen.py +++ b/src/build_supp/error_gen.py @@ -66,7 +66,7 @@ error_codes = { } -error_dir = '../error_pages' +error_dir = '../build/error_pages' os.makedirs(error_dir, exist_ok=True) for code, title in error_codes.items(): diff --git a/src/build_supp/version.cpp b/src/build_supp/version.cpp new file mode 100644 index 0000000..590ee4e --- /dev/null +++ b/src/build_supp/version.cpp @@ -0,0 +1,3 @@ +#include +const std::string ANTHRACITE_VERSION_STRING = "0.2.0"; +const std::string ANTHRACITE_FULL_VERSION_STRING = "Anthracite/0.2.0"; diff --git a/src/build/version.sh b/src/build_supp/version.sh similarity index 100% rename from src/build/version.sh rename to src/build_supp/version.sh diff --git a/src/build/version.txt b/src/build_supp/version.txt similarity index 100% rename from src/build/version.txt rename to src/build_supp/version.txt diff --git a/src/datastructures/smart_map.cpp b/src/datastructures/smart_map.cpp deleted file mode 100644 index 87c0bcc..0000000 --- a/src/datastructures/smart_map.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * - * smart_map.cpp - * ------------- - * Class that wraps unordered_map. Chooses whether to do O(1) hash lookup - * or O(N) linear lookup based on benchmark. This is done as O(smallN) can - * be faster than O(1) - * - */ - -#include -#include -#include -#include -#include -#include -#include - -constexpr int benchmark_loops = 100; -constexpr int SEED = 570; - -template -class smart_map { - private: - bool use_hmap = false; - std::unordered_map hmap; - - double assess_hmap(const std::vector> check) { - const auto& start = std::chrono::high_resolution_clock::now(); - for(int i = 0; i < benchmark_loops; i++) { - for(const auto& check_item : check) { - assert(check_item.second == hmap[check_item.first]); - } - } - const auto& end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration(end - start).count(); - return duration; - } - - double assess_vmap(const std::vector> check) { - const auto& start = std::chrono::high_resolution_clock::now(); - for(int i = 0; i < benchmark_loops; i++) { - for(const auto& check_item : check) { - for(const auto& item : hmap) { - if(check_item.first == item.first) { - assert(check_item.second == item.second); - } - } - } - } - const auto& end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration(end - start).count(); - return duration; - } - - public: - smart_map () = default; - - void assess_datastructure() { - std::vector> vals(hmap.begin(), hmap.end()); - std::shuffle(vals.begin(), vals.end(), std::default_random_engine(SEED)); - use_hmap = assess_hmap(vals) > assess_vmap(vals); - } - - bool will_use_hmap() { - return use_hmap; - } - - ValueType* get(const KeyType& key) { - if(use_hmap) { - if(hmap.find(key) != hmap.end()) { - return &hmap[key]; - } else { - return NULL; - } - } - - for(auto& item : hmap) { - if(item.first == key) { - std::string& ref = item.second; - return &ref; - } - } - - return NULL; - } - - void insert(const KeyType key, const ValueType value) { - hmap[key] = value; - } -}; diff --git a/src/file_main.cpp b/src/file_main.cpp new file mode 100644 index 0000000..5ffc071 --- /dev/null +++ b/src/file_main.cpp @@ -0,0 +1,10 @@ +#include "./anthracite_main.hpp" +#include "backends/file_backend.hpp" + +using namespace anthracite; + +int main(int argc, char** argv) { + auto args = std::span(argv, size_t(argc)); + backends::file_backend fb(argc > 2 ? args[2] : "./www"); + anthracite_main(argc, argv, fb); +} diff --git a/src/http/constants.cpp b/src/http/constants.hpp similarity index 72% rename from src/http/constants.cpp rename to src/http/constants.hpp index 43880fe..2cf61eb 100644 --- a/src/http/constants.cpp +++ b/src/http/constants.hpp @@ -1,9 +1,13 @@ +#pragma once + #include #include -constexpr int HTTP_HEADER_BYTES = 8190; +namespace anthracite::http { -enum http_method { +constexpr int HEADER_BYTES = 8190; + +enum method { GET, POST, DELETE, @@ -24,7 +28,7 @@ enum http_method { UNKNOWN }; -enum http_status_codes { +enum status_codes { CONTINUE = 100, SWITCHING_PROTOCOLS = 101, PROCESSING = 102, @@ -90,49 +94,49 @@ enum http_status_codes { NETWORK_AUTHENTICATION_REQUIRED = 511 }; -static std::unordered_map const http_method_map = { - { "GET", http_method::GET }, - { "POST", http_method::POST }, - { "DELETE", http_method::DELETE }, - { "PUT", http_method::PUT }, - { "PATCH", http_method::PATCH }, - { "HEAD", http_method::HEAD }, - { "OPTIONS", http_method::OPTIONS }, - { "CONNECT", http_method::CONNECT }, - { "TRACE", http_method::TRACE }, - { "COPY", http_method::COPY }, - { "LINK", http_method::LINK }, - { "UNLINK", http_method::UNLINK }, - { "PURGE", http_method::PURGE }, - { "LOCK", http_method::LOCK }, - { "UNLOCK", http_method::UNLOCK }, - { "PROPFIND", http_method::PROPFIND }, - { "VIEW", http_method::VIEW }, - { "UNKNOWN", http_method::UNKNOWN } +static std::unordered_map const method_map = { + { "GET", method::GET }, + { "POST", method::POST }, + { "DELETE", method::DELETE }, + { "PUT", method::PUT }, + { "PATCH", method::PATCH }, + { "HEAD", method::HEAD }, + { "OPTIONS", method::OPTIONS }, + { "CONNECT", method::CONNECT }, + { "TRACE", method::TRACE }, + { "COPY", method::COPY }, + { "LINK", method::LINK }, + { "UNLINK", method::UNLINK }, + { "PURGE", method::PURGE }, + { "LOCK", method::LOCK }, + { "UNLOCK", method::UNLOCK }, + { "PROPFIND", method::PROPFIND }, + { "VIEW", method::VIEW }, + { "UNKNOWN", method::UNKNOWN } }; -static std::unordered_map const http_reverse_method_map = { - { http_method::GET, "GET" }, - { http_method::POST, "POST" }, - { http_method::DELETE, "DELETE" }, - { http_method::PUT, "PUT" }, - { http_method::PATCH, "PATCH" }, - { http_method::HEAD, "HEAD" }, - { http_method::OPTIONS, "OPTIONS" }, - { http_method::CONNECT, "CONNECT" }, - { http_method::TRACE, "TRACE" }, - { http_method::COPY, "COPY" }, - { http_method::LINK, "LINK" }, - { http_method::UNLINK, "UNLINK" }, - { http_method::PURGE, "PURGE" }, - { http_method::LOCK, "LOCK" }, - { http_method::UNLOCK, "UNLOCK" }, - { http_method::PROPFIND, "PROPFIND" }, - { http_method::VIEW, "VIEW" }, - { http_method::UNKNOWN, "UNKNOWN" } +static std::unordered_map const reverse_method_map = { + { method::GET, "GET" }, + { method::POST, "POST" }, + { method::DELETE, "DELETE" }, + { method::PUT, "PUT" }, + { method::PATCH, "PATCH" }, + { method::HEAD, "HEAD" }, + { method::OPTIONS, "OPTIONS" }, + { method::CONNECT, "CONNECT" }, + { method::TRACE, "TRACE" }, + { method::COPY, "COPY" }, + { method::LINK, "LINK" }, + { method::UNLINK, "UNLINK" }, + { method::PURGE, "PURGE" }, + { method::LOCK, "LOCK" }, + { method::UNLOCK, "UNLOCK" }, + { method::PROPFIND, "PROPFIND" }, + { method::VIEW, "VIEW" }, + { method::UNKNOWN, "UNKNOWN" } }; -static std::unordered_map const http_status_map = { +static std::unordered_map const status_map = { { 100, "CONTINUE" }, { 101, "SWITCHING PROTOCOLS" }, { 200, "OK" }, @@ -196,6 +200,7 @@ static std::unordered_map const mime_types = { { "css", "text/css" }, { "js", "application/javascript" }, + { "json", "application/json" }, { "pdf", "application/pdf" }, { "ico", "image/x-icon" }, @@ -212,13 +217,13 @@ static std::unordered_map const mime_types = { { "wmv", "video/x-ms-wmv" }, }; -enum http_version { HTTP_0_9, +enum version { HTTP_0_9, HTTP_1_0, HTTP_1_1, HTTP_2_0, HTTP_3_0 }; -static std::unordered_map const http_version_map = { +static std::unordered_map const version_map = { // This is because HTTP 0.9 didn't specify version in the header { "", HTTP_0_9 }, { "HTTP/0.9", HTTP_0_9 }, @@ -228,7 +233,7 @@ static std::unordered_map const http_version_map = { { "HTTP/3.0", HTTP_3_0 } }; -static std::unordered_map const http_reverse_version_map = { +static std::unordered_map const reverse_version_map = { { HTTP_0_9, "HTTP/0.9" }, { HTTP_1_0, "HTTP/1.0" }, { HTTP_1_1, "HTTP/1.1" }, @@ -236,6 +241,4 @@ static std::unordered_map const http_reverse_version_ { HTTP_3_0, "HTTP/3.0" } }; - - - +}; diff --git a/src/http/header_query.cpp b/src/http/header_query.hpp similarity index 88% rename from src/http/header_query.cpp rename to src/http/header_query.hpp index eb37f5e..1afb118 100644 --- a/src/http/header_query.cpp +++ b/src/http/header_query.hpp @@ -1,5 +1,8 @@ +#pragma once #include +namespace anthracite::http { + class name_value { private: std::string _name; @@ -26,13 +29,13 @@ public: virtual std::string to_string() { return ""; } }; -class http_header : public name_value { +class header : public name_value { public: - http_header() + header() : name_value() { } - http_header(std::string name, std::string value) + header(std::string name, std::string value) : name_value(name, value) { } @@ -53,3 +56,5 @@ public: std::string to_string() override { return name() + "=" + value(); } }; + +}; diff --git a/src/http/http.hpp b/src/http/http.hpp deleted file mode 100644 index bba2f32..0000000 --- a/src/http/http.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include -#include - -#include "constants.cpp" -#include "header_query.cpp" -#include "../socket.cpp" -#include "../build/version.cpp" diff --git a/src/http/http_response.cpp b/src/http/http_response.cpp deleted file mode 100644 index eaa1cf8..0000000 --- a/src/http/http_response.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "http.hpp" - -class http_response { -private: - int _status_code; - std::string& _content; - std::string _filename; - std::unordered_map _headers; // kinda goofy, whatever - -public: - http_response(std::string& content, std::string filename, int status_code = http_status_codes::OK) - : _content(content) - , _status_code(status_code) - , _filename(std::move(filename)) - { - } - - int status_code() { return _status_code; } - - void add_header(http_header header, bool override_existing = true) - { - if (override_existing || _headers.find(header.name()) == _headers.end()) { - _headers[header.name()] = header; - } - } - - std::string& content() - { - return _content; - } - - std::string header_to_string() - { - std::string response = ""; - response += "HTTP/1.1 " + std::to_string(_status_code) + " " + http_status_map.find(_status_code)->second + "\r\n"; - std::string content_type = "text/html"; - std::string file_extension = _filename.substr(_filename.rfind('.') + 1); - auto mime_type = mime_types.find(file_extension); - if (mime_type != mime_types.end()) { - content_type = mime_type->second; - } - - add_header(http_header("Content-Type", content_type), false); - add_header(http_header("Content-Length", std::to_string(_content.length())), false); - add_header(http_header("Server", ANTHRACITE_FULL_VERSION_STRING), false); - add_header(http_header("Origin-Server", ANTHRACITE_FULL_VERSION_STRING), false); - - for (auto header : _headers) { - response += header.second.to_string(); - } - - response += "\r\n"; - - return response; - } - - std::string to_string() - { - return header_to_string() + _content; - } -}; diff --git a/src/http/http_request.cpp b/src/http/request.cpp similarity index 71% rename from src/http/http_request.cpp rename to src/http/request.cpp index 1e9fd71..a8ca8aa 100644 --- a/src/http/http_request.cpp +++ b/src/http/request.cpp @@ -1,25 +1,11 @@ -#include "http.hpp" +#include "request.hpp" +#include "constants.hpp" +#include "../log/log.hpp" +#include -class http_request { -private: - enum parser_state { METHOD, - PATH, - QUERY_PARAM_NAME, - QUERY_PARAM_VALUE, - VERSION, - HEADER_NAME, - HEADER_VALUE, - BODY_CONTENT }; - http_method _method; - http_version _http_version; - std::string _path; - std::string _client_ipaddr; - std::string _body_content; - std::unordered_map _headers; // kinda goofy, whatever - std::unordered_map _query_params; // kinda goofy, whatever +namespace anthracite::http { -public: - http_request(std::string& raw_data, std::string client_ip) + request::request(std::string& raw_data, const std::string& client_ip) : _path(""), _client_ipaddr(client_ip) { @@ -31,10 +17,10 @@ public: switch (state) { case METHOD: { if (raw_data[i] == ' ') { - if (http_method_map.find(scratch) == http_method_map.end()) { - _method = http_method::UNKNOWN; + if (method_map.find(scratch) == method_map.end()) { + _method = method::UNKNOWN; } else { - _method = http_method_map.find(scratch)->second; + _method = method_map.find(scratch)->second; } scratch = ""; state = PATH; @@ -86,7 +72,7 @@ public: case VERSION: { if (raw_data[i] == '\n') { - _http_version = http_version_map.find(scratch)->second; + _http_version = version_map.find(scratch)->second; scratch = ""; state = HEADER_NAME; } else if (raw_data[i] != '\r') { @@ -113,7 +99,7 @@ public: case HEADER_VALUE: { if (raw_data[i] == '\n') { - _headers[scratch] = http_header(scratch, scratch_2); + _headers[scratch] = header(scratch, scratch_2); scratch = ""; scratch_2 = ""; state = HEADER_NAME; @@ -129,21 +115,22 @@ public: } } - std::string path() { return _path; } + std::string request::path() { return _path; } - http_method method() { return _method; } + method request::get_method() { return _method; } - std::string client_ip() { return _client_ipaddr; } + std::string request::client_ip() { return _client_ipaddr; } - http_version get_http_version() { + version request::get_http_version() { return _http_version; } - bool is_supported_version() { - return _http_version == HTTP_1_1 || _http_version == HTTP_1_0; + bool request::is_supported_version() { + //log::err << reverse_version_map.find(_http_version)->second << std::endl; + return _http_version == HTTP_1_1 || _http_version == HTTP_1_0; } - bool close_connection() { + bool request::close_connection() { const auto& header = _headers.find("Connection"); const bool found = header != _headers.end(); @@ -154,16 +141,16 @@ public: return true; } - std::string to_string() + std::string request::to_string() { std::string response = ""; - response += http_reverse_method_map.find(_method)->second + " " + _path + "?"; + response += reverse_method_map.find(_method)->second + " " + _path + "?"; for (auto qp : _query_params) { response += qp.second.to_string() + "&"; } - response += " " + http_reverse_version_map.find(_http_version)->second + "\r\n"; + response += " " + reverse_version_map.find(_http_version)->second + "\r\n"; for (auto header : _headers) { response += header.second.to_string(); @@ -174,4 +161,5 @@ public: return response; } + }; diff --git a/src/http/request.hpp b/src/http/request.hpp new file mode 100644 index 0000000..7d6855b --- /dev/null +++ b/src/http/request.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include "./header_query.hpp" +#include "./constants.hpp" + +namespace anthracite::http { + +class request { +private: + enum parser_state { METHOD, + PATH, + QUERY_PARAM_NAME, + QUERY_PARAM_VALUE, + VERSION, + HEADER_NAME, + HEADER_VALUE, + BODY_CONTENT }; + method _method; + version _http_version; + std::string _path; + std::string _client_ipaddr; + std::string _body_content; + std::unordered_map _headers; // kinda goofy, whatever + std::unordered_map _query_params; // kinda goofy, whatever + +public: + request(std::string& raw_data, const std::string& client_ip); + std::string path(); + method get_method(); + std::string client_ip(); + version get_http_version(); + bool is_supported_version(); + bool close_connection(); + std::string to_string(); +}; + +}; diff --git a/src/http/response.cpp b/src/http/response.cpp new file mode 100644 index 0000000..646af07 --- /dev/null +++ b/src/http/response.cpp @@ -0,0 +1,58 @@ +#include "response.hpp" +#include "../build_supp/version.cpp" + +namespace anthracite::http { + + response::response() {}; + + int response::status_code() { return _status_code; } + + void response::add_body(const std::string body) { + _content_noref = body; + _content = &_content_noref; + } + + void response::add_body_ref(std::string* body) { + _content = body; + } + + void response::add_header(header header, bool override_existing) + { + if (override_existing || _headers.find(header.name()) == _headers.end()) { + _headers[header.name()] = header; + } + } + + void response::add_status(int code) { + _status_code = code; + } + + std::string& response::content() + { + return *_content; + } + + std::string response::header_to_string() + { + std::string response = ""; + response += "HTTP/1.1 " + std::to_string(_status_code) + " " + status_map.find(_status_code)->second + "\r\n"; + + add_header(header("Content-Length", std::to_string(_content->length())), false); + add_header(header("Server", ANTHRACITE_FULL_VERSION_STRING), false); + add_header(header("Origin-Server", ANTHRACITE_FULL_VERSION_STRING), false); + + for (auto header : _headers) { + response += header.second.to_string(); + } + + response += "\r\n"; + + return response; + } + + std::string response::to_string() + { + return header_to_string() + *_content; + } + +}; diff --git a/src/http/response.hpp b/src/http/response.hpp new file mode 100644 index 0000000..ed0a90b --- /dev/null +++ b/src/http/response.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include "header_query.hpp" +#include "constants.hpp" +#include + +namespace anthracite::http { + +class response { +private: + int _status_code; + std::string* _content; + std::string _content_noref; + std::unordered_map _headers; // kinda goofy, whatever + +public: + response(); + int status_code(); + + void add_body(const std::string body); + void add_body_ref(std::string* body); + void add_status(int); + void add_header(header header, bool override_existing = true); + std::string& content(); + std::string header_to_string(); + std::string to_string(); +}; + +}; diff --git a/src/log/log.cpp b/src/log/log.cpp new file mode 100644 index 0000000..f1bc09e --- /dev/null +++ b/src/log/log.cpp @@ -0,0 +1,24 @@ +#pragma once + +#include "./log.hpp" + +namespace anthracite::log { + enum LOG_LEVEL Logger::_level = LOG_LEVEL_NONE; + + // TODO: implement logger as a singleton to prevent duplicates + Logger::Logger() = default; + void Logger::initialize(enum LOG_LEVEL level) { + _level = level; + } + + LogBuf::LogBuf(std::ostream& output_stream, const std::string& tag, enum LOG_LEVEL level) : _output_stream(output_stream), _tag(tag), _level(level) {} + + int LogBuf::sync() { + if (this->_level <= logger._level) { + std::cout << "[" << this ->_tag << "] " << this->str(); + std::cout.flush(); + } + this->str(""); + return 0; + } +}; diff --git a/src/log/log.hpp b/src/log/log.hpp new file mode 100644 index 0000000..ba1bb05 --- /dev/null +++ b/src/log/log.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +namespace anthracite::log { + enum LOG_LEVEL { + LOG_LEVEL_NONE = 0, + LOG_LEVEL_ERROR = 1, + LOG_LEVEL_WARN = 2, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_VERBOSE = 4, + LOG_LEVEL_DEBUG = 5 + }; + + class Logger { + friend class LogBuf; + static enum LOG_LEVEL _level; + public: + + Logger(); + void initialize(enum LOG_LEVEL level); + }; + + + class LogBuf : public std::stringbuf + { + std::string _tag; + std::ostream& _output_stream; + enum LOG_LEVEL _level; + public: + LogBuf(std::ostream& output_stream, const std::string& tag, enum LOG_LEVEL level); + int sync() override; + }; + + static class Logger logger{}; + + static class LogBuf errBuf{std::cerr, "EROR", LOG_LEVEL_ERROR}; + static std::ostream err(&errBuf); + + static class LogBuf warnBuf{std::cerr, "WARN", LOG_LEVEL_WARN}; + static std::ostream warn(&warnBuf); + + static class LogBuf infoBuf{std::cout, "INFO", LOG_LEVEL_INFO}; + static std::ostream info(&infoBuf); + + static class LogBuf verboseBuf{std::cout, "VERB", LOG_LEVEL_VERBOSE}; + static std::ostream verbose(&verboseBuf); + + static class LogBuf debugBuf{std::cout, "DEBG", LOG_LEVEL_DEBUG}; + static std::ostream debug(&debugBuf); +}; diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 30da4a6..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "backends/file_backend.cpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -void log_request_and_response(http_request& req, std::unique_ptr& resp); - -constexpr int default_port = 80; -constexpr int max_worker_threads = 128; - -void handle_client(anthracite_socket s, backend& b, file_backend& fb, std::mutex& thread_wait_mutex, std::condition_variable& thread_wait_condvar, int& active_threads) -{ - while (true) { - std::string raw_request = s.recv_message(HTTP_HEADER_BYTES); - if (raw_request == "") { - break; - } - http_request req(raw_request, s.get_client_ip()); - std::unique_ptr resp = req.is_supported_version() ? b.handle_request(req) : fb.handle_error(http_status_codes::HTTP_VERSION_NOT_SUPPORTED); - log_request_and_response(req, resp); - std::string header = resp->header_to_string(); - s.send_message(header); - s.send_message(resp->content()); - resp.reset(); - if (req.close_connection()) { - break; - } - } - s.close_conn(); - { - std::lock_guard lock(thread_wait_mutex); - active_threads--; - } - thread_wait_condvar.notify_one(); -} - -int main(int argc, char** argv) -{ - auto args = std::span(argv, size_t(argc)); - int port_number = default_port; - - if (argc > 1) { - port_number = atoi(args[1]); - } - - std::cout << "Initializing Anthracite" << std::endl; - anthracite_socket s(port_number); - file_backend fb(argc > 2 ? args[2] : "./www"); - std::cout << "Initialization Complete" << std::endl; - std::cout << "Listening for HTTP connections on port " << port_number << std::endl; - - int active_threads = 0; - std::mutex thread_wait_mutex; - std::condition_variable thread_wait_condvar; - - while (true) { - s.wait_for_conn(); - std::unique_lock lock(thread_wait_mutex); - thread_wait_condvar.wait(lock, [active_threads] { return active_threads < max_worker_threads; }); - active_threads++; - std::thread(handle_client, s, std::ref(fb), std::ref(fb), std::ref(thread_wait_mutex), std::ref(thread_wait_condvar), std::ref(active_threads)).detach(); - } - - exit(0); -} - -void log_request_and_response(http_request& req, std::unique_ptr& resp) -{ - std::cout << "[" << resp->status_code() << " " + http_status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http_reverse_method_map.find(req.method())->second + " " + req.path() << std::endl; -} diff --git a/src/socket.cpp b/src/socket.hpp similarity index 52% rename from src/socket.cpp rename to src/socket.hpp index 76d1873..790ae9f 100644 --- a/src/socket.cpp +++ b/src/socket.hpp @@ -1,14 +1,14 @@ #include -#include -#include -#include +#include #include #include -#include +#include #include #include #include -#include +#include + +namespace anthracite::socket { constexpr int MAX_QUEUE_LENGTH = 100; @@ -19,10 +19,13 @@ private: std::string client_ip; struct sockaddr_in client_addr {}; socklen_t client_addr_len {}; + static constexpr struct timeval timeout_tv { + .tv_sec = 5, .tv_usec = 0 + }; public: anthracite_socket(int port, int max_queue = MAX_QUEUE_LENGTH) - : server_socket(socket(AF_INET, SOCK_STREAM, 0)) + : server_socket(::socket(AF_INET, SOCK_STREAM, 0)) , client_ip("") { struct sockaddr_in address {}; @@ -30,23 +33,23 @@ public: address.sin_port = htons(port); address.sin_addr.s_addr = INADDR_ANY; - int x = 1; - setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); - bind(server_socket, (struct sockaddr*)&address, sizeof(address)); + int reuse_opt = 1; + setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse_opt, sizeof(reuse_opt)); + bind(server_socket, reinterpret_cast(&address), sizeof(address)); - ::listen(server_socket, max_queue); + listen(server_socket, max_queue); } void wait_for_conn() { client_ip = ""; - client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len); - char ip_str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, INET_ADDRSTRLEN); - client_ip = std::string(ip_str); + client_socket = accept(server_socket, reinterpret_cast(&client_addr), &client_addr_len); + std::array ip_str { 0 }; + inet_ntop(AF_INET, &client_addr.sin_addr, ip_str.data(), INET_ADDRSTRLEN); + client_ip = std::string(ip_str.data()); } - std::string get_client_ip() + const std::string& get_client_ip() { return client_ip; } @@ -71,18 +74,17 @@ public: return ""; } - struct timeval tv; - tv.tv_sec = 5; - tv.tv_usec = 0; - setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); - char response[buffer_size + 1]; - int result = recv(client_socket, response, sizeof(response), 0); + setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout_tv, sizeof timeout_tv); + std::vector response(buffer_size + 1); + ssize_t result = recv(client_socket, response.data(), buffer_size + 1, 0); if (result < 1) { return ""; } response[buffer_size] = '\0'; - return std::string(response); + return { response.data() }; } }; + +};