mirror of
https://github.com/Karaka-Management/cOMS.git
synced 2026-01-10 19:08:39 +00:00
208 lines
7.3 KiB
C
208 lines
7.3 KiB
C
/**
|
|
* Jingga
|
|
*
|
|
* @copyright Jingga
|
|
* @license OMS License 2.0
|
|
* @version 1.0.0
|
|
* @link https://jingga.app
|
|
*/
|
|
#ifndef COMS_JINGGA_HTTP_ROUTER_H
|
|
#define COMS_JINGGA_HTTP_ROUTER_H
|
|
|
|
#include "../stdlib/Types.h"
|
|
#include "../memory/BufferMemory.h"
|
|
#include "../utils/RegexSimplified.h"
|
|
#include "HttpRoute.h"
|
|
#include "HttpMethod.h"
|
|
|
|
#define HTTP_ROUTE_SEGMENT_LENGTH 32
|
|
|
|
struct HttpRouteNode {
|
|
char segment[HTTP_ROUTE_SEGMENT_LENGTH];
|
|
|
|
// Route information
|
|
// This is empty relatively often since only the last node(s) in a path usually have an endpoint defined
|
|
// However, replacing this with another uint16 route_id for example only saves us 4 bytes,
|
|
// BUT costs us another indirection once we arrive at a matched route/endpoint
|
|
// The current implemenation allows us to directly jump into the detail definitions and iterate them
|
|
uint32 detail_offset;
|
|
byte detail_count;
|
|
|
|
// Do this node require regex matching?
|
|
bool is_regex;
|
|
|
|
// How many child nodes does this node have
|
|
uint16 children_count;
|
|
|
|
// Defines the offset into the nodes array where the children can be found
|
|
uint32 children_offset;
|
|
};
|
|
|
|
struct HttpRouter {
|
|
HttpRouteNode* nodes;
|
|
HttpRouteDetails* route_details;
|
|
|
|
uint16 node_count;
|
|
uint16 node_capacity;
|
|
|
|
uint32 route_detail_count;
|
|
uint32 route_detail_capacity;
|
|
};
|
|
|
|
void http_router_init(HttpRouter* router, uint32 route_count, BufferMemory* buf, int32 alignment = 64) {
|
|
// We expect 3 path components per route
|
|
// If more are required, we will increase the memory later
|
|
router->nodes = (HttpRouteNode *) buffer_get_memory(buf, route_count * 3 * sizeof(HttpRouteNode), alignment, true);
|
|
router->node_capacity = route_count * 3;
|
|
router->node_count = 0;
|
|
|
|
// We expect at least one route detail per route
|
|
// On average it is probably more like 1.x but if we need more we will increase as required later
|
|
router->route_details = (HttpRouteDetails *) buffer_get_memory(buf, route_count * sizeof(HttpRouteDetails), alignment, true);
|
|
router->route_detail_capacity = route_count;
|
|
router->route_detail_count = 0;
|
|
}
|
|
|
|
/**
|
|
* Optimizes the memory layout of the router by making all nodes with the same level consecutive in memory
|
|
* This improves the caching since an element doesn't match we need to compare our current search with the other elements of the same level
|
|
* If these elements are consecutive, there is a bigger chance that they are already loaded into L1 or L2 cache
|
|
*/
|
|
void http_router_optimize() {
|
|
|
|
}
|
|
|
|
// Add a new route
|
|
void http_router_add(
|
|
HttpRouter* router,
|
|
const HttpRoute* route
|
|
) {
|
|
|
|
}
|
|
|
|
void http_router_find_iter(
|
|
const HttpRouter* router,
|
|
const char* uri_segments,
|
|
int32 uri_segment_index,
|
|
int32 uri_segment_count,
|
|
HttpRouteDetails** matches,
|
|
int32* match_count,
|
|
HttpRouteNode* node = NULL
|
|
) {
|
|
for (uint32 i = 0; i < node->children_count; ++i) {
|
|
HttpRouteNode* test_node = &router->nodes[node->children_offset + i];
|
|
if ((!test_node->is_regex && str_compare(test_node->segment, uri_segments) == 0)
|
|
|| (test_node->is_regex && regex_simplified_validate(test_node->segment, uri_segments))
|
|
) {
|
|
if (uri_segment_index < uri_segment_count && test_node->children_count) {
|
|
// We have more in our uri path AND more child nodes
|
|
// -> We need to continue pattern matching
|
|
http_router_find_iter(
|
|
router,
|
|
uri_segments + str_length(uri_segments) + 1,
|
|
uri_segment_index + 1,
|
|
uri_segment_count,
|
|
matches,
|
|
match_count,
|
|
test_node
|
|
);
|
|
} else if (uri_segment_index == uri_segment_count && !test_node->children_count) {
|
|
// We reached the end of the uri path and the end of the node chain
|
|
// -> We found a possible match
|
|
matches[(*match_count)++] = &router->route_details[test_node->detail_offset + i];
|
|
} else if (uri_segment_index >= uri_segment_count && test_node->children_count) {
|
|
// We reached the end of the uri path BUT still have child nodes
|
|
// -> This can only be a match if any of the child chains from here on are optional/wildcard matches
|
|
http_router_find_iter(
|
|
router,
|
|
"",
|
|
uri_segment_index + 1,
|
|
uri_segment_count,
|
|
matches,
|
|
match_count,
|
|
test_node
|
|
);
|
|
} else if (uri_segment_index < uri_segment_count && !test_node->children_count) {
|
|
// We have more in our uri path BUT no more child nodes
|
|
// -> This can only be a match if the test_node is a regex node that also matches all other path segments
|
|
if (test_node->is_regex) {
|
|
bool is_valid = true;
|
|
for (int32 j = uri_segment_index + 1; j < uri_segment_count; ++j) {
|
|
if (!regex_simplified_validate(test_node->segment, uri_segments)) {
|
|
is_valid = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_valid) {
|
|
matches[(*match_count)++] = &router->route_details[test_node->detail_offset + i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void http_router_route(
|
|
const HttpRouter* router,
|
|
const char* uri,
|
|
bool has_csrf,
|
|
HttpMethod method,
|
|
HttpRouteDetails** matches,
|
|
int32* match_count
|
|
) {
|
|
char uri_segments[MAX_HTTP_ROUTE_LENGTH];
|
|
char* segments_temp = uri_segments;
|
|
|
|
int32 uri_segment_count = 0;
|
|
int32 i = 0;
|
|
|
|
while (*uri != '\0' && i < MAX_HTTP_ROUTE_LENGTH) {
|
|
if (*uri == '/') {
|
|
*segments_temp++ = '\0';
|
|
++uri;
|
|
++uri_segment_count;
|
|
} else {
|
|
*segments_temp++ = *uri++;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
*segments_temp = '\0';
|
|
|
|
// Find potential matches based on the route
|
|
int32 temp_match_count = 0;
|
|
http_router_find_iter(
|
|
router,
|
|
uri_segments,
|
|
0,
|
|
uri_segment_count,
|
|
matches,
|
|
&temp_match_count,
|
|
router->nodes
|
|
);
|
|
|
|
// Remove matches that don't fit the additional criteria
|
|
// The reason why we don't do this in the route iteration is that we don't want to pass this information in every step
|
|
// We need to remember that often only the last 1/2 path entries have actually a route attached
|
|
*match_count = 0;
|
|
for (i = 0; i < temp_match_count; ++i) {
|
|
if ((matches[i]->method & method) // matches method/verb
|
|
&& (matches[i]->flags & HTTP_ROUTE_FLAG_ACTUVE) // route is active
|
|
&& (!(matches[i]->flags & HTTP_ROUTE_FLAG_CSRF_REQUIRED) // doesn't require csrf
|
|
|| ((matches[i]->flags & HTTP_ROUTE_FLAG_CSRF_REQUIRED) && has_csrf) // requires csrf & person has csrf
|
|
)
|
|
) {
|
|
// We only have to re-assign if the temp result has different elements than the final result
|
|
// aka if a route has additional conditions like method, activity, ...
|
|
if (*match_count != i) {
|
|
matches[*match_count] = matches[i];
|
|
}
|
|
|
|
++(*match_count);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif |