feat: allow matching regex routes
parent
dd0ba31506
commit
464753d627
|
@ -1,12 +1,14 @@
|
|||
#ifndef HTTP
|
||||
#define HTTP
|
||||
|
||||
#include <regex.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "picohttpparser.h"
|
||||
|
||||
#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_header_names[];
|
||||
|
@ -33,6 +35,7 @@ typedef struct http_request {
|
|||
size_t path_len;
|
||||
const char *query;
|
||||
size_t query_len;
|
||||
regmatch_t regex_groups[HTTP_MAX_REGEX_GROUPS];
|
||||
struct phr_header headers[HTTP_MAX_ALLOWED_HEADERS];
|
||||
} 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);
|
||||
|
||||
void http_loop_run(event_loop *el, int port);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#include "http_loop.h"
|
||||
#include <regex.h>
|
||||
|
||||
#include "http.h"
|
||||
#include "http_loop.h"
|
||||
#include "log.h"
|
||||
|
||||
bool http_loop_handle_request(event_loop_conn *conn) {
|
||||
// Prevents the request handler function from looping indefinitely without
|
||||
|
@ -54,3 +57,28 @@ event_loop *http_loop_init(http_loop_gctx *gctx) {
|
|||
|
||||
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;
|
||||
size_t method_len;
|
||||
char *path;
|
||||
size_t path_len;
|
||||
|
||||
int res = phr_parse_request(
|
||||
(const char *)&conn->rbuf[conn->rbuf_read],
|
||||
conn->rbuf_size - conn->rbuf_read, &method, &method_len, &req->path,
|
||||
&req->path_len, &req->minor_version, req->headers, &num_headers, 0);
|
||||
int res =
|
||||
phr_parse_request((const char *)&conn->rbuf[conn->rbuf_read],
|
||||
conn->rbuf_size - conn->rbuf_read, &method, &method_len,
|
||||
(const char **)&path, &path_len, &req->minor_version,
|
||||
req->headers, &num_headers, 0);
|
||||
|
||||
if (res == -1) {
|
||||
return http_parse_error_invalid;
|
||||
|
@ -45,16 +48,16 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) {
|
|||
i = 0;
|
||||
bool no_query = true;
|
||||
|
||||
while (no_query && i < req->path_len) {
|
||||
if (req->path[i] == '?') {
|
||||
while (no_query && i < path_len) {
|
||||
if (path[i] == '?') {
|
||||
// Ensure we don't store an invalid pointer if the request simply ends
|
||||
// with '?'
|
||||
if (i + 1 < req->path_len) {
|
||||
req->query = &req->path[i + 1];
|
||||
req->query_len = req->path_len - (i + 1);
|
||||
req->query = &path[i + 1];
|
||||
req->query_len = path_len - (i + 1);
|
||||
}
|
||||
|
||||
req->path_len = i;
|
||||
path_len = i;
|
||||
|
||||
no_query = false;
|
||||
}
|
||||
|
@ -62,6 +65,15 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) {
|
|||
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
|
||||
if (no_query) {
|
||||
req->query = NULL;
|
||||
|
@ -80,6 +92,7 @@ void http_loop_route_request(event_loop_conn *conn) {
|
|||
|
||||
http_route *route;
|
||||
bool path_matched = false;
|
||||
char c;
|
||||
|
||||
for (size_t i = 0; i < gctx->route_count; i++) {
|
||||
route = &gctx->routes[i];
|
||||
|
@ -95,8 +108,17 @@ void http_loop_route_request(event_loop_conn *conn) {
|
|||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
.method = http_get,
|
||||
.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() {
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
@ -50,5 +66,5 @@ int main() {
|
|||
gctx->route_count = sizeof(routes) / sizeof(routes[0]);
|
||||
event_loop *el = http_loop_init(gctx);
|
||||
|
||||
event_loop_run(el, 8000);
|
||||
http_loop_run(el, 8000);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue