Big optimizations

This commit is contained in:
Sky Johnson 2025-06-12 22:02:53 -05:00
parent 8523bcd5cd
commit 0f5ce5b524
9 changed files with 238 additions and 360 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/server

58
.vscode/settings.json vendored
View File

@ -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"
}
}

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
enum class HttpMethod : uint8_t { enum class HttpMethod : uint8_t {

View File

@ -1,8 +1,119 @@
// #pragma once
// Created by sky on 6/12/25.
//
#ifndef HTTP_RESPONSE_H #include <string>
#define HTTP_RESPONSE_H #include <string_view>
#include <unordered_map>
#include <array>
#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<std::string, std::string> 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;
}
};

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "http_common.hpp" #include "http_common.hpp"
#include "http_parser.hpp" #include "http_parser.hpp"
#include "http_response.hpp"
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -9,27 +11,6 @@
#include <memory> #include <memory>
using std::string_view; using std::string_view;
struct HttpResponse {
int status = 200;
std::string body;
std::string content_type = "text/plain";
std::unordered_map<std::string, std::string> 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<void(const HttpRequest&, HttpResponse&)>; using Handler = std::function<void(const HttpRequest&, HttpResponse&)>;

BIN
server

Binary file not shown.

View File

@ -1,27 +1,64 @@
#pragma once #pragma once
#include "sockets/epoll.hpp" #include "epoll_socket.hpp"
#include "router.hpp" #include "router.hpp"
#include "http_parser.hpp" #include "http_parser.hpp"
#include "http_response.hpp"
#include <iostream> #include <iostream>
#include <string.h> #include <string.h>
#include <string_view> #include <string_view>
#include <array> #include <array>
#include <string> #include <string>
#include <thread>
#include <vector>
#include <memory>
class HttpServer { class HttpServer {
public: public:
explicit HttpServer(uint16_t port, Router& router) : socket_(port), router_(router) { explicit HttpServer(uint16_t port, Router& router) : port_(port), router_(router) {}
socket_.on_connection([this](int fd) { handle_connection(fd); });
socket_.on_data([this](int fd) { handle_data(fd); }); ~HttpServer() {
socket_.on_disconnect([this](int fd) { handle_disconnect(fd); }); stop();
for (auto& worker : workers_) {
if (worker->thread.joinable()) {
worker->thread.join();
}
}
} }
bool start() { return socket_.start(); } bool start() {
void run() { socket_.run(); } unsigned int num_cores = std::thread::hardware_concurrency();
void stop() { socket_.stop(); } 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<Worker>(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) { static std::string get_path_param(string_view path, size_t segment_index = 0) {
size_t start = 0; size_t start = 0;
size_t current_segment = 0; size_t current_segment = 0;
@ -46,104 +83,91 @@ public:
} }
private: private:
static constexpr int BUFFER_SIZE = 8192; static constexpr int BUFFER_SIZE = 65536;
EpollSocket socket_; struct Worker {
Router& router_; EpollSocket socket;
std::array<char, BUFFER_SIZE> buffer_; Router& router;
std::array<char, BUFFER_SIZE> buffer;
std::thread thread;
void handle_connection(int client_fd) { Worker(uint16_t port, Router& r) : socket(port), router(r) {
// Client connected 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) { void handle_connection(int client_fd) {
while (true) { // Client connected
ssize_t bytes_read = read(client_fd, buffer_.data(), buffer_.size()); }
if (bytes_read == -1) { void handle_data(int client_fd) {
if (errno == EAGAIN || errno == EWOULDBLOCK) break; 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; return;
} }
if (bytes_read == 0) return; HttpResponse response;
std::string request_data(buffer_.data(), bytes_read); if (router.handle(req, response)) {
if (request_data.find("\r\n\r\n") != std::string::npos) { send_http_response(client_fd, response, req.version);
process_request(client_fd, request_data); } else {
response.status = 404;
response.set_text("Not Found");
send_http_response(client_fd, response, req.version);
} }
} }
}
void handle_disconnect(int client_fd) { void send_http_response(int client_fd, const HttpResponse& response, string_view version) {
// Client disconnected std::string http_response = HttpResponseBuilder::build_response(response, version);
} send_raw_response(client_fd, http_response);
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;
} }
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) while (total_sent < response_len) {
if (router_.handle(req, response)) { ssize_t sent = write(client_fd, response.data() + total_sent, response_len - total_sent);
send_http_response(client_fd, response); if (sent == -1) {
} else { if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
response.status = 404; break;
response.set_text("Not Found"); }
send_http_response(client_fd, response); total_sent += sent;
}
}
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;
} }
total_sent += sent;
} }
}
void send_error_response(int client_fd, const std::string& message, int status) { void send_error_response(int client_fd, const std::string& message, int status, string_view version) {
HttpResponse response; std::string response = HttpResponseBuilder::build_error_response(status, message, version);
response.status = status; send_raw_response(client_fd, response);
response.set_text(message); }
response.headers["Connection"] = "close"; };
send_http_response(client_fd, response);
} uint16_t port_;
Router& router_;
std::vector<std::unique_ptr<Worker>> workers_;
}; };

View File

@ -1,182 +0,0 @@
#pragma once
#include <liburing.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <functional>
#include <unordered_map>
#include <memory>
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<void(int client_fd)>;
using DataHandler = std::function<void(int client_fd, const char* data, size_t len)>;
using DisconnectHandler = std::function<void(int client_fd)>;
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<int, char*> 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<sockaddr*>(&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<IoData*>(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_);
}
};