From e3b7bddf0561a6aa02b7a5aa1b0f52f0846a0dfa Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Fri, 13 Jun 2025 12:32:48 -0500 Subject: [PATCH] rewrite router, add kv and session store --- http_parser.hpp | 20 +----------- http_request.hpp | 13 +++++++- kv_store.hpp | 66 +++++++++++++++++++++++++++++++++++++++ main.cpp | 16 +++++----- router.hpp | 77 +++++++++++----------------------------------- server.hpp | 45 +++++++++++++++++++++++++-- session_store.hpp | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 225 insertions(+), 90 deletions(-) create mode 100644 kv_store.hpp create mode 100644 session_store.hpp diff --git a/http_parser.hpp b/http_parser.hpp index 2c93583..bbc9704 100644 --- a/http_parser.hpp +++ b/http_parser.hpp @@ -1,6 +1,7 @@ #pragma once #include "http_common.hpp" +#include "http_request.hpp" #include "router.hpp" #include "cookie.hpp" #include @@ -10,25 +11,6 @@ using std::string_view; -struct HttpRequest { - HttpMethod method = HttpMethod::UNKNOWN; - string_view path; - string_view query; - string_view version; - string_view body; - std::unordered_map headers; - std::unordered_map params; // URL parameters - std::vector cookies; // Parsed cookies - size_t content_length = 0; - - bool valid = false; - - // Cookie helper method - string_view get_cookie(string_view name) const { - return CookieHelpers::get_cookie(cookies, name); - } -}; - class HttpParser { public: static HttpRequest parse(string_view data) { diff --git a/http_request.hpp b/http_request.hpp index 7fee4ea..c5d9f9c 100644 --- a/http_request.hpp +++ b/http_request.hpp @@ -5,6 +5,9 @@ #include #include +using std::string_view; +using std::string; + struct HttpRequest { HttpMethod method = HttpMethod::UNKNOWN; string_view path; @@ -12,9 +15,17 @@ struct HttpRequest { string_view version; string_view body; std::unordered_map headers; - std::unordered_map params; // URL parameters + std::unordered_map params; // URL parameters std::vector cookies; // Parsed cookies size_t content_length = 0; bool valid = false; + + string_view get_cookie(string_view name) const { + return CookieHelpers::get_cookie(cookies, name); + } + + string session_id() const { + return string(get_cookie("session_id")); + } }; diff --git a/kv_store.hpp b/kv_store.hpp new file mode 100644 index 0000000..302a49b --- /dev/null +++ b/kv_store.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include +#include + +using std::string; + +class KeyValueStore { +public: + void set(const string& key, const string& value) { + std::lock_guard lock(mutex_); + data_[key] = value; + } + + std::optional get(const string& key) { + std::lock_guard lock(mutex_); + auto it = data_.find(key); + return it != data_.end() ? std::optional{it->second} : std::nullopt; + } + + bool del(const string& key) { + std::lock_guard lock(mutex_); + return data_.erase(key) > 0; + } + + size_t size() { + std::lock_guard lock(mutex_); + return data_.size(); + } + + void set_file(const string& filename) { + filename_ = filename; + } + + void load() { + std::ifstream file(filename_); + if (!file.is_open()) return; + + std::lock_guard lock(mutex_); + string line; + while (std::getline(file, line)) { + size_t eq = line.find('='); + if (eq != string::npos) { + data_[line.substr(0, eq)] = line.substr(eq + 1); + } + } + } + + void save() { + std::lock_guard lock(mutex_); + std::ofstream file(filename_); + if (!file.is_open()) return; + + for (const auto& [key, value] : data_) { + file << key << "=" << value << "\n"; + } + } + +private: + std::unordered_map data_; + mutable std::mutex mutex_; + string filename_ = "server_store.txt"; +}; diff --git a/main.cpp b/main.cpp index f18eadf..84341c3 100644 --- a/main.cpp +++ b/main.cpp @@ -45,7 +45,6 @@ int main() { res.status = 201; }); - // Request info route router.get("/request-info", [](const HttpRequest& req, HttpResponse& res) { std::string info = "Method: " + std::to_string(static_cast(req.method)) + "\n"; info += "Path: " + std::string(req.path) + "\n"; @@ -55,15 +54,16 @@ int main() { res.set_text(info); }); - // Different HTTP methods - router.put("/users/:id", [](const HttpRequest& req, HttpResponse& res) { - std::string id = req.params.at("id"); - res.set_json("{\"message\":\"User " + id + " updated\"}"); + router.get("/foo", [](const HttpRequest& req, HttpResponse& res) { + res.set_text("Admin"); }); - router.del("/users/:id", [](const HttpRequest& req, HttpResponse& res) { - std::string id = req.params.at("id"); - res.set_json("{\"message\":\"User " + id + " deleted\"}"); + router.get("/admin/counter", [](const HttpRequest& req, HttpResponse& res) { + auto current = server->store.get("visit_count"); + int count = current ? std::stoi(current.value()) : 0; + server->store.set("visit_count", std::to_string(count + 1)); + + res.set_text("Counter: " + std::to_string(count + 1)); }); server = new HttpServer(8080, router); diff --git a/router.hpp b/router.hpp index 85c4ab1..adf69b0 100644 --- a/router.hpp +++ b/router.hpp @@ -15,13 +15,12 @@ using std::string_view; using Handler = std::function; struct TrieNode { - std::string prefix; - std::string param_name; // For parameter nodes + std::string param_name; std::unordered_map handlers; - std::unordered_map> children; - bool is_param = false; + std::unordered_map> children; + std::unique_ptr param_child; - TrieNode(string_view p = "") : prefix(p) {} + TrieNode() = default; }; class Router { @@ -39,7 +38,7 @@ private: } bool is_param = path[i] == ':'; - if (is_param) i++; // skip ':' + if (is_param) i++; size_t start = i; while (i < path.length() && path[i] != '/') i++; @@ -47,31 +46,17 @@ private: std::string segment(path.substr(start, i - start)); if (is_param) { - // All parameters share the same node - auto it = current->children.find(':'); - if (it == current->children.end()) { - auto new_node = std::make_unique(); - new_node->is_param = true; - new_node->param_name = segment; - current->children[':'] = std::move(new_node); + if (!current->param_child) { + current->param_child = std::make_unique(); } - current = current->children[':'].get(); + current->param_child->param_name = segment; + current = current->param_child.get(); } else { - char first_char = segment[0]; - auto it = current->children.find(first_char); + auto it = current->children.find(segment); if (it == current->children.end()) { - auto new_node = std::make_unique(segment); - current->children[first_char] = std::move(new_node); - current = current->children[first_char].get(); - } else { - current = it->second.get(); - if (current->prefix != segment) { - // Handle prefix mismatch - split node if needed - auto new_node = std::make_unique(segment); - current->children[first_char] = std::move(new_node); - current = current->children[first_char].get(); - } + current->children[segment] = std::make_unique(); } + current = current->children[segment].get(); } } return current; @@ -91,19 +76,19 @@ private: while (i < path.length() && path[i] != '/') i++; string_view segment = path.substr(start, i - start); + std::string segment_str(segment); // Try exact match first - auto it = current->children.find(segment[0]); - if (it != current->children.end() && it->second->prefix == segment) { + auto it = current->children.find(segment_str); + if (it != current->children.end()) { current = it->second.get(); continue; } // Try parameter match - auto param_it = current->children.find(':'); - if (param_it != current->children.end()) { - current = param_it->second.get(); - params[current->param_name] = std::string(segment); + if (current->param_child) { + params[current->param_child->param_name] = segment_str; + current = current->param_child.get(); continue; } @@ -153,29 +138,3 @@ public: return false; } }; - -// Usage example: -/* -Router router; - -router.get("/users", [](const HttpRequest& req, HttpResponse& res) { - res.set_json("{\"users\": []}"); -}); - -router.get("/users/:id", [](const HttpRequest& req, HttpResponse& res) { - std::string id = req.params.at("id"); - res.set_json("{\"id\": \"" + id + "\"}"); -}); - -router.post("/users", [](const HttpRequest& req, HttpResponse& res) { - // Use req.body to get POST data - res.status = 201; - res.set_json("{\"created\": true}"); -}); - -router.get("/users/:id/posts/:postId", [](const HttpRequest& req, HttpResponse& res) { - std::string userId = req.params.at("id"); - std::string postId = req.params.at("postId"); - res.set_json("{\"userId\": \"" + userId + "\", \"postId\": \"" + postId + "\"}"); -}); -*/ diff --git a/server.hpp b/server.hpp index 318ebe4..f80d185 100644 --- a/server.hpp +++ b/server.hpp @@ -5,6 +5,8 @@ #include "http_parser.hpp" #include "http_response.hpp" #include "static_file_handler.hpp" +#include "kv_store.hpp" +#include "session_store.hpp" #include #include #include @@ -16,10 +18,15 @@ class HttpServer { public: + KeyValueStore store; + SessionStore sessions; + explicit HttpServer(uint16_t port, Router& router) : port_(port), router_(router) {} ~HttpServer() { stop(); + store.save(); + sessions.save(); for (auto& worker : workers_) { if (worker->thread.joinable()) { worker->thread.join(); @@ -28,13 +35,16 @@ public: } bool start() { + store.load(); + sessions.load(); + unsigned int num_cores = std::thread::hardware_concurrency(); if (num_cores == 0) num_cores = 1; workers_.reserve(num_cores); for (unsigned int i = 0; i < num_cores; ++i) { - auto worker = std::make_unique(port_, router_, static_handler_); + auto worker = std::make_unique(port_, router_, static_handler_, sessions); if (!worker->socket.start()) return false; workers_.push_back(std::move(worker)); } @@ -95,11 +105,12 @@ private: EpollSocket socket; Router& router; std::shared_ptr& static_handler; + SessionStore& sessions; std::array buffer; std::thread thread; - Worker(uint16_t port, Router& r, std::shared_ptr& sh) - : socket(port), router(r), static_handler(sh) { + Worker(uint16_t port, Router& r, std::shared_ptr& sh, SessionStore& s) + : socket(port), router(r), static_handler(sh), sessions(s) { socket.on_connection([this](int fd) { handle_connection(fd); }); socket.on_data([this](int fd) { handle_data(fd); }); socket.on_disconnect([this](int fd) { handle_disconnect(fd); }); @@ -139,24 +150,52 @@ private: return; } + // Handle session + std::string session_id = get_session_id(req); + if (session_id.empty()) { + session_id = sessions.create(); + } + HttpResponse response; // Try router first if (router.handle(req, response)) { + set_session_cookie(response, session_id); send_http_response(client_fd, response, req.version); return; } // Then try static files if (static_handler && static_handler->handle(req, response)) { + set_session_cookie(response, session_id); send_http_response(client_fd, response, req.version); } else { response.status = 404; response.set_text("Not Found"); + set_session_cookie(response, session_id); send_http_response(client_fd, response, req.version); } } + std::string get_session_id(const HttpRequest& req) { + auto it = req.headers.find("Cookie"); + if (it == req.headers.end()) return ""; + + std::string_view cookies = it->second; + size_t pos = cookies.find("session_id="); + if (pos == std::string_view::npos) return ""; + + pos += 11; // length of "session_id=" + size_t end = cookies.find(';', pos); + if (end == std::string_view::npos) end = cookies.length(); + + return std::string(cookies.substr(pos, end - pos)); + } + + void set_session_cookie(HttpResponse& response, const std::string& session_id) { + response.headers["Set-Cookie"] = "session_id=" + session_id + "; HttpOnly; Path=/; SameSite=Strict"; + } + void send_http_response(int client_fd, const HttpResponse& response, string_view version) { std::string http_response = HttpResponseBuilder::build_response(response, version); send_raw_response(client_fd, http_response); diff --git a/session_store.hpp b/session_store.hpp new file mode 100644 index 0000000..0b5783c --- /dev/null +++ b/session_store.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "kv_store.hpp" +#include +#include +#include + +class SessionStore { +public: + SessionStore() : rng_(std::random_device{}()) {} + + std::string create() { + std::string id = generate_id(); + std::lock_guard lock(mutex_); + sessions_[id] = {}; + return id; + } + + std::optional get(const std::string& id, const std::string& key) { + std::lock_guard lock(mutex_); + auto it = sessions_.find(id); + if (it == sessions_.end()) return std::nullopt; + + auto data_it = it->second.find(key); + return data_it != it->second.end() ? std::optional{data_it->second} : std::nullopt; + } + + void set(const std::string& id, const std::string& key, const std::string& value) { + std::lock_guard lock(mutex_); + sessions_[id][key] = value; + } + + bool del(const std::string& id) { + std::lock_guard lock(mutex_); + return sessions_.erase(id) > 0; + } + + size_t size() { + std::lock_guard lock(mutex_); + return sessions_.size(); + } + + void set_file(const std::string& filename) { + store_.set_file(filename); + } + + void load() { + store_.load(); + } + + void save() { + std::lock_guard lock(mutex_); + for (const auto& [id, data] : sessions_) { + for (const auto& [key, value] : data) { + store_.set(id + ":" + key, value); + } + } + store_.save(); + } + +private: + std::unordered_map> sessions_; + mutable std::mutex mutex_; + KeyValueStore store_; + std::mt19937 rng_; + + std::string generate_id() { + static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + std::string id; + id.reserve(16); + + for (int i = 0; i < 16; ++i) { + id += chars[rng_() % (sizeof(chars) - 1)]; + } + + return id; + } +};