-
Notifications
You must be signed in to change notification settings - Fork 144
Description
I saw that a generic HTTP routing helper library for the http_s struct was on your wishlist. I've been developing a collection of higher-level abstractions for Facil.io called Sencillo, which adds (among other things) a route handler to Facil.io.
Sencillo's route handler does quite a bit that is far outside the scope of Facil.io (https://github.com/epixoip/sencillo/blob/main/sencillo.h#L1030-L1166), therefore a modified implementation that is more inline with Facil.io's goals is provided below for your consideration:
bool http_path_matches(http_s *request, char *__route) {
// make a mutable copy of the route
char *route = strdup(__route);
if (!route) {
FIO_LOG_ERROR("Failed to allocate memory!");
return false;
}
char *path = fiobj_obj2cstr(request->path).data;
// truncate the path at the query string delimiter,
// as we only care about the path itself and the
// query string is parsed by http_parse_query()
path = strtok(path, "?");
// does the route contain any inline path variables?
if (strchr(route, ':') == null) {
// no - perform direct string comparison
if (strcmp(route, path) == 0) {
free(route);
return true;
} else {
free(route);
return false;
}
}
int route_part_cnt = 0;
int path_part_cnt = 0;
// count the number of parts in the route and the path
for (int i = 0; route[i]; route[i] == '/' ? route_part_cnt++, i++ : i++);
for (int i = 0; path[i]; path[i] == '/' ? path_part_cnt++, i++ : i++);
// do we have an equal number of parts?
if (route_part_cnt != path_part_cnt) {
return false;
}
char *route_parts[route_part_cnt];
char *path_parts[path_part_cnt];
char *route_remaining;
char *path_remaining;
int matches = 0;
// loop through each part
char *route_next_part = strtok_r(route, "/", &route_remaining);
char *path_next_part = strtok_r(path, "/", &path_remaining);
while (route_next_part && path_next_part) {
// if the route part is an inline variable, extract the variable name and its value
if (route_next_part[0] == ':') {
route_parts[matches] = route_next_part + 1;
path_parts[matches++] = path_next_part;
} else {
// the route part is literal, does it match the path part?
if (strcmp(route_next_part, path_next_part)) {
free(route);
return false;
}
}
route_next_part = strtok_r(null, "/", &route_remaining);
path_next_part = strtok_r(null, "/", &path_remaining);
}
free(route);
// add the inline path variable names and values to the request params
for (int i = 0; i < matches; i++) {
http_add2hash(request->params, route_parts[i], strlen(route_parts[i]), path_parts[i], strlen(path_parts[i]), 1);
}
return true;
}
There are a couple ways you could implement http_path_matches
depending on the desired experience.
First, you could create macros for simple conditionals:
#define http_route_get(_h, _route) \
if (strcmp(fiobj_obj2cstr(request->method).data, "GET") == 0 && http_path_matches(_h, _route))
#define http_route_post(_h, _route) \
if (strcmp(fiobj_obj2cstr(request->method).data, "POST") == 0 && http_path_matches(_h, _route))
#define http_route_put(_h, _route) \
if (strcmp(fiobj_obj2cstr(request->method).data, "PUT") == 0 && http_path_matches(_h, _route))
#define http_route_delete(_h, _route) \
if (strcmp(fiobj_obj2cstr(request->method).data, "DELETE") == 0 && http_path_matches(_h, _route))
Use of these macros would be straightforward:
static void on_http_request(http_s *h) {
http_parse_query(h);
http_parse_body(h);
http_route_get(h, "/user/:id") {
FIOBJ id_str = fiobj_str_new("id", 2);
FIOBJ id_val = fiobj_hash_get(request->params, id_str);
char *id = fiobj_obj2cstr(id_val).data;
char greeting[32];
snprintf(greeting, sizeof greeting, "Welcome user %s!", id);
fiobj_free(id_str);
fiobj_free(id_val);
http_send_body(h, greeting, strlen(greeting));
return;
}
}
Another way would be to create macros that map each route to a function:
#define http_route_handler(_h, _method, _route, _func) { \
if (strcmp(fiobj_obj2cstr(request->method).data, _method) == 0 && http_path_matches(_h, _route)) { \
FIO_LOG_DEBUG("Matched route %s %s", _method, _route); \
_func(_h); \
return; \
} \
}
#define http_route_get(_h, _route, _func) \
http_route_handler(_h, "GET", _route, _func)
#define http_route_post(_h, _route, _func) \
http_route_handler(_h, "POST", _route, _func)
#define http_route_put(_h, _route, _func) \
http_route_handler(_h, "PUT", _route, _func)
#define http_route_delete(_h, _route, _func) \
http_route_handler(_h, "DELETE", _route, _func)
Which could simply be incorporated as:
static void on_http_request(http_s *h) {
http_parse_query(h);
http_parse_body(h);
http_route_get(h, "/user/:id", getUserById);
}