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 e9f8251..0000000 Binary files a/server and /dev/null differ diff --git a/server.hpp b/server.hpp index b930bce..db2d876 100644 --- a/server.hpp +++ b/server.hpp @@ -1,27 +1,64 @@ #pragma once -#include "sockets/epoll.hpp" +#include "epoll_socket.hpp" #include "router.hpp" #include "http_parser.hpp" +#include "http_response.hpp" #include #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_); - } -};