allman style
This commit is contained in:
parent
89c1f82441
commit
c6161f382d
107
goldfish.hpp
107
goldfish.hpp
@ -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;
|
||||||
|
|
||||||
|
77
tests.cpp
77
tests.cpp
@ -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();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user