From ae1aa94aa3c0d4dbac3bf48ce7d6cf7a975050b2 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Fri, 13 Jun 2025 18:07:18 -0500 Subject: [PATCH] big optimizations and refactor --- cookie.hpp | 12 ++-- main.cpp | 22 +++--- parser.hpp | 6 +- http_request.hpp => request.hpp | 24 ++++++- http_response.hpp => response.hpp | 6 +- router.hpp | 110 ++++++++++++++++++------------ server.hpp | 28 ++++---- static_file_handler.hpp | 4 +- 8 files changed, 131 insertions(+), 81 deletions(-) rename http_request.hpp => request.hpp (54%) rename http_response.hpp => response.hpp (95%) diff --git a/cookie.hpp b/cookie.hpp index 5afe643..87720ee 100644 --- a/cookie.hpp +++ b/cookie.hpp @@ -60,10 +60,14 @@ private: static const char* skip_separators(const char* ptr, const char* end) { // Unrolled loop for common case while (likely(ptr < end - 3)) { - if (*ptr != ' ' && *ptr != ';') break; ++ptr; - if (*ptr != ' ' && *ptr != ';') break; ++ptr; - if (*ptr != ' ' && *ptr != ';') break; ++ptr; - if (*ptr != ' ' && *ptr != ';') break; ++ptr; + if (*ptr != ' ' && *ptr != ';') break; + ++ptr; + if (*ptr != ' ' && *ptr != ';') break; + ++ptr; + if (*ptr != ' ' && *ptr != ';') break; + ++ptr; + if (*ptr != ' ' && *ptr != ';') break; + ++ptr; } while (ptr < end && (*ptr == ' ' || *ptr == ';')) ++ptr; return ptr; diff --git a/main.cpp b/main.cpp index d339e9f..5c6fd35 100644 --- a/main.cpp +++ b/main.cpp @@ -2,7 +2,7 @@ #include #include -HttpServer* server = nullptr; +Server* server = nullptr; void signal_handler(int sig) { if (server) { @@ -20,34 +20,34 @@ int main() { Router router; // Root route - router.get("/", [](const HttpRequest& req, HttpResponse& res) { + router.get("/", [](const Request& req, Response& res) { res.set_text("Hello, World! HTTP Server with Router\n"); res.set_cookie("test_cookie", "hey there!"); }); // API routes - router.get("/api/status", [](const HttpRequest& req, HttpResponse& res) { + router.get("/api/status", [](const Request& req, Response& res) { res.set_json("{\"status\":\"running\",\"version\":\"1.0\"}"); }); // Users routes - router.get("/users", [](const HttpRequest& req, HttpResponse& res) { + router.get("/users", [](const Request& req, Response& res) { res.set_json("{\"users\":[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]}"); }); - router.get("/users/:id", [](const HttpRequest& req, HttpResponse& res) { - std::string id = req.params.at("id"); + router.get("/users/:id", [](const Request& req, Response& res) { + std::string id = req.get_param("id"); std::string response = "{\"id\":" + id + ",\"name\":\"User" + id + "\"}"; res.set_json(response); }); - router.post("/users", [](const HttpRequest& req, HttpResponse& res) { + router.post("/users", [](const Request& req, Response& res) { std::string body_preview = std::string(req.body.substr(0, 100)); res.set_json("{\"message\":\"User created\",\"received\":\"" + body_preview + "\"}"); res.status = 201; }); - router.get("/request-info", [](const HttpRequest& req, HttpResponse& res) { + router.get("/request-info", [](const Request& req, Response& res) { std::string info = "Method: " + std::to_string(static_cast(req.method)) + "\n"; info += "Path: " + std::string(req.path) + "\n"; info += "Query: " + std::string(req.query) + "\n"; @@ -56,11 +56,11 @@ int main() { res.set_text(info); }); - router.get("/foo", [](const HttpRequest& req, HttpResponse& res) { + router.get("/foo", [](const Request& req, Response& res) { res.set_text("Admin"); }); - router.get("/admin/counter", [](const HttpRequest& req, HttpResponse& res) { + router.get("/admin/counter", [](const Request& req, Response& res) { auto current = server->store.get("visit_count"); int count = current ? current.value() : 0; server->store.set("visit_count", count + 1); @@ -68,7 +68,7 @@ int main() { res.set_text("Counter: " + std::to_string(count + 1)); }); - server = new HttpServer(8080, router); + server = new Server(8080, router); server->serve_static("../assets"); diff --git a/parser.hpp b/parser.hpp index ac063a6..5e508d9 100644 --- a/parser.hpp +++ b/parser.hpp @@ -1,7 +1,7 @@ #pragma once #include "common.hpp" -#include "http_request.hpp" +#include "request.hpp" #include "router.hpp" #include "cookie.hpp" #include @@ -14,8 +14,8 @@ using std::string_view; class Parser { public: - static HttpRequest parse(string_view data) { - HttpRequest req; + static Request parse(string_view data) { + Request req; const char* ptr = data.data(); const char* end = ptr + data.size(); diff --git a/http_request.hpp b/request.hpp similarity index 54% rename from http_request.hpp rename to request.hpp index f02b4c7..c93b604 100644 --- a/http_request.hpp +++ b/request.hpp @@ -8,14 +8,14 @@ using std::string_view; using std::string; -struct HttpRequest { +struct Request { 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::unordered_map params; // URL parameters std::vector cookies; // Parsed cookies size_t content_length = 0; @@ -28,4 +28,24 @@ struct HttpRequest { string session_id() const { return string(get_cookie("session_id")); } + + string get_param(string_view key) const { + auto it = params.find(key); + return it != params.end() ? string(it->second) : string{}; + } + + int get_param_int(string_view key) const { + auto it = params.find(key); + if (it == params.end()) return 0; + + int result = 0; + for (char c : it->second) { + if (c >= '0' && c <= '9') { + result = result * 10 + (c - '0'); + } else { + break; + } + } + return result; + } }; diff --git a/http_response.hpp b/response.hpp similarity index 95% rename from http_response.hpp rename to response.hpp index 59f23a8..7fa806e 100644 --- a/http_response.hpp +++ b/response.hpp @@ -8,7 +8,7 @@ using std::string_view; -struct HttpResponse { +struct Response { int status = 200; std::string body; std::string content_type = "text/plain"; @@ -42,7 +42,7 @@ struct HttpResponse { } }; -class HttpResponseBuilder { +class ResponseBuilder { private: static constexpr size_t BUFFER_SIZE = 4096; static constexpr const char* STATUS_LINES[] = { @@ -65,7 +65,7 @@ private: } public: - static std::string build_response(const HttpResponse& response, string_view version = "HTTP/1.1") { + static std::string build_response(const Response& response, string_view version = "HTTP/1.1") { std::string result; result.reserve(BUFFER_SIZE); diff --git a/router.hpp b/router.hpp index 078814d..795248a 100644 --- a/router.hpp +++ b/router.hpp @@ -1,18 +1,19 @@ #pragma once #include "common.hpp" -#include "http_response.hpp" -#include "http_request.hpp" +#include "response.hpp" +#include "request.hpp" #include #include #include #include #include #include +#include using std::string_view; -using Handler = std::function; +using Handler = std::function; struct TrieNode { std::string param_name; @@ -27,67 +28,89 @@ class Router { private: std::unique_ptr root = std::make_unique(); + // Zero-allocation path segment iterator + struct PathIterator { + const char* current; + const char* end; + + PathIterator(string_view path) : current(path.data()), end(path.data() + path.size()) { + skip_slash(); + } + + bool has_next() const { return current < end; } + + string_view next() { + if (!has_next()) return {}; + + const char* start = current; + while (current < end && *current != '/') ++current; + string_view segment(start, current - start); + skip_slash(); + return segment; + } + + private: + void skip_slash() { + while (current < end && *current == '/') ++current; + } + }; + TrieNode* insert_path(string_view path) { TrieNode* current = root.get(); - size_t i = 0; + PathIterator it(path); - while (i < path.length()) { - if (path[i] == '/') { - i++; - continue; - } - - bool is_param = path[i] == ':'; - if (is_param) i++; - - size_t start = i; - while (i < path.length() && path[i] != '/') i++; - - std::string segment(path.substr(start, i - start)); + while (it.has_next()) { + string_view segment = it.next(); + if (segment.empty()) continue; + bool is_param = segment[0] == ':'; if (is_param) { + segment = segment.substr(1); // Remove ':' if (!current->param_child) { current->param_child = std::make_unique(); } - current->param_child->param_name = segment; + current->param_child->param_name = std::string(segment); current = current->param_child.get(); } else { - auto it = current->children.find(segment); + std::string segment_key(segment); + auto it = current->children.find(segment_key); if (it == current->children.end()) { - current->children[segment] = std::make_unique(); + current->children[segment_key] = std::make_unique(); } - current = current->children[segment].get(); + current = current->children[segment_key].get(); } } return current; } - TrieNode* find_path(string_view path, std::unordered_map& params) const { + TrieNode* find_path(string_view path, std::unordered_map& params) const { TrieNode* current = root.get(); - size_t i = 0; + PathIterator it(path); - while (i < path.length() && current) { - if (path[i] == '/') { - i++; - continue; + while (it.has_next() && current) { + string_view segment = it.next(); + if (segment.empty()) continue; + + // Try exact match first - zero allocation lookup + auto child_it = current->children.end(); + for (auto iter = current->children.begin(); iter != current->children.end(); ++iter) { + if (likely(iter->first.size() == segment.size()) && + memcmp(iter->first.data(), segment.data(), segment.size()) == 0) { + child_it = iter; + break; + } } - size_t start = i; - 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_str); - if (it != current->children.end()) { - current = it->second.get(); + if (child_it != current->children.end()) { + current = child_it->second.get(); continue; } // Try parameter match if (current->param_child) { - params[current->param_child->param_name] = segment_str; + // Zero-allocation parameter storage - points to original path + string_view param_name(current->param_child->param_name); + params[param_name] = segment; current = current->param_child.get(); continue; } @@ -118,18 +141,21 @@ public: insert_path(path)->handlers[HttpMethod::PATCH] = std::move(handler); } - bool route(HttpMethod method, string_view path, Handler& out_handler, std::unordered_map& params) const { + bool route(HttpMethod method, string_view path, Handler& out_handler, std::unordered_map& params) const { TrieNode* node = find_path(path, params); - if (!node) return false; + if (unlikely(!node)) return false; auto it = node->handlers.find(method); - if (it == node->handlers.end()) return false; + if (unlikely(it == node->handlers.end())) return false; out_handler = it->second; return true; } - bool handle(HttpRequest& request, HttpResponse& response) const { + bool handle(Request& request, Response& response) const { + // Clear old params without allocation + request.params.clear(); + Handler handler; if (route(request.method, request.path, handler, request.params)) { handler(request, response); diff --git a/server.hpp b/server.hpp index 67a1839..48be584 100644 --- a/server.hpp +++ b/server.hpp @@ -3,7 +3,7 @@ #include "epoll_socket.hpp" #include "router.hpp" #include "parser.hpp" -#include "http_response.hpp" +#include "response.hpp" #include "static_file_handler.hpp" #include "kv_store.hpp" #include "session_store.hpp" @@ -16,14 +16,14 @@ #include #include -class HttpServer { +class Server { public: KeyValueStore store; SessionStore sessions; - explicit HttpServer(uint16_t port, Router& router) : port_(port), router_(router) {} + explicit Server(uint16_t port, Router& router) : port_(port), router_(router) {} - ~HttpServer() { + ~Server() { stop(); store.save(); sessions.save(); @@ -143,7 +143,7 @@ private: } void process_request(int client_fd, std::string_view request_data) { - HttpRequest req = Parser::parse(request_data); + Request req = Parser::parse(request_data); if (!req.valid) { send_error_response(client_fd, "Bad Request", 400, req.version); @@ -156,28 +156,28 @@ private: session_id = sessions.create(); } - HttpResponse response; + Response response; // Try router first if (router.handle(req, response)) { set_session_cookie(response, session_id); - send_http_response(client_fd, response, req.version); + send_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); + send_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); + send_response(client_fd, response, req.version); } } - std::string get_session_id(const HttpRequest& req) { + std::string get_session_id(const Request& req) { auto it = req.headers.find("Cookie"); if (it == req.headers.end()) return ""; @@ -192,12 +192,12 @@ private: return std::string(cookies.substr(pos, end - pos)); } - void set_session_cookie(HttpResponse& response, const std::string& session_id) { + void set_session_cookie(Response& 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); + void send_response(int client_fd, const Response& response, string_view version) { + std::string http_response = ResponseBuilder::build_response(response, version); send_raw_response(client_fd, http_response); } @@ -216,7 +216,7 @@ private: } void send_error_response(int client_fd, const std::string& message, int status, string_view version) { - std::string response = HttpResponseBuilder::build_error_response(status, message, version); + std::string response = ResponseBuilder::build_error_response(status, message, version); send_raw_response(client_fd, response); } }; diff --git a/static_file_handler.hpp b/static_file_handler.hpp index 9ccd8a9..0ab95ec 100644 --- a/static_file_handler.hpp +++ b/static_file_handler.hpp @@ -1,7 +1,7 @@ #pragma once #include "parser.hpp" -#include "http_response.hpp" +#include "response.hpp" #include "common.hpp" #include #include @@ -81,7 +81,7 @@ public: } } - bool handle(const HttpRequest& req, HttpResponse& res) { + bool handle(const Request& req, Response& res) { if (req.method != HttpMethod::GET && req.method != HttpMethod::HEAD) { return false; }