From 0f5ce5b524d127b1b24189044ad9e48eafe9e2a2 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Thu, 12 Jun 2025 22:02:53 -0500 Subject: [PATCH] Big optimizations --- .gitignore | 1 + .vscode/settings.json | 58 ------- sockets/epoll.hpp => epoll_socket.hpp | 0 http_common.hpp | 1 + http_response.hpp | 123 ++++++++++++++- router.hpp | 23 +-- server | Bin 105040 -> 0 bytes server.hpp | 210 ++++++++++++++------------ sockets/uring.hpp | 182 ---------------------- 9 files changed, 238 insertions(+), 360 deletions(-) create mode 100644 .gitignore delete mode 100644 .vscode/settings.json rename sockets/epoll.hpp => epoll_socket.hpp (100%) delete mode 100755 server delete mode 100644 sockets/uring.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d34d02b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/server \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5402af2..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "files.associations": { - "*.mllo.php": "blade", - "*.template": "blade", - "array": "cpp", - "atomic": "cpp", - "bit": "cpp", - "cctype": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "compare": "cpp", - "concepts": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "string": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "functional": "cpp", - "iterator": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "optional": "cpp", - "random": "cpp", - "ratio": "cpp", - "string_view": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "initializer_list": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "new": "cpp", - "numbers": "cpp", - "ostream": "cpp", - "semaphore": "cpp", - "span": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "stop_token": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cinttypes": "cpp", - "typeinfo": "cpp" - } -} diff --git a/sockets/epoll.hpp b/epoll_socket.hpp similarity index 100% rename from sockets/epoll.hpp rename to epoll_socket.hpp diff --git a/http_common.hpp b/http_common.hpp index a9938c2..65c2924 100644 --- a/http_common.hpp +++ b/http_common.hpp @@ -1,4 +1,5 @@ #pragma once + #include enum class HttpMethod : uint8_t { diff --git a/http_response.hpp b/http_response.hpp index e9be53f..7c93b65 100644 --- a/http_response.hpp +++ b/http_response.hpp @@ -1,8 +1,119 @@ -// -// Created by sky on 6/12/25. -// +#pragma once -#ifndef HTTP_RESPONSE_H -#define HTTP_RESPONSE_H +#include +#include +#include +#include -#endif //HTTP_RESPONSE_H +using std::string_view; + +struct HttpResponse { + int status = 200; + std::string body; + std::string content_type = "text/plain"; + std::unordered_map headers; + + void set_json(const std::string& json) { + body = json; + content_type = "application/json"; + } + + void set_text(const std::string& text) { + body = text; + content_type = "text/plain"; + } + + void set_html(const std::string& html) { + body = html; + content_type = "text/html"; + } +}; + +class HttpResponseBuilder { +private: + static constexpr size_t BUFFER_SIZE = 4096; + static constexpr const char* STATUS_LINES[] = { + "HTTP/1.1 200 OK\r\n", + "HTTP/1.1 201 Created\r\n", + "HTTP/1.1 400 Bad Request\r\n", + "HTTP/1.1 404 Not Found\r\n", + "HTTP/1.1 500 Internal Server Error\r\n" + }; + + static int get_status_index(int status) { + switch (status) { + case 200: return 0; + case 201: return 1; + case 400: return 2; + case 404: return 3; + case 500: return 4; + default: return -1; + } + } + +public: + static std::string build_response(const HttpResponse& response, string_view version = "HTTP/1.1") { + std::string result; + result.reserve(BUFFER_SIZE); + + // Status line + int status_idx = get_status_index(response.status); + if (status_idx >= 0) { + result += STATUS_LINES[status_idx]; + } else { + result += version; + result += " "; + result += std::to_string(response.status); + result += " Unknown\r\n"; + } + + // Content headers + result += "Content-Type: "; + result += response.content_type; + result += "\r\nContent-Length: "; + result += std::to_string(response.body.size()); + result += "\r\n"; + + // Custom headers + for (const auto& [key, value] : response.headers) { + result += key; + result += ": "; + result += value; + result += "\r\n"; + } + + // Connection handling + bool keep_alive = version != "HTTP/1.0"; + if (response.headers.find("Connection") == response.headers.end()) { + result += keep_alive ? "Connection: keep-alive\r\n" : "Connection: close\r\n"; + } + + result += "\r\n"; + result += response.body; + + return result; + } + + // Fast path for common responses + static std::string build_error_response(int status, string_view message, string_view version = "HTTP/1.1") { + std::string result; + result.reserve(256); + + int status_idx = get_status_index(status); + if (status_idx >= 0) { + result += STATUS_LINES[status_idx]; + } else { + result += version; + result += " "; + result += std::to_string(status); + result += " Error\r\n"; + } + + result += "Content-Type: text/plain\r\nContent-Length: "; + result += std::to_string(message.size()); + result += "\r\nConnection: close\r\n\r\n"; + result += message; + + return result; + } +}; \ No newline at end of file diff --git a/router.hpp b/router.hpp index 4f6d920..85c4ab1 100644 --- a/router.hpp +++ b/router.hpp @@ -1,6 +1,8 @@ #pragma once + #include "http_common.hpp" #include "http_parser.hpp" +#include "http_response.hpp" #include #include #include @@ -9,27 +11,6 @@ #include using std::string_view; -struct HttpResponse { - int status = 200; - std::string body; - std::string content_type = "text/plain"; - std::unordered_map headers; - - void set_json(const std::string& json) { - body = json; - content_type = "application/json"; - } - - void set_text(const std::string& text) { - body = text; - content_type = "text/plain"; - } - - void set_html(const std::string& html) { - body = html; - content_type = "text/html"; - } -}; using Handler = std::function; diff --git a/server b/server deleted file mode 100755 index e9f8251a9f43c540d139b89b1d301051d95bba46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105040 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 dmMP2%%b1?7j>|g$BE^5HLt #include #include #include #include +#include +#include +#include class HttpServer { public: - explicit HttpServer(uint16_t port, Router& router) : socket_(port), router_(router) { - 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); }); + explicit HttpServer(uint16_t port, Router& router) : port_(port), router_(router) {} + + ~HttpServer() { + stop(); + for (auto& worker : workers_) { + if (worker->thread.joinable()) { + worker->thread.join(); + } + } } - bool start() { return socket_.start(); } - void run() { socket_.run(); } - void stop() { socket_.stop(); } + bool start() { + 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_); + if (!worker->socket.start()) return false; + workers_.push_back(std::move(worker)); + } + + for (auto& worker : workers_) { + worker->thread = std::thread([&worker]() { worker->socket.run(); }); + } + + return true; + } + + void run() { + for (auto& worker : workers_) { + if (worker->thread.joinable()) { + worker->thread.join(); + } + } + } + + void stop() { + for (auto& worker : workers_) { + worker->socket.stop(); + } + } - // Utility function to extract path parameters static std::string get_path_param(string_view path, size_t segment_index = 0) { size_t start = 0; size_t current_segment = 0; @@ -46,104 +83,91 @@ public: } private: - static constexpr int BUFFER_SIZE = 8192; + static constexpr int BUFFER_SIZE = 65536; - EpollSocket socket_; - Router& router_; - std::array buffer_; + struct Worker { + EpollSocket socket; + Router& router; + std::array buffer; + std::thread thread; - void handle_connection(int client_fd) { - // Client connected - } + Worker(uint16_t port, Router& r) : socket(port), router(r) { + 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); }); + } - void handle_data(int client_fd) { - while (true) { - ssize_t bytes_read = read(client_fd, buffer_.data(), buffer_.size()); + void handle_connection(int client_fd) { + // Client connected + } - if (bytes_read == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) break; + void handle_data(int client_fd) { + while (true) { + ssize_t bytes_read = read(client_fd, buffer.data(), buffer.size()); + + if (bytes_read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) break; + return; + } + + if (bytes_read == 0) return; + + std::string request_data(buffer.data(), bytes_read); + if (request_data.find("\r\n\r\n") != std::string::npos) { + process_request(client_fd, request_data); + } + } + } + + void handle_disconnect(int client_fd) { + // Client disconnected + } + + void process_request(int client_fd, std::string_view request_data) { + HttpRequest req = HttpParser::parse(request_data); + + if (!req.valid) { + send_error_response(client_fd, "Bad Request", 400, req.version); return; } - if (bytes_read == 0) return; + HttpResponse response; - std::string request_data(buffer_.data(), bytes_read); - if (request_data.find("\r\n\r\n") != std::string::npos) { - process_request(client_fd, request_data); + if (router.handle(req, response)) { + send_http_response(client_fd, response, req.version); + } else { + response.status = 404; + response.set_text("Not Found"); + send_http_response(client_fd, response, req.version); } } - } - void handle_disconnect(int client_fd) { - // Client disconnected - } - - void process_request(int client_fd, std::string_view request_data) { - HttpRequest req = HttpParser::parse(request_data); - - if (!req.valid) { - send_error_response(client_fd, "Bad Request", 400); - return; + 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); } - HttpResponse response; + void send_raw_response(int client_fd, const std::string& response) { + ssize_t total_sent = 0; + ssize_t response_len = response.size(); - // Try to route the request (router will populate req.params) - if (router_.handle(req, response)) { - send_http_response(client_fd, response); - } else { - response.status = 404; - response.set_text("Not Found"); - send_http_response(client_fd, response); - } - } - - void send_http_response(int client_fd, const HttpResponse& response) { - std::string http_response = "HTTP/1.1 " + std::to_string(response.status); - - switch (response.status) { - case 200: http_response += " OK"; break; - case 201: http_response += " Created"; break; - case 400: http_response += " Bad Request"; break; - case 404: http_response += " Not Found"; break; - case 500: http_response += " Internal Server Error"; break; - default: http_response += " Unknown"; break; - } - - http_response += "\r\n"; - http_response += "Content-Type: " + response.content_type + "\r\n"; - http_response += "Content-Length: " + std::to_string(response.body.size()) + "\r\n"; - - // Add custom headers - for (const auto& [key, value] : response.headers) { - http_response += key + ": " + value + "\r\n"; - } - - http_response += "Connection: keep-alive\r\n\r\n"; - http_response += response.body; - - send_raw_response(client_fd, http_response); - } - - void send_raw_response(int client_fd, const std::string& response) { - ssize_t total_sent = 0; - ssize_t response_len = response.size(); - - while (total_sent < response_len) { - ssize_t sent = write(client_fd, response.data() + total_sent, response_len - total_sent); - if (sent == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) continue; - break; + while (total_sent < response_len) { + ssize_t sent = write(client_fd, response.data() + total_sent, response_len - total_sent); + if (sent == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + break; + } + total_sent += sent; } - total_sent += sent; } - } - void send_error_response(int client_fd, const std::string& message, int status) { - HttpResponse response; - response.status = status; - response.set_text(message); - response.headers["Connection"] = "close"; - send_http_response(client_fd, response); - } -}; + 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); + send_raw_response(client_fd, response); + } + }; + + uint16_t port_; + Router& router_; + std::vector> workers_; +}; \ No newline at end of file diff --git a/sockets/uring.hpp b/sockets/uring.hpp deleted file mode 100644 index ca20983..0000000 --- a/sockets/uring.hpp +++ /dev/null @@ -1,182 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -enum class OpType : uint8_t { - ACCEPT, - READ, - WRITE -}; - -struct IoData { - OpType type; - int fd; - char* buffer; - size_t size; -}; - -class IoUringSocket { -public: - using ConnectionHandler = std::function; - using DataHandler = std::function; - using DisconnectHandler = std::function; - - explicit IoUringSocket(uint16_t port = 8080, int queue_depth = 256) - : port_(port), queue_depth_(queue_depth) {} - - ~IoUringSocket() { - if (server_fd_ != -1) close(server_fd_); - io_uring_queue_exit(&ring_); - for (auto& [fd, buffer] : buffers_) { - delete[] buffer; - } - } - - bool start() { - if (!create_server_socket()) return false; - if (io_uring_queue_init(queue_depth_, &ring_, 0) < 0) return false; - submit_accept(); - return true; - } - - void run() { - while (running_) { - io_uring_cqe* cqe; - int ret = io_uring_wait_cqe(&ring_, &cqe); - if (ret < 0) break; - - handle_completion(cqe); - io_uring_cqe_seen(&ring_, cqe); - } - } - - void stop() { running_ = false; } - - // Event handlers - void on_connection(ConnectionHandler handler) { on_connection_ = std::move(handler); } - void on_data(DataHandler handler) { on_data_ = std::move(handler); } - void on_disconnect(DisconnectHandler handler) { on_disconnect_ = std::move(handler); } - -private: - static constexpr int BUFFER_SIZE = 8192; - - uint16_t port_; - int queue_depth_; - int server_fd_ = -1; - bool running_ = true; - io_uring ring_; - - std::unordered_map buffers_; - ConnectionHandler on_connection_; - DataHandler on_data_; - DisconnectHandler on_disconnect_; - - bool create_server_socket() { - server_fd_ = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd_ == -1) return false; - - int opt = 1; - if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { - return false; - } - - sockaddr_in addr{}; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - addr.sin_port = htons(port_); - - if (bind(server_fd_, reinterpret_cast(&addr), sizeof(addr)) == -1) { - return false; - } - - return listen(server_fd_, SOMAXCONN) != -1; - } - - void submit_accept() { - auto* data = new IoData{OpType::ACCEPT, server_fd_, nullptr, 0}; - - io_uring_sqe* sqe = io_uring_get_sqe(&ring_); - io_uring_prep_accept(sqe, server_fd_, nullptr, nullptr, 0); - io_uring_sqe_set_data(sqe, data); - io_uring_submit(&ring_); - } - - void submit_read(int client_fd) { - if (buffers_.find(client_fd) == buffers_.end()) { - buffers_[client_fd] = new char[BUFFER_SIZE]; - } - - auto* data = new IoData{OpType::READ, client_fd, buffers_[client_fd], BUFFER_SIZE}; - - io_uring_sqe* sqe = io_uring_get_sqe(&ring_); - io_uring_prep_read(sqe, client_fd, data->buffer, data->size, 0); - io_uring_sqe_set_data(sqe, data); - io_uring_submit(&ring_); - } - - void handle_completion(io_uring_cqe* cqe) { - auto* data = static_cast(io_uring_cqe_get_data(cqe)); - if (!data) return; - - switch (data->type) { - case OpType::ACCEPT: - handle_accept(cqe->res); - submit_accept(); // Continue accepting - break; - - case OpType::READ: - handle_read(data->fd, cqe->res, data->buffer); - break; - - case OpType::WRITE: - // Write completed, nothing special needed - break; - } - - delete data; - } - - void handle_accept(int result) { - if (result < 0) return; - - int client_fd = result; - if (on_connection_) on_connection_(client_fd); - submit_read(client_fd); - } - - void handle_read(int client_fd, int result, char* buffer) { - if (result <= 0) { - if (on_disconnect_) on_disconnect_(client_fd); - cleanup_client(client_fd); - return; - } - - if (on_data_) on_data_(client_fd, buffer, result); - submit_read(client_fd); // Continue reading - } - - void cleanup_client(int client_fd) { - close(client_fd); - if (buffers_.find(client_fd) != buffers_.end()) { - delete[] buffers_[client_fd]; - buffers_.erase(client_fd); - } - } - -public: - void write_async(int client_fd, const char* data, size_t len) { - auto* io_data = new IoData{OpType::WRITE, client_fd, nullptr, len}; - - io_uring_sqe* sqe = io_uring_get_sqe(&ring_); - io_uring_prep_write(sqe, client_fd, data, len, 0); - io_uring_sqe_set_data(sqe, io_data); - io_uring_submit(&ring_); - } -};