big optimizations and refactor
This commit is contained in:
parent
f7f70ccfe5
commit
ae1aa94aa3
12
cookie.hpp
12
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;
|
||||
|
22
main.cpp
22
main.cpp
@ -2,7 +2,7 @@
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
|
||||
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<int>(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<int>("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");
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.hpp"
|
||||
#include "http_request.hpp"
|
||||
#include "request.hpp"
|
||||
#include "router.hpp"
|
||||
#include "cookie.hpp"
|
||||
#include <string_view>
|
||||
@ -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();
|
||||
|
||||
|
@ -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<string_view, string_view> headers;
|
||||
std::unordered_map<string, string> params; // URL parameters
|
||||
std::unordered_map<string_view, string_view> params; // URL parameters
|
||||
std::vector<Cookie> 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;
|
||||
}
|
||||
};
|
@ -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);
|
||||
|
110
router.hpp
110
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 <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
|
||||
using std::string_view;
|
||||
|
||||
using Handler = std::function<void(const HttpRequest&, HttpResponse&)>;
|
||||
using Handler = std::function<void(const Request&, Response&)>;
|
||||
|
||||
struct TrieNode {
|
||||
std::string param_name;
|
||||
@ -27,67 +28,89 @@ class Router {
|
||||
private:
|
||||
std::unique_ptr<TrieNode> root = std::make_unique<TrieNode>();
|
||||
|
||||
// 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<TrieNode>();
|
||||
}
|
||||
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<TrieNode>();
|
||||
current->children[segment_key] = std::make_unique<TrieNode>();
|
||||
}
|
||||
current = current->children[segment].get();
|
||||
current = current->children[segment_key].get();
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
TrieNode* find_path(string_view path, std::unordered_map<std::string, std::string>& params) const {
|
||||
TrieNode* find_path(string_view path, std::unordered_map<string_view, string_view>& 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<std::string, std::string>& params) const {
|
||||
bool route(HttpMethod method, string_view path, Handler& out_handler, std::unordered_map<string_view, string_view>& 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);
|
||||
|
28
server.hpp
28
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 <vector>
|
||||
#include <memory>
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "parser.hpp"
|
||||
#include "http_response.hpp"
|
||||
#include "response.hpp"
|
||||
#include "common.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user