feat: allow matching regex routes
parent
dd0ba31506
commit
464753d627
|
@ -1,12 +1,14 @@
|
||||||
#ifndef HTTP
|
#ifndef HTTP
|
||||||
#define HTTP
|
#define HTTP
|
||||||
|
|
||||||
|
#include <regex.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "picohttpparser.h"
|
#include "picohttpparser.h"
|
||||||
|
|
||||||
#define HTTP_MAX_ALLOWED_HEADERS 16
|
#define HTTP_MAX_ALLOWED_HEADERS 16
|
||||||
|
#define HTTP_MAX_REGEX_GROUPS 4
|
||||||
|
|
||||||
extern const char *http_response_type_names[5][32];
|
extern const char *http_response_type_names[5][32];
|
||||||
extern const char *http_header_names[];
|
extern const char *http_header_names[];
|
||||||
|
@ -33,6 +35,7 @@ typedef struct http_request {
|
||||||
size_t path_len;
|
size_t path_len;
|
||||||
const char *query;
|
const char *query;
|
||||||
size_t query_len;
|
size_t query_len;
|
||||||
|
regmatch_t regex_groups[HTTP_MAX_REGEX_GROUPS];
|
||||||
struct phr_header headers[HTTP_MAX_ALLOWED_HEADERS];
|
struct phr_header headers[HTTP_MAX_ALLOWED_HEADERS];
|
||||||
} http_request;
|
} http_request;
|
||||||
|
|
||||||
|
|
|
@ -105,4 +105,6 @@ void http_loop_res_add_header(http_loop_ctx *ctx, http_header type,
|
||||||
*/
|
*/
|
||||||
event_loop *http_loop_init(http_loop_gctx *gctx);
|
event_loop *http_loop_init(http_loop_gctx *gctx);
|
||||||
|
|
||||||
|
void http_loop_run(event_loop *el, int port);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "http_loop.h"
|
#include <regex.h>
|
||||||
|
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
#include "http_loop.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
bool http_loop_handle_request(event_loop_conn *conn) {
|
bool http_loop_handle_request(event_loop_conn *conn) {
|
||||||
// Prevents the request handler function from looping indefinitely without
|
// Prevents the request handler function from looping indefinitely without
|
||||||
|
@ -54,3 +57,28 @@ event_loop *http_loop_init(http_loop_gctx *gctx) {
|
||||||
|
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void http_loop_run(event_loop *el, int port) {
|
||||||
|
debug("Compiling RegEx routes");
|
||||||
|
|
||||||
|
http_loop_gctx *gctx = el->gctx;
|
||||||
|
http_route *route;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < gctx->route_count; i++) {
|
||||||
|
route = &gctx->routes[i];
|
||||||
|
|
||||||
|
if (route->type == http_route_regex) {
|
||||||
|
regex_t *r = calloc(sizeof(regex_t), 1);
|
||||||
|
|
||||||
|
if (regcomp(r, route->path, 0) != 0) {
|
||||||
|
critical(1, "RegEx expression '%s' failed to compile", route->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
route->regex = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("RegEx routes compiled successfully");
|
||||||
|
|
||||||
|
event_loop_run(el, port);
|
||||||
|
}
|
||||||
|
|
|
@ -12,11 +12,14 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) {
|
||||||
|
|
||||||
const char *method;
|
const char *method;
|
||||||
size_t method_len;
|
size_t method_len;
|
||||||
|
char *path;
|
||||||
|
size_t path_len;
|
||||||
|
|
||||||
int res = phr_parse_request(
|
int res =
|
||||||
(const char *)&conn->rbuf[conn->rbuf_read],
|
phr_parse_request((const char *)&conn->rbuf[conn->rbuf_read],
|
||||||
conn->rbuf_size - conn->rbuf_read, &method, &method_len, &req->path,
|
conn->rbuf_size - conn->rbuf_read, &method, &method_len,
|
||||||
&req->path_len, &req->minor_version, req->headers, &num_headers, 0);
|
(const char **)&path, &path_len, &req->minor_version,
|
||||||
|
req->headers, &num_headers, 0);
|
||||||
|
|
||||||
if (res == -1) {
|
if (res == -1) {
|
||||||
return http_parse_error_invalid;
|
return http_parse_error_invalid;
|
||||||
|
@ -45,16 +48,16 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) {
|
||||||
i = 0;
|
i = 0;
|
||||||
bool no_query = true;
|
bool no_query = true;
|
||||||
|
|
||||||
while (no_query && i < req->path_len) {
|
while (no_query && i < path_len) {
|
||||||
if (req->path[i] == '?') {
|
if (path[i] == '?') {
|
||||||
// Ensure we don't store an invalid pointer if the request simply ends
|
// Ensure we don't store an invalid pointer if the request simply ends
|
||||||
// with '?'
|
// with '?'
|
||||||
if (i + 1 < req->path_len) {
|
if (i + 1 < req->path_len) {
|
||||||
req->query = &req->path[i + 1];
|
req->query = &path[i + 1];
|
||||||
req->query_len = req->path_len - (i + 1);
|
req->query_len = path_len - (i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
req->path_len = i;
|
path_len = i;
|
||||||
|
|
||||||
no_query = false;
|
no_query = false;
|
||||||
}
|
}
|
||||||
|
@ -62,6 +65,15 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) {
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The path needs to be NULL-terminated in order for regex routes to be
|
||||||
|
// matched properly. We know we can overwrite this char because it's either
|
||||||
|
// '?' if there's a query, or '\n' because we know the buf contains a valid
|
||||||
|
// HTTP rqeuest
|
||||||
|
path[path_len] = '\0';
|
||||||
|
|
||||||
|
req->path = path;
|
||||||
|
req->path_len = path_len;
|
||||||
|
|
||||||
// Ensure we clear the old request's query
|
// Ensure we clear the old request's query
|
||||||
if (no_query) {
|
if (no_query) {
|
||||||
req->query = NULL;
|
req->query = NULL;
|
||||||
|
@ -80,6 +92,7 @@ void http_loop_route_request(event_loop_conn *conn) {
|
||||||
|
|
||||||
http_route *route;
|
http_route *route;
|
||||||
bool path_matched = false;
|
bool path_matched = false;
|
||||||
|
char c;
|
||||||
|
|
||||||
for (size_t i = 0; i < gctx->route_count; i++) {
|
for (size_t i = 0; i < gctx->route_count; i++) {
|
||||||
route = &gctx->routes[i];
|
route = &gctx->routes[i];
|
||||||
|
@ -95,8 +108,17 @@ void http_loop_route_request(event_loop_conn *conn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// TODO
|
case http_route_regex:
|
||||||
case http_route_regex:;
|
if (regexec(route->regex, ctx->req.path, HTTP_MAX_REGEX_GROUPS,
|
||||||
|
ctx->req.regex_groups, 0) == 0) {
|
||||||
|
path_matched = true;
|
||||||
|
|
||||||
|
if (ctx->req.method == route->method) {
|
||||||
|
ctx->route = route;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
src/main.c
20
src/main.c
|
@ -25,10 +25,26 @@ bool lander_get_index(event_loop_conn *conn) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool lander_get_entry(event_loop_conn *conn) {
|
||||||
|
http_loop_ctx *ctx = conn->ctx;
|
||||||
|
|
||||||
|
size_t match_len =
|
||||||
|
ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so;
|
||||||
|
info("matched: %.*s", match_len,
|
||||||
|
&ctx->req.path[ctx->req.regex_groups[1].rm_so]);
|
||||||
|
conn->state = event_loop_conn_state_res;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
http_route routes[] = {{.type = http_route_literal,
|
http_route routes[] = {{.type = http_route_literal,
|
||||||
.method = http_get,
|
.method = http_get,
|
||||||
.path = "/",
|
.path = "/",
|
||||||
.steps = {lander_get_index, NULL}}};
|
.steps = {lander_get_index, NULL}},
|
||||||
|
{.type = http_route_regex,
|
||||||
|
.method = http_get,
|
||||||
|
.path = "^/\\([^/]\\+\\)$",
|
||||||
|
.steps = {lander_get_entry, NULL}}};
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
setvbuf(stdout, NULL, _IONBF, 0);
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
|
@ -50,5 +66,5 @@ int main() {
|
||||||
gctx->route_count = sizeof(routes) / sizeof(routes[0]);
|
gctx->route_count = sizeof(routes) / sizeof(routes[0]);
|
||||||
event_loop *el = http_loop_init(gctx);
|
event_loop *el = http_loop_init(gctx);
|
||||||
|
|
||||||
event_loop_run(el, 8000);
|
http_loop_run(el, 8000);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue