#include #include #include #include #include #include #include #include #include static endpoint_t endpoints[ROUTER_ENDPOINTS_MAX_COUNT]; static endpoint_t endpoint_not_found; static int endpoints_registered = 0; static const char delimiter[2] = "/"; static struct mg_str get_method_str_for_int(int method_int) { if(method_int == HTTP_METHOD_GET) { return mg_mk_str("GET"); } if(method_int == HTTP_METHOD_POST) { return mg_mk_str("POST"); } if(method_int == HTTP_METHOD_PUT) { return mg_mk_str("PUT"); } if(method_int == HTTP_METHOD_DELETE) { return mg_mk_str("DELETE"); } if(method_int == HTTP_METHOD_OPTIONS) { return mg_mk_str("OPTIONS"); } return mg_mk_str("GET"); } endpoint_t* router_get_not_found_endpoint() { return &endpoint_not_found; } void router_init() { // add 404 endpoint endpoint_not_found.route = NULL; endpoint_not_found.func = endpoint_func_not_found; endpoint_not_found.method = 0; endpoint_not_found.args_count = 0; endpoint_not_found.args = NULL; router_register_endpoint("/api/v1/schedules/", HTTP_METHOD_GET, api_v1_schedules_GET); router_register_endpoint("/api/v1/schedules/", HTTP_METHOD_POST, api_v1_schedules_POST); router_register_endpoint("/api/v1/schedules/list/", HTTP_METHOD_POST, api_v1_schedules_list_POST); router_register_endpoint("/api/v1/schedules/{str}", HTTP_METHOD_GET, api_v1_schedules_STR_GET); router_register_endpoint("/api/v1/schedules/{str}", HTTP_METHOD_PUT, api_v1_schedules_STR_PUT); router_register_endpoint("/api/v1/schedules/{str}", HTTP_METHOD_DELETE, api_v1_schedules_STR_DELETE); router_register_endpoint("/api/v1/schedules/tag/{str}", HTTP_METHOD_GET, api_v1_schedules_tag_STR_GET); router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_POST, api_v1_controllers_discover_POST); router_register_endpoint("/api/v1/controllers/", HTTP_METHOD_GET, api_v1_controllers_GET); router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_GET, api_v1_controllers_STR_GET); router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_PUT, api_v1_controllers_STR_PUT); router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_DELETE, api_v1_controllers_STR_DELETE); router_register_endpoint("/api/v1/controllers/{str}/relays/", HTTP_METHOD_GET, api_v1_controllers_STR_relays_GET); router_register_endpoint("/api/v1/controllers/{str}/relays/{int}", HTTP_METHOD_GET, api_v1_controllers_STR_relays_INT_GET); router_register_endpoint("/api/v1/controllers/{str}/relays/{int}", HTTP_METHOD_PUT, api_v1_controllers_STR_relays_INT_PUT); router_register_endpoint("/api/v1/relays/", HTTP_METHOD_GET, api_v1_relays_GET); router_register_endpoint("/api/v1/relays/tag/{str}", HTTP_METHOD_GET, api_v1_relays_tag_STR_GET); router_register_endpoint("/api/v1/tags/", HTTP_METHOD_GET, api_v1_tags_GET); } endpoint_t* router_register_endpoint(const char *route, int method, endpoint_func_f func) { endpoint_t *options_endpoint = NULL; if(method != HTTP_METHOD_OPTIONS) { struct mg_str method_str = get_method_str_for_int(HTTP_METHOD_OPTIONS); options_endpoint = router_find_endpoint(route, strlen(route), &method_str); if(options_endpoint == NULL) { options_endpoint = router_register_endpoint(route, HTTP_METHOD_OPTIONS, NULL); } else { for(int i = 0; i < options_endpoint->args_count; ++i) { if(options_endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR) { free((char*)options_endpoint->args[i].value.v_str); } } } } if(endpoints_registered >= ROUTER_ENDPOINTS_MAX_COUNT) { LOG_ERROR("can't register more than %d endpoints\n", ROUTER_ENDPOINTS_MAX_COUNT); return NULL; } endpoint_t *endpoint = &endpoints[endpoints_registered]; endpoint->full_route = route; endpoint->trailing_slash = 0; // unused because trailing slashes are optional. TODO make option if(route[strlen(route)] == delimiter[0]) { endpoint->trailing_slash = 1; } int route_parts_count = 1; size_t route_len = strlen(route); for(size_t i = 0; i < route_len; ++i) { if(route[i] == delimiter[0] || route[i] == '\0') { ++route_parts_count; } } // +1 for NULL terminator endpoint->route = malloc(sizeof(char*) * (route_parts_count + 1)); endpoint->route_keeper = malloc(sizeof(char) * (route_len + 1)); strncpy(endpoint->route_keeper, route, route_len); endpoint->route_keeper[route_len] = '\0'; int route_part = 0; int route_args_count = 0; char *route_token = strtok(endpoint->route_keeper, delimiter); while(route_token) { if(strcmp(route_token, "{int}") == 0) { ++route_args_count; } if(strcmp(route_token, "{str}") == 0) { ++route_args_count; } endpoint->route[route_part] = route_token; ++route_part; route_token = strtok(NULL, delimiter); } endpoint->route[route_part] = NULL; endpoint->args_count = route_args_count; endpoint->args = NULL; if(route_args_count) { endpoint->args = malloc(sizeof(endpoint_args_t) * route_args_count); } endpoint->func = func; endpoint->method = method; endpoint->options = 0; if(options_endpoint) { options_endpoint->options |= method; } ++endpoints_registered; return endpoint; } static int get_method_int_for_str(struct mg_str *method_str) { if(strncmp(method_str->p, "GET", method_str->len) == 0) { return HTTP_METHOD_GET; } if(strncmp(method_str->p, "POST", method_str->len) == 0) { return HTTP_METHOD_POST; } if(strncmp(method_str->p, "PUT", method_str->len) == 0) { return HTTP_METHOD_PUT; } if(strncmp(method_str->p, "DELETE", method_str->len) == 0) { return HTTP_METHOD_DELETE; } if(strncmp(method_str->p, "OPTIONS", method_str->len) == 0) { return HTTP_METHOD_OPTIONS; } return HTTP_METHOD_GET; } endpoint_t* router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_str) { char *uri = malloc(sizeof(char) * (uri_len + 1)); strncpy(uri, uri_str, uri_len); uri[uri_len] = '\0'; int method = get_method_int_for_str(method_str); for(int i = 0; i < endpoints_registered; ++i) { if(endpoints[i].method & method) { endpoints[i].possible_route = 1; } else { endpoints[i].possible_route = 0; } endpoints[i].args_found = 0; } char *uri_token; char *uri_token_save; uri_token = strtok_r(uri, delimiter, &uri_token_save); int route_part = 0; if(!uri_token) { free(uri); return NULL; } while(uri_token) { for(int i = 0; i < endpoints_registered; ++i) { if(!endpoints[i].possible_route) { continue; } if(!endpoints[i].route[route_part]) { endpoints[i].possible_route = 0; continue; } if(uri_token_save[0] == '\0' && endpoints[i].route[route_part + 1]) { endpoints[i].possible_route = 0; continue; } if(strcmp(endpoints[i].route[route_part], "{str}") == 0) { endpoints[i].possible_route += 1; endpoints[i].args[endpoints[i].args_found].type = ENDPOINT_ARG_TYPE_STR; endpoints[i].args[endpoints[i].args_found].value.v_str = uri_token; ++endpoints[i].args_found; continue; } if(strcmp(endpoints[i].route[route_part], "{int}") == 0) { char *endptr; errno = 0; int found_arg_int = strtol(uri_token, &endptr, 10); if(errno || (endptr && *endptr != '\0')) { endpoints[i].possible_route = 0; continue; } endpoints[i].possible_route += 2; endpoints[i].args[endpoints[i].args_found].type = ENDPOINT_ARG_TYPE_INT; endpoints[i].args[endpoints[i].args_found].value.v_int = found_arg_int; ++endpoints[i].args_found; continue; } if(strcmp(endpoints[i].route[route_part], uri_token) == 0) { endpoints[i].possible_route += 3; continue; } endpoints[i].possible_route = 0; continue; } uri_token = strtok_r(NULL, delimiter, &uri_token_save); ++route_part; } int best_rating = 0; endpoint_t *best_endpoint = NULL; for(int i = 0; i < endpoints_registered; ++i) { int rating = endpoints[i].possible_route; if(rating > best_rating) { best_endpoint = &endpoints[i]; best_rating = best_endpoint->possible_route; } } if(best_endpoint == NULL) { free(uri); return NULL; } for(int i = 0; i < best_endpoint->args_count; ++i) { if(best_endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR) { char *arg_value_str = malloc(sizeof(char) * (strlen(best_endpoint->args[i].value.v_str) + 1)); strcpy(arg_value_str, best_endpoint->args[i].value.v_str); best_endpoint->args[i].value.v_str = arg_value_str; } } free(uri); return best_endpoint; } void router_free() { for(int i = 0; i < endpoints_registered; ++i) { free(endpoints[i].route_keeper); free(endpoints[i].route); if(endpoints[i].args) { free(endpoints[i].args); } } }