allman style

This commit is contained in:
Sky Johnson 2025-06-23 23:43:04 -05:00
parent 89c1f82441
commit c6161f382d
2 changed files with 108 additions and 76 deletions

View File

@ -8,18 +8,21 @@
#include <memory> #include <memory>
#include <cstring> #include <cstring>
namespace goldfish { namespace goldfish
{
using std::string_view; using namespace std;
using std::string;
enum class HttpMethod : uint8_t { enum class HttpMethod : uint8_t
{
GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, UNKNOWN GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, UNKNOWN
}; };
class MimeTypes { class MimeTypes
{
public: public:
static string_view get_mime_type(string_view extension) { static string_view get_mime_type(string_view extension)
{
if (!extension.empty() && extension[0] == '.') { if (!extension.empty() && extension[0] == '.') {
extension = extension.substr(1); extension = extension.substr(1);
} }
@ -53,7 +56,8 @@ public:
return "application/octet-stream"; return "application/octet-stream";
} }
static bool should_compress(string_view mime_type) { static bool should_compress(string_view mime_type)
{
return mime_type.starts_with("text/") || return mime_type.starts_with("text/") ||
mime_type.starts_with("application/json") || mime_type.starts_with("application/json") ||
mime_type.starts_with("application/javascript") || mime_type.starts_with("application/javascript") ||
@ -61,7 +65,8 @@ public:
} }
}; };
class IRequest { class IRequest
{
public: public:
virtual ~IRequest() = default; virtual ~IRequest() = default;
virtual HttpMethod method() const = 0; virtual HttpMethod method() const = 0;
@ -71,7 +76,8 @@ public:
virtual string_view get_header(string_view name) const = 0; virtual string_view get_header(string_view name) const = 0;
}; };
class IResponse { class IResponse
{
public: public:
virtual ~IResponse() = default; virtual ~IResponse() = default;
virtual void set_status(int status) = 0; virtual void set_status(int status) = 0;
@ -80,28 +86,31 @@ public:
virtual void set_content_type(string_view content_type) = 0; virtual void set_content_type(string_view content_type) = 0;
}; };
using Params = const std::vector<string_view>&; using Params = const vector<string_view>&;
using Handler = std::function<void(IRequest&, IResponse&, Params)>; using Handler = function<void(IRequest&, IResponse&, Params)>;
struct Node { struct Node
{
string segment; string segment;
Handler handler; Handler handler;
std::vector<std::unique_ptr<Node>> children; vector<unique_ptr<Node>> children;
bool is_param = false; bool is_param = false;
uint8_t max_params = 0; uint8_t max_params = 0;
}; };
class Router { class Router
{
private: private:
std::unique_ptr<Node> get_root = std::make_unique<Node>(); unique_ptr<Node> get_root = make_unique<Node>();
std::unique_ptr<Node> post_root = std::make_unique<Node>(); unique_ptr<Node> post_root = make_unique<Node>();
std::unique_ptr<Node> put_root = std::make_unique<Node>(); unique_ptr<Node> put_root = make_unique<Node>();
std::unique_ptr<Node> delete_root = std::make_unique<Node>(); unique_ptr<Node> delete_root = make_unique<Node>();
std::unique_ptr<Node> patch_root = std::make_unique<Node>(); unique_ptr<Node> patch_root = make_unique<Node>();
mutable std::vector<string_view> params_buffer; mutable vector<string_view> params_buffer;
Node* method_node(HttpMethod method) const { Node* method_node(HttpMethod method) const
{
switch (method) { switch (method) {
case HttpMethod::GET: return get_root.get(); case HttpMethod::GET: return get_root.get();
case HttpMethod::POST: return post_root.get(); case HttpMethod::POST: return post_root.get();
@ -112,12 +121,14 @@ private:
} }
} }
struct Segment { struct Segment
{
string_view text; string_view text;
bool has_more; bool has_more;
}; };
Segment read_segment(string_view path, size_t& pos) const { Segment read_segment(string_view path, size_t& pos) const
{
if (pos >= path.size()) return {"", false}; if (pos >= path.size()) return {"", false};
// Skip leading slashes // Skip leading slashes
@ -130,9 +141,10 @@ private:
return {path.substr(start, pos - start), pos < path.size()}; return {path.substr(start, pos - start), pos < path.size()};
} }
void add_route(Node* root, string_view path, Handler handler) { void add_route(Node* root, string_view path, Handler handler)
{
if (path == "/") { if (path == "/") {
root->handler = std::move(handler); root->handler = move(handler);
return; return;
} }
@ -159,11 +171,11 @@ private:
} }
if (!child) { if (!child) {
auto new_child = std::make_unique<Node>(); auto new_child = make_unique<Node>();
new_child->segment = string(segment); new_child->segment = string(segment);
new_child->is_param = is_param; new_child->is_param = is_param;
child = new_child.get(); child = new_child.get();
current->children.push_back(std::move(new_child)); current->children.push_back(move(new_child));
} }
if (child->max_params < param_count) { if (child->max_params < param_count) {
@ -174,10 +186,11 @@ private:
if (!has_more) break; if (!has_more) break;
} }
current->handler = std::move(handler); current->handler = move(handler);
} }
std::pair<Handler, std::vector<string_view>> lookup(Node* root, string_view path) const { pair<Handler, vector<string_view>> lookup(Node* root, string_view path) const
{
if (path == "/") { if (path == "/") {
return {root->handler, {}}; return {root->handler, {}};
} }
@ -191,10 +204,11 @@ private:
auto result = match(root, path, 0); auto result = match(root, path, 0);
if (!result.first) return {nullptr, {}}; if (!result.first) return {nullptr, {}};
return {result.first, std::vector<string_view>(params_buffer.begin(), params_buffer.begin() + result.second)}; return {result.first, vector<string_view>(params_buffer.begin(), params_buffer.begin() + result.second)};
} }
std::pair<Handler, int> match(Node* current, string_view path, size_t start) const { pair<Handler, int> match(Node* current, string_view path, size_t start) const
{
size_t pos = start; size_t pos = start;
auto [segment, has_more] = read_segment(path, pos); auto [segment, has_more] = read_segment(path, pos);
@ -225,31 +239,38 @@ private:
} }
public: public:
Router() { Router()
{
params_buffer.reserve(16); params_buffer.reserve(16);
} }
void get(string_view path, Handler handler) { void get(string_view path, Handler handler)
add_route(get_root.get(), path, std::move(handler)); {
add_route(get_root.get(), path, move(handler));
} }
void post(string_view path, Handler handler) { void post(string_view path, Handler handler)
add_route(post_root.get(), path, std::move(handler)); {
add_route(post_root.get(), path, move(handler));
} }
void put(string_view path, Handler handler) { void put(string_view path, Handler handler)
add_route(put_root.get(), path, std::move(handler)); {
add_route(put_root.get(), path, move(handler));
} }
void del(string_view path, Handler handler) { void del(string_view path, Handler handler)
add_route(delete_root.get(), path, std::move(handler)); {
add_route(delete_root.get(), path, move(handler));
} }
void patch(string_view path, Handler handler) { void patch(string_view path, Handler handler)
add_route(patch_root.get(), path, std::move(handler)); {
add_route(patch_root.get(), path, move(handler));
} }
bool handle(IRequest& request, IResponse& response) const { bool handle(IRequest& request, IResponse& response) const
{
Node* root = method_node(request.method()); Node* root = method_node(request.method());
if (!root) return false; if (!root) return false;

View File

@ -6,8 +6,10 @@
#include <vector> #include <vector>
using namespace goldfish; using namespace goldfish;
using namespace std;
class TestRequest : public IRequest { class TestRequest : public IRequest
{
private: private:
HttpMethod method_; HttpMethod method_;
string_view path_; string_view path_;
@ -25,7 +27,8 @@ public:
string_view get_header(string_view) const override { return {}; } string_view get_header(string_view) const override { return {}; }
}; };
class TestResponse : public IResponse { class TestResponse : public IResponse
{
private: private:
int status_ = 200; int status_ = 200;
string body_; string body_;
@ -41,7 +44,8 @@ public:
const string& body() const { return body_; } const string& body() const { return body_; }
}; };
void test_basic_routing() { void test_basic_routing()
{
Router router; Router router;
bool handler_called = false; bool handler_called = false;
@ -56,10 +60,11 @@ void test_basic_routing() {
assert(router.handle(req, res)); assert(router.handle(req, res));
assert(handler_called); assert(handler_called);
assert(res.body() == "success"); assert(res.body() == "success");
std::cout << "✓ Basic routing test passed\n"; cout << "✓ Basic routing test passed\n";
} }
void test_parameter_extraction() { void test_parameter_extraction()
{
Router router; Router router;
string extracted_id; string extracted_id;
@ -73,10 +78,11 @@ void test_parameter_extraction() {
assert(router.handle(req, res)); assert(router.handle(req, res));
assert(extracted_id == "123"); assert(extracted_id == "123");
std::cout << "✓ Parameter extraction test passed\n"; cout << "✓ Parameter extraction test passed\n";
} }
void test_multiple_parameters() { void test_multiple_parameters()
{
Router router; Router router;
string user_id, post_id; string user_id, post_id;
@ -91,10 +97,11 @@ void test_multiple_parameters() {
assert(router.handle(req, res)); assert(router.handle(req, res));
assert(user_id == "456"); assert(user_id == "456");
assert(post_id == "789"); assert(post_id == "789");
std::cout << "✓ Multiple parameters test passed\n"; cout << "✓ Multiple parameters test passed\n";
} }
void test_method_routing() { void test_method_routing()
{
Router router; Router router;
string method_called; string method_called;
@ -115,10 +122,11 @@ void test_method_routing() {
assert(router.handle(post_req, res)); assert(router.handle(post_req, res));
assert(method_called == "POST"); assert(method_called == "POST");
std::cout << "✓ Method routing test passed\n"; cout << "✓ Method routing test passed\n";
} }
void test_no_match() { void test_no_match()
{
Router router; Router router;
router.get("/existing", [](IRequest&, IResponse&, Params) {}); router.get("/existing", [](IRequest&, IResponse&, Params) {});
@ -127,10 +135,11 @@ void test_no_match() {
TestResponse res; TestResponse res;
assert(!router.handle(req, res)); assert(!router.handle(req, res));
std::cout << "✓ No match test passed\n"; cout << "✓ No match test passed\n";
} }
void test_mixed_static_and_dynamic() { void test_mixed_static_and_dynamic()
{
Router router; Router router;
string result; string result;
@ -151,18 +160,19 @@ void test_mixed_static_and_dynamic() {
assert(router.handle(dynamic_req, res)); assert(router.handle(dynamic_req, res));
assert(result == "dynamic:posts"); assert(result == "dynamic:posts");
std::cout << "✓ Mixed static/dynamic routing test passed\n"; cout << "✓ Mixed static/dynamic routing test passed\n";
} }
void benchmark_routing() { void benchmark_routing()
std::cout << "\nBenchmarking router performance...\n"; {
cout << "\nBenchmarking router performance...\n";
Router router; Router router;
std::vector<string> test_paths; vector<string> test_paths;
std::mt19937 rng(42); mt19937 rng(42);
std::uniform_int_distribution<> path_len(2, 6); uniform_int_distribution<> path_len(2, 6);
std::uniform_int_distribution<> segment_len(3, 12); uniform_int_distribution<> segment_len(3, 12);
std::uniform_int_distribution<> param_chance(1, 4); uniform_int_distribution<> param_chance(1, 4);
const int num_routes = 10000; const int num_routes = 10000;
const int num_lookups = 1000000; const int num_lookups = 1000000;
@ -190,8 +200,8 @@ void benchmark_routing() {
} }
// Create lookup paths with realistic hit/miss ratio // Create lookup paths with realistic hit/miss ratio
std::vector<string> lookup_paths; vector<string> lookup_paths;
std::uniform_int_distribution<> hit_chance(1, 3); uniform_int_distribution<> hit_chance(1, 3);
for (int i = 0; i < num_lookups; ++i) { for (int i = 0; i < num_lookups; ++i) {
if (hit_chance(rng) == 1 && !test_paths.empty()) { if (hit_chance(rng) == 1 && !test_paths.empty()) {
@ -228,17 +238,18 @@ void benchmark_routing() {
double avg_lookup_time = double(duration.count()) / num_lookups; double avg_lookup_time = double(duration.count()) / num_lookups;
double lookups_per_second = 1000000.0 / avg_lookup_time; double lookups_per_second = 1000000.0 / avg_lookup_time;
std::cout << "Routes registered: " << num_routes << "\n"; cout << "Routes registered: " << num_routes << "\n";
std::cout << "Lookups performed: " << num_lookups << "\n"; cout << "Lookups performed: " << num_lookups << "\n";
std::cout << "Successful routes: " << successful_routes << " (" cout << "Successful routes: " << successful_routes << " ("
<< (100.0 * successful_routes / num_lookups) << "%)\n"; << (100.0 * successful_routes / num_lookups) << "%)\n";
std::cout << "Total time: " << duration.count() << " μs\n"; cout << "Total time: " << duration.count() << " μs\n";
std::cout << "Average lookup: " << avg_lookup_time << " μs\n"; cout << "Average lookup: " << avg_lookup_time << " μs\n";
std::cout << "Lookups/second: " << int(lookups_per_second) << "\n"; cout << "Lookups/second: " << int(lookups_per_second) << "\n";
} }
int main() { int main()
std::cout << "Running Goldfish Router Tests...\n\n"; {
cout << "Running Goldfish Router Tests...\n\n";
test_basic_routing(); test_basic_routing();
test_parameter_extraction(); test_parameter_extraction();
@ -247,7 +258,7 @@ int main() {
test_no_match(); test_no_match();
test_mixed_static_and_dynamic(); test_mixed_static_and_dynamic();
std::cout << "\n✅ All tests passed!\n"; cout << "\n✅ All tests passed!\n";
benchmark_routing(); benchmark_routing();