Compare commits
16 Commits
1f63f06e0c
...
115baecde8
Author | SHA1 | Date |
---|---|---|
Jef Roosens | 115baecde8 | |
Jef Roosens | cf4451740f | |
Jef Roosens | 2ce49a3347 | |
Jef Roosens | 6eb965adcd | |
Jef Roosens | 115bf74456 | |
Jef Roosens | 3ae1b62dd0 | |
Jef Roosens | fbd41f7e4e | |
Jef Roosens | 9fa009ccf4 | |
Jef Roosens | e29e02ff85 | |
Jef Roosens | d739157fb1 | |
Jef Roosens | 9dbdb0b089 | |
Jef Roosens | f652fa08c1 | |
Jef Roosens | de4e509c9c | |
Jef Roosens | 386d83ec93 | |
Jef Roosens | 0e1d5d3f23 | |
Jef Roosens | 71cf5a5981 |
2
Makefile
2
Makefile
|
@ -73,7 +73,7 @@ $(BINS_TEST): %: %.c.o $(LIB)
|
||||||
$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
|
$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
|
||||||
mkdir -p $(dir $@)
|
mkdir -p $(dir $@)
|
||||||
$(CC) $(_CFLAGS) -I$(TEST_DIR) \
|
$(CC) $(_CFLAGS) -I$(TEST_DIR) \
|
||||||
-I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \
|
-I$(SRC_DIR)/_include \
|
||||||
-c $< -o $@
|
-c $< -o $@
|
||||||
|
|
||||||
# =====EXAMPLES=====
|
# =====EXAMPLES=====
|
||||||
|
|
|
@ -21,15 +21,19 @@ lnm_http_step_err slow_step(lnm_http_conn *conn) {
|
||||||
int main() {
|
int main() {
|
||||||
lnm_http_loop *hl;
|
lnm_http_loop *hl;
|
||||||
lnm_http_step *step = NULL;
|
lnm_http_step *step = NULL;
|
||||||
lnm_http_route *route;
|
|
||||||
|
|
||||||
lnm_http_loop_init(&hl, NULL, ctx_init,
|
lnm_http_loop_init(&hl, NULL, ctx_init,
|
||||||
ctx_reset,
|
ctx_reset,
|
||||||
ctx_free);
|
ctx_free);
|
||||||
|
|
||||||
lnm_http_step_append(&step, slow_step, true);
|
lnm_http_router *router;
|
||||||
lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step);
|
lnm_http_router_init(&router);
|
||||||
lnm_http_loop_route_add(hl, route);
|
|
||||||
|
lnm_http_route *route;
|
||||||
|
lnm_http_router_add(&route, router, lnm_http_method_get, "/");
|
||||||
|
lnm_http_route_step_append(route, slow_step, true);
|
||||||
|
|
||||||
|
lnm_http_loop_router_set(hl, router);
|
||||||
|
|
||||||
lnm_log_init_global();
|
lnm_log_init_global();
|
||||||
lnm_log_register_stdout(lnm_log_level_debug);
|
lnm_log_register_stdout(lnm_log_level_debug);
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "lnm/http/req.h"
|
||||||
|
#include "lnm/log.h"
|
||||||
|
#include "lnm/loop.h"
|
||||||
|
#include "lnm/http/loop.h"
|
||||||
|
|
||||||
|
lnm_err ctx_init(void **c_ctx, void *gctx) {
|
||||||
|
*c_ctx = NULL;
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ctx_reset(void *c_ctx) {}
|
||||||
|
void ctx_free(void *c_ctx) {}
|
||||||
|
|
||||||
|
lnm_http_step_err print_step(lnm_http_conn *conn) {
|
||||||
|
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||||
|
|
||||||
|
const char *key;
|
||||||
|
size_t key_len = lnm_http_req_route_segment(&key, &ctx->req, "key") ;
|
||||||
|
lnm_linfo("main", "key: %.*s", key_len, key);
|
||||||
|
return lnm_http_step_err_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_step_err print_step2(lnm_http_conn *conn) {
|
||||||
|
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||||
|
|
||||||
|
const char *key;
|
||||||
|
size_t key_len = lnm_http_req_route_segment(&key, &ctx->req, "key") ;
|
||||||
|
lnm_linfo("main", "yuhh key: %.*s", key_len, key);
|
||||||
|
return lnm_http_step_err_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_step_err print_step3(lnm_http_conn *conn) {
|
||||||
|
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||||
|
|
||||||
|
const char *key;
|
||||||
|
size_t key_len = lnm_http_req_route_segment(&key, &ctx->req, "cool") ;
|
||||||
|
lnm_linfo("main", "cool: %.*s", key_len, key);
|
||||||
|
return lnm_http_step_err_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
lnm_http_loop *hl;
|
||||||
|
|
||||||
|
lnm_http_loop_init(&hl, NULL, ctx_init,
|
||||||
|
ctx_reset,
|
||||||
|
ctx_free);
|
||||||
|
|
||||||
|
lnm_http_router *router;
|
||||||
|
lnm_http_router_init(&router);
|
||||||
|
|
||||||
|
lnm_http_route *route;
|
||||||
|
lnm_http_router_add(&route, router, lnm_http_method_get, "/emma");
|
||||||
|
lnm_http_router_add(&route, router, lnm_http_method_get, "/:key");
|
||||||
|
lnm_http_route_step_append(route, print_step, false);
|
||||||
|
lnm_http_router_add(&route, router, lnm_http_method_get, "/:key/two");
|
||||||
|
lnm_http_route_step_append(route, print_step2, false);
|
||||||
|
lnm_http_router_add(&route, router, lnm_http_method_get, "/*cool");
|
||||||
|
lnm_http_route_step_append(route, print_step3, false);
|
||||||
|
|
||||||
|
lnm_http_loop_router_set(hl, router);
|
||||||
|
|
||||||
|
lnm_log_init_global();
|
||||||
|
lnm_log_register_stdout(lnm_log_level_debug);
|
||||||
|
|
||||||
|
printf("res = %i\n", lnm_http_loop_run(hl, 8080, 1, 0));
|
||||||
|
}
|
|
@ -32,6 +32,9 @@ typedef enum lnm_err {
|
||||||
lnm_err_not_setup,
|
lnm_err_not_setup,
|
||||||
lnm_err_bad_regex,
|
lnm_err_bad_regex,
|
||||||
lnm_err_not_found,
|
lnm_err_not_found,
|
||||||
|
lnm_err_already_present,
|
||||||
|
lnm_err_invalid_route,
|
||||||
|
lnm_err_overlapping_route,
|
||||||
} lnm_err;
|
} lnm_err;
|
||||||
|
|
||||||
typedef struct lnm_loop lnm_http_loop;
|
typedef struct lnm_loop lnm_http_loop;
|
||||||
|
@ -86,4 +89,12 @@ uint64_t lnm_atoi(const char *s, size_t len);
|
||||||
*/
|
*/
|
||||||
uint64_t lnm_digits(uint64_t num);
|
uint64_t lnm_digits(uint64_t num);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given nul-terminated string solely consists of ASCII
|
||||||
|
* characters.
|
||||||
|
*
|
||||||
|
* @param s nul-terminated string to check
|
||||||
|
*/
|
||||||
|
bool lnm_is_ascii(const char *s);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -13,6 +13,7 @@ typedef enum lnm_http_method {
|
||||||
lnm_http_method_patch,
|
lnm_http_method_patch,
|
||||||
lnm_http_method_delete,
|
lnm_http_method_delete,
|
||||||
lnm_http_method_head,
|
lnm_http_method_head,
|
||||||
|
lnm_http_method_total,
|
||||||
} lnm_http_method;
|
} lnm_http_method;
|
||||||
|
|
||||||
extern const char *lnm_http_status_names[][32];
|
extern const char *lnm_http_status_names[][32];
|
||||||
|
|
|
@ -6,15 +6,7 @@
|
||||||
#include "lnm/common.h"
|
#include "lnm/common.h"
|
||||||
#include "lnm/http/req.h"
|
#include "lnm/http/req.h"
|
||||||
#include "lnm/http/res.h"
|
#include "lnm/http/res.h"
|
||||||
|
#include "lnm/http/route.h"
|
||||||
typedef enum lnm_http_step_err {
|
|
||||||
lnm_http_step_err_done = 0,
|
|
||||||
lnm_http_step_err_io_needed,
|
|
||||||
lnm_http_step_err_close,
|
|
||||||
lnm_http_step_err_res,
|
|
||||||
} lnm_http_step_err;
|
|
||||||
|
|
||||||
typedef lnm_http_step_err (*lnm_http_step_fn)(lnm_http_conn *conn);
|
|
||||||
|
|
||||||
typedef lnm_err (*lnm_http_ctx_init_fn)(void **c_ctx, void *gctx);
|
typedef lnm_err (*lnm_http_ctx_init_fn)(void **c_ctx, void *gctx);
|
||||||
|
|
||||||
|
@ -32,47 +24,7 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx,
|
||||||
lnm_http_ctx_reset_fn ctx_reset,
|
lnm_http_ctx_reset_fn ctx_reset,
|
||||||
lnm_http_ctx_free_fn ctx_free);
|
lnm_http_ctx_free_fn ctx_free);
|
||||||
|
|
||||||
/**
|
void lnm_http_loop_router_set(lnm_http_loop *hl, lnm_http_router *router);
|
||||||
* Append the given step fn to the step.
|
|
||||||
*
|
|
||||||
* @param out both the previous step to append the new step to, and the output
|
|
||||||
* variable to which the new step is appended
|
|
||||||
* @param fn step function
|
|
||||||
* @param blocking whether the step is blocking or not
|
|
||||||
*/
|
|
||||||
lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step_fn fn,
|
|
||||||
bool blocking);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new route of type literal.
|
|
||||||
*
|
|
||||||
* @param out where to store pointer to new `lnm_http_route`
|
|
||||||
* @param path literal path to match
|
|
||||||
* @param step step to process request with
|
|
||||||
*/
|
|
||||||
lnm_err lnm_http_route_init_literal(lnm_http_route **out,
|
|
||||||
lnm_http_method method, const char *path,
|
|
||||||
lnm_http_step *step);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new route of type regex.
|
|
||||||
*
|
|
||||||
* @param out where to store pointer to new `lnm_http_route`
|
|
||||||
* @param pattern regex pattern
|
|
||||||
* @param regex_group_count how many regex groups are contained in the pattern
|
|
||||||
* @param step step to process request with
|
|
||||||
*/
|
|
||||||
lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method,
|
|
||||||
const char *pattern, int regex_group_count,
|
|
||||||
lnm_http_step *step);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new route to the HTTP route.
|
|
||||||
*
|
|
||||||
* @param hl HTTP loop to modify
|
|
||||||
* @param route route to add
|
|
||||||
*/
|
|
||||||
lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route);
|
|
||||||
|
|
||||||
lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,
|
lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,
|
||||||
size_t epoll_threads, size_t worker_threads);
|
size_t epoll_threads, size_t worker_threads);
|
||||||
|
@ -106,10 +58,7 @@ typedef enum lnm_http_loop_state {
|
||||||
} lnm_http_loop_state;
|
} lnm_http_loop_state;
|
||||||
|
|
||||||
typedef struct lnm_http_loop_gctx {
|
typedef struct lnm_http_loop_gctx {
|
||||||
struct {
|
lnm_http_router *router;
|
||||||
lnm_http_route **arr;
|
|
||||||
size_t len;
|
|
||||||
} routes;
|
|
||||||
lnm_http_ctx_init_fn ctx_init;
|
lnm_http_ctx_init_fn ctx_init;
|
||||||
lnm_http_ctx_reset_fn ctx_reset;
|
lnm_http_ctx_reset_fn ctx_reset;
|
||||||
lnm_http_ctx_free_fn ctx_free;
|
lnm_http_ctx_free_fn ctx_free;
|
||||||
|
@ -122,7 +71,6 @@ typedef struct lnm_http_loop_ctx {
|
||||||
lnm_http_loop_state state;
|
lnm_http_loop_state state;
|
||||||
lnm_http_req req;
|
lnm_http_req req;
|
||||||
lnm_http_res res;
|
lnm_http_res res;
|
||||||
lnm_http_route *route;
|
|
||||||
lnm_http_step *cur_step;
|
lnm_http_step *cur_step;
|
||||||
lnm_http_loop_gctx *g;
|
lnm_http_loop_gctx *g;
|
||||||
void *c;
|
void *c;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "lnm/common.h"
|
#include "lnm/common.h"
|
||||||
#include "lnm/http/consts.h"
|
#include "lnm/http/consts.h"
|
||||||
|
#include "lnm/http/route.h"
|
||||||
|
|
||||||
#define LNM_HTTP_MAX_REQ_HEADERS 32
|
#define LNM_HTTP_MAX_REQ_HEADERS 32
|
||||||
#define LNM_HTTP_MAX_REGEX_GROUPS 4
|
#define LNM_HTTP_MAX_REGEX_GROUPS 4
|
||||||
|
@ -35,6 +36,7 @@ typedef struct lnm_http_req {
|
||||||
} buf;
|
} buf;
|
||||||
int minor_version;
|
int minor_version;
|
||||||
lnm_http_method method;
|
lnm_http_method method;
|
||||||
|
lnm_http_route_match route_match;
|
||||||
struct {
|
struct {
|
||||||
size_t o;
|
size_t o;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -108,4 +110,14 @@ lnm_err lnm_http_req_header_get(const char **out, size_t *out_len,
|
||||||
lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len,
|
lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len,
|
||||||
lnm_http_req *req, const char *name);
|
lnm_http_req *req, const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a named key segment from the matched route.
|
||||||
|
*
|
||||||
|
* @param out where to write pointer to string
|
||||||
|
* @param key key of the segment
|
||||||
|
* @return length of the outputted char buffer, or 0 if the key doesn't exist
|
||||||
|
*/
|
||||||
|
size_t lnm_http_req_route_segment(const char **out, lnm_http_req *req,
|
||||||
|
const char *key);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
#ifndef LNM_HTTP_ROUTE
|
||||||
|
#define LNM_HTTP_ROUTE
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "lnm/common.h"
|
||||||
|
#include "lnm/http/consts.h"
|
||||||
|
|
||||||
|
#define LNM_HTTP_MAX_KEY_SEGMENTS 4
|
||||||
|
|
||||||
|
typedef enum lnm_http_step_err {
|
||||||
|
lnm_http_step_err_done = 0,
|
||||||
|
lnm_http_step_err_io_needed,
|
||||||
|
lnm_http_step_err_close,
|
||||||
|
lnm_http_step_err_res,
|
||||||
|
} lnm_http_step_err;
|
||||||
|
|
||||||
|
typedef lnm_http_step_err (*lnm_http_step_fn)(lnm_http_conn *conn);
|
||||||
|
|
||||||
|
typedef struct lnm_http_route lnm_http_route;
|
||||||
|
|
||||||
|
typedef struct lnm_http_route_match_segment {
|
||||||
|
size_t start;
|
||||||
|
size_t len;
|
||||||
|
} lnm_http_route_match_segment;
|
||||||
|
|
||||||
|
typedef struct lnm_http_route_match {
|
||||||
|
const lnm_http_route *route;
|
||||||
|
lnm_http_method method;
|
||||||
|
lnm_http_route_match_segment key_segments[LNM_HTTP_MAX_KEY_SEGMENTS];
|
||||||
|
} lnm_http_route_match;
|
||||||
|
|
||||||
|
typedef struct lnm_http_router lnm_http_router;
|
||||||
|
|
||||||
|
typedef enum lnm_http_route_err {
|
||||||
|
lnm_http_route_err_match = 0,
|
||||||
|
lnm_http_route_err_unknown_route = 1,
|
||||||
|
lnm_http_route_err_unknown_method = 2,
|
||||||
|
} lnm_http_route_err;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate and initialize a new http_router.
|
||||||
|
*/
|
||||||
|
lnm_err lnm_http_router_init(lnm_http_router **out);
|
||||||
|
|
||||||
|
void lnm_http_router_free(lnm_http_router *router);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new path & method in the given router, returning a handle to the
|
||||||
|
* newly created route struct.
|
||||||
|
*/
|
||||||
|
lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router,
|
||||||
|
lnm_http_method method, const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the two routers have any conflicting parts.
|
||||||
|
*/
|
||||||
|
bool lnm_http_router_conflicts(const lnm_http_router *r1,
|
||||||
|
const lnm_http_router *r2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two routers, with the result ending up in r1. This is equivalent to
|
||||||
|
* nesting a router on '/'.
|
||||||
|
*/
|
||||||
|
lnm_err lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integrate the child router into the parent routing, mounting its paths on the
|
||||||
|
* given prefix.
|
||||||
|
*/
|
||||||
|
lnm_err lnm_http_router_nest(lnm_http_router *parent, lnm_http_router *child,
|
||||||
|
const char *prefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route the given path & method.
|
||||||
|
*/
|
||||||
|
lnm_http_route_err lnm_http_router_route(lnm_http_route_match *out,
|
||||||
|
const lnm_http_router *router,
|
||||||
|
lnm_http_method method,
|
||||||
|
const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a path segment using its name.
|
||||||
|
*
|
||||||
|
* @return NULL if not found, otherwise pointer to the match segment struct
|
||||||
|
* representing the key
|
||||||
|
*/
|
||||||
|
const lnm_http_route_match_segment *
|
||||||
|
lnm_http_route_match_get(lnm_http_route_match *match, const char *key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the given step function to the route's step list.
|
||||||
|
*/
|
||||||
|
lnm_err lnm_http_route_step_append(lnm_http_route *route, lnm_http_step_fn fn,
|
||||||
|
bool blocking);
|
||||||
|
|
||||||
|
#endif
|
|
@ -5,42 +5,49 @@
|
||||||
|
|
||||||
#include "lnm/http/loop.h"
|
#include "lnm/http/loop.h"
|
||||||
|
|
||||||
|
typedef struct lnm_http_route_segment_trie {
|
||||||
|
struct lnm_http_route_segment_trie *children[128];
|
||||||
|
size_t index;
|
||||||
|
bool represents_segment;
|
||||||
|
} lnm_http_route_segment_trie;
|
||||||
|
|
||||||
|
struct lnm_http_route {
|
||||||
|
lnm_http_route_segment_trie *key_segments;
|
||||||
|
lnm_http_step *step;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lnm_http_router {
|
||||||
|
struct lnm_http_router *exact_children[128];
|
||||||
|
struct lnm_http_router *single_segment_child;
|
||||||
|
lnm_http_route *multi_segment_routes[lnm_http_method_total];
|
||||||
|
lnm_http_route *routes[lnm_http_method_total];
|
||||||
|
bool represents_route;
|
||||||
|
};
|
||||||
|
|
||||||
|
lnm_err lnm_http_route_segment_trie_init(lnm_http_route_segment_trie **out);
|
||||||
|
|
||||||
|
void lnm_http_route_segment_trie_free(lnm_http_route_segment_trie *trie);
|
||||||
|
|
||||||
|
lnm_err lnm_http_route_key_segment_insert(lnm_http_route *route,
|
||||||
|
const char *key, size_t key_len,
|
||||||
|
size_t index);
|
||||||
typedef struct lnm_http_step {
|
typedef struct lnm_http_step {
|
||||||
lnm_http_step_fn fn;
|
lnm_http_step_fn fn;
|
||||||
struct lnm_http_step *next;
|
struct lnm_http_step *next;
|
||||||
bool blocking;
|
bool blocking;
|
||||||
} lnm_http_step;
|
} lnm_http_step;
|
||||||
|
|
||||||
typedef enum lnm_http_route_type {
|
|
||||||
lnm_http_route_type_literal = 0,
|
|
||||||
lnm_http_route_type_regex,
|
|
||||||
} lnm_http_route_type;
|
|
||||||
|
|
||||||
typedef struct lnm_http_route {
|
|
||||||
union {
|
|
||||||
regex_t *regex;
|
|
||||||
const char *s;
|
|
||||||
} route;
|
|
||||||
lnm_http_method method;
|
|
||||||
lnm_http_route_type type;
|
|
||||||
int regex_group_count;
|
|
||||||
lnm_http_step *step;
|
|
||||||
} lnm_http_route;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new empty route.
|
|
||||||
*
|
|
||||||
* @param out where to store pointer to new `lnm_http_route`
|
|
||||||
*/
|
|
||||||
lnm_err lnm_http_route_init(lnm_http_route **out);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a first step.
|
* Initialize a first step.
|
||||||
*
|
*
|
||||||
* @param out where to store pointer to new `lnm_http_step`
|
* @param out where to store pointer to new `lnm_http_step`
|
||||||
* @param fn step function associated with the step
|
* @param fn step function associated with the step
|
||||||
*/
|
*/
|
||||||
lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn);
|
lnm_err lnm_http_step_init(lnm_http_step **out);
|
||||||
|
|
||||||
|
lnm_err lnm_http_route_init(lnm_http_route **out);
|
||||||
|
|
||||||
|
void lnm_http_route_free(lnm_http_route *route);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new global context object.
|
* Initialize a new global context object.
|
||||||
|
|
|
@ -28,94 +28,9 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx,
|
||||||
return lnm_err_ok;
|
return lnm_err_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step_fn fn,
|
void lnm_http_loop_router_set(lnm_http_loop *hl, lnm_http_router *router) {
|
||||||
bool blocking) {
|
|
||||||
lnm_http_step *step = calloc(1, sizeof(lnm_http_step));
|
|
||||||
|
|
||||||
if (step == NULL) {
|
|
||||||
return lnm_err_failed_alloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
step->fn = fn;
|
|
||||||
step->blocking = blocking;
|
|
||||||
|
|
||||||
if ((*out) != NULL) {
|
|
||||||
(*out)->next = step;
|
|
||||||
}
|
|
||||||
|
|
||||||
*out = step;
|
|
||||||
|
|
||||||
return lnm_err_ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
lnm_err lnm_http_route_init(lnm_http_route **out) {
|
|
||||||
lnm_http_route *route = calloc(1, sizeof(lnm_http_route));
|
|
||||||
|
|
||||||
if (route == NULL) {
|
|
||||||
return lnm_err_failed_alloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
*out = route;
|
|
||||||
|
|
||||||
return lnm_err_ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
lnm_err lnm_http_route_init_literal(lnm_http_route **out,
|
|
||||||
lnm_http_method method, const char *path,
|
|
||||||
lnm_http_step *step) {
|
|
||||||
LNM_RES(lnm_http_route_init(out));
|
|
||||||
|
|
||||||
(*out)->type = lnm_http_route_type_literal;
|
|
||||||
(*out)->method = method;
|
|
||||||
(*out)->route.s = path;
|
|
||||||
(*out)->step = step;
|
|
||||||
|
|
||||||
return lnm_err_ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method,
|
|
||||||
const char *pattern, int regex_group_count,
|
|
||||||
lnm_http_step *step) {
|
|
||||||
regex_t *regex = calloc(1, sizeof(regex_t));
|
|
||||||
|
|
||||||
if (regex == NULL) {
|
|
||||||
return lnm_err_failed_alloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (regcomp(regex, pattern, REG_EXTENDED) != 0) {
|
|
||||||
free(regex);
|
|
||||||
return lnm_err_bad_regex;
|
|
||||||
}
|
|
||||||
|
|
||||||
LNM_RES2(lnm_http_route_init(out), free(regex));
|
|
||||||
|
|
||||||
(*out)->method = method;
|
|
||||||
(*out)->type = lnm_http_route_type_regex;
|
|
||||||
(*out)->route.regex = regex;
|
|
||||||
(*out)->regex_group_count = regex_group_count;
|
|
||||||
(*out)->step = step;
|
|
||||||
|
|
||||||
return lnm_err_ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route) {
|
|
||||||
lnm_http_loop_gctx *gctx = hl->gctx;
|
lnm_http_loop_gctx *gctx = hl->gctx;
|
||||||
|
gctx->router = router;
|
||||||
lnm_http_route **new_routes =
|
|
||||||
gctx->routes.len > 0
|
|
||||||
? realloc(gctx->routes.arr,
|
|
||||||
(gctx->routes.len + 1) * sizeof(lnm_http_route *))
|
|
||||||
: malloc(sizeof(lnm_http_route *));
|
|
||||||
|
|
||||||
if (new_routes == NULL) {
|
|
||||||
return lnm_err_failed_alloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_routes[gctx->routes.len] = route;
|
|
||||||
gctx->routes.arr = new_routes;
|
|
||||||
gctx->routes.len++;
|
|
||||||
|
|
||||||
return lnm_err_ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,
|
lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,
|
||||||
|
|
|
@ -42,7 +42,6 @@ void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx) {
|
||||||
lnm_http_req_reset(&ctx->req);
|
lnm_http_req_reset(&ctx->req);
|
||||||
lnm_http_res_reset(&ctx->res);
|
lnm_http_res_reset(&ctx->res);
|
||||||
|
|
||||||
ctx->route = NULL;
|
|
||||||
ctx->cur_step = NULL;
|
ctx->cur_step = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,55 +56,22 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) {
|
||||||
|
|
||||||
void lnm_http_loop_process_route(lnm_http_conn *conn) {
|
void lnm_http_loop_process_route(lnm_http_conn *conn) {
|
||||||
lnm_http_loop_ctx *ctx = conn->ctx;
|
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||||
lnm_http_loop_gctx *gctx = ctx->g;
|
const lnm_http_loop_gctx *gctx = ctx->g;
|
||||||
|
|
||||||
// 0: no match
|
switch (lnm_http_router_route(&ctx->req.route_match, gctx->router,
|
||||||
// 1: matched route, but not method
|
ctx->req.method,
|
||||||
// 2: fully matched route
|
ctx->req.buf.s + ctx->req.path.o)) {
|
||||||
int match_level = 0;
|
case lnm_http_route_err_match:
|
||||||
lnm_http_route *route;
|
ctx->cur_step = ctx->req.route_match.route->step;
|
||||||
|
ctx->state = lnm_http_loop_state_parse_headers;
|
||||||
for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) {
|
|
||||||
route = gctx->routes.arr[i];
|
|
||||||
bool matched_path = false;
|
|
||||||
|
|
||||||
switch (route->type) {
|
|
||||||
case lnm_http_route_type_literal:
|
|
||||||
matched_path = strncmp(route->route.s, ctx->req.buf.s + ctx->req.path.o,
|
|
||||||
ctx->req.path.len) == 0;
|
|
||||||
break;
|
break;
|
||||||
case lnm_http_route_type_regex:
|
case lnm_http_route_err_unknown_method:
|
||||||
matched_path =
|
|
||||||
regexec(route->route.regex, ctx->req.buf.s + ctx->req.path.o,
|
|
||||||
LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET routes also automatically route HEAD requests
|
|
||||||
bool matched_method = route->method == ctx->req.method ||
|
|
||||||
(route->method == lnm_http_method_get &&
|
|
||||||
ctx->req.method == lnm_http_method_head);
|
|
||||||
int new_match_level = 2 * matched_path + matched_method;
|
|
||||||
|
|
||||||
// Remember the previous match levels so we can return the correct status
|
|
||||||
// message
|
|
||||||
match_level = match_level < new_match_level ? new_match_level : match_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (match_level) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
ctx->res.status = lnm_http_status_not_found;
|
|
||||||
ctx->state = lnm_http_loop_state_first_res;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
ctx->res.status = lnm_http_status_method_not_allowed;
|
ctx->res.status = lnm_http_status_method_not_allowed;
|
||||||
ctx->state = lnm_http_loop_state_first_res;
|
ctx->state = lnm_http_loop_state_first_res;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case lnm_http_route_err_unknown_route:
|
||||||
ctx->route = route;
|
ctx->res.status = lnm_http_status_not_found;
|
||||||
ctx->cur_step = route->step;
|
ctx->state = lnm_http_loop_state_first_res;
|
||||||
ctx->state = lnm_http_loop_state_parse_headers;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,3 +112,19 @@ lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len,
|
||||||
|
|
||||||
return lnm_err_not_found;
|
return lnm_err_not_found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t lnm_http_req_route_segment(const char **out, lnm_http_req *req,
|
||||||
|
const char *key) {
|
||||||
|
const lnm_http_route_match_segment *segment =
|
||||||
|
lnm_http_route_match_get(&req->route_match, key);
|
||||||
|
|
||||||
|
if (segment == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != NULL) {
|
||||||
|
*out = req->buf.s + req->path.o + segment->start;
|
||||||
|
}
|
||||||
|
|
||||||
|
return segment->len;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
#include "lnm/http/loop_internal.h"
|
||||||
|
|
||||||
|
lnm_err lnm_http_route_init(lnm_http_route **out) {
|
||||||
|
lnm_http_route *route = calloc(1, sizeof(lnm_http_route));
|
||||||
|
|
||||||
|
if (route == NULL) {
|
||||||
|
return lnm_err_failed_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = route;
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lnm_http_route_free(lnm_http_route *route) {
|
||||||
|
if (route == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_route_segment_trie_free(route->key_segments);
|
||||||
|
free(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_err lnm_http_route_segment_trie_init(lnm_http_route_segment_trie **out) {
|
||||||
|
lnm_http_route_segment_trie *trie =
|
||||||
|
calloc(1, sizeof(lnm_http_route_segment_trie));
|
||||||
|
|
||||||
|
if (trie == NULL) {
|
||||||
|
return lnm_err_failed_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = trie;
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lnm_http_route_segment_trie_free(lnm_http_route_segment_trie *trie) {
|
||||||
|
if (trie == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 128; i++) {
|
||||||
|
lnm_http_route_segment_trie_free(trie->children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(trie);
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_err lnm_http_route_key_segment_insert(lnm_http_route *route,
|
||||||
|
const char *key, size_t key_len,
|
||||||
|
size_t index) {
|
||||||
|
if (route->key_segments == NULL) {
|
||||||
|
LNM_RES(lnm_http_route_segment_trie_init(&route->key_segments));
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_route_segment_trie *trie = route->key_segments;
|
||||||
|
|
||||||
|
for (size_t key_index = 0; key_index < key_len; key_index++) {
|
||||||
|
unsigned char c = key[key_index];
|
||||||
|
|
||||||
|
if (trie->children[c] == NULL) {
|
||||||
|
LNM_RES(lnm_http_route_segment_trie_init(&trie->children[c]));
|
||||||
|
}
|
||||||
|
|
||||||
|
trie = trie->children[c];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trie->represents_segment) {
|
||||||
|
return lnm_err_already_present;
|
||||||
|
}
|
||||||
|
|
||||||
|
trie->represents_segment = true;
|
||||||
|
trie->index = index;
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lnm_http_route_match_segment *
|
||||||
|
lnm_http_route_match_get(lnm_http_route_match *match, const char *key) {
|
||||||
|
if (match->route->key_segments == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_route_segment_trie *trie = match->route->key_segments;
|
||||||
|
|
||||||
|
while (*key != '\0') {
|
||||||
|
trie = trie->children[(unsigned char)*key];
|
||||||
|
|
||||||
|
if (trie == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
key++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trie->represents_segment) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &match->key_segments[trie->index];
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_err lnm_http_step_init(lnm_http_step **out) {
|
||||||
|
lnm_http_step *step = calloc(1, sizeof(lnm_http_step));
|
||||||
|
|
||||||
|
if (step == NULL) {
|
||||||
|
return lnm_err_failed_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = step;
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_err lnm_http_route_step_append(lnm_http_route *route, lnm_http_step_fn fn,
|
||||||
|
bool blocking) {
|
||||||
|
lnm_http_step **step_ptr = &route->step;
|
||||||
|
|
||||||
|
while (*step_ptr != NULL) {
|
||||||
|
step_ptr = &(*step_ptr)->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
LNM_RES(lnm_http_step_init(step_ptr));
|
||||||
|
(*step_ptr)->fn = fn;
|
||||||
|
(*step_ptr)->blocking = blocking;
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
|
@ -0,0 +1,347 @@
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "lnm/common.h"
|
||||||
|
#include "lnm/http/loop_internal.h"
|
||||||
|
|
||||||
|
lnm_err lnm_http_router_init(lnm_http_router **out) {
|
||||||
|
lnm_http_router *router = calloc(1, sizeof(lnm_http_router));
|
||||||
|
|
||||||
|
if (router == NULL) {
|
||||||
|
return lnm_err_failed_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = router;
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lnm_http_router_free(lnm_http_router *router) {
|
||||||
|
if (router == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 128; i++) {
|
||||||
|
lnm_http_router_free(router->exact_children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_router_free(router->single_segment_child);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < lnm_http_method_total; i++) {
|
||||||
|
lnm_http_route_free(router->routes[i]);
|
||||||
|
lnm_http_route_free(router->multi_segment_routes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router,
|
||||||
|
lnm_http_method method, const char *path) {
|
||||||
|
if (path[0] != '/' || !lnm_is_ascii(path)) {
|
||||||
|
return lnm_err_invalid_route;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_route *route;
|
||||||
|
LNM_RES(lnm_http_route_init(&route));
|
||||||
|
|
||||||
|
size_t key_segments_count = 0;
|
||||||
|
lnm_err res = lnm_err_ok;
|
||||||
|
|
||||||
|
while (*path != '\0') {
|
||||||
|
unsigned char c = *path;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case ':': {
|
||||||
|
// Match the segment content as the variable name
|
||||||
|
const char *next_slash_ptr = strchr(path + 1, '/');
|
||||||
|
|
||||||
|
// Account for segment being the final part of the route
|
||||||
|
const char *new_path =
|
||||||
|
next_slash_ptr == NULL ? strchr(path + 1, '\0') : next_slash_ptr;
|
||||||
|
size_t key_len = new_path - (path + 1);
|
||||||
|
|
||||||
|
if (key_len == 0) {
|
||||||
|
res = lnm_err_invalid_route;
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = lnm_http_route_key_segment_insert(route, path + 1, key_len,
|
||||||
|
key_segments_count);
|
||||||
|
|
||||||
|
if (res != lnm_err_ok) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_segments_count++;
|
||||||
|
|
||||||
|
if (http_router->single_segment_child == NULL) {
|
||||||
|
res = lnm_http_router_init(&http_router->single_segment_child);
|
||||||
|
|
||||||
|
if (res != lnm_err_ok) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http_router = http_router->single_segment_child;
|
||||||
|
path = new_path;
|
||||||
|
} break;
|
||||||
|
case '*': {
|
||||||
|
const char *next_slash_ptr = strchr(path + 1, '/');
|
||||||
|
|
||||||
|
// Star match should be at end of route
|
||||||
|
if (next_slash_ptr != NULL) {
|
||||||
|
res = lnm_err_invalid_route;
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *end = strchr(path + 1, '\0');
|
||||||
|
size_t key_len = end - (path + 1);
|
||||||
|
|
||||||
|
if (key_len == 0) {
|
||||||
|
res = lnm_err_invalid_route;
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = lnm_http_route_key_segment_insert(route, path + 1, key_len,
|
||||||
|
key_segments_count);
|
||||||
|
|
||||||
|
if (res != lnm_err_ok) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_segments_count++;
|
||||||
|
|
||||||
|
if (http_router->multi_segment_routes[method] != NULL) {
|
||||||
|
res = lnm_err_overlapping_route;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_router->multi_segment_routes[method] = route;
|
||||||
|
goto end;
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
if (http_router->exact_children[c] == NULL) {
|
||||||
|
res = lnm_http_router_init(&http_router->exact_children[c]);
|
||||||
|
|
||||||
|
if (res != lnm_err_ok) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http_router = http_router->exact_children[c];
|
||||||
|
path++;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_router->routes[method] != NULL) {
|
||||||
|
res = lnm_err_overlapping_route;
|
||||||
|
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_router->routes[method] = route;
|
||||||
|
http_router->represents_route = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (res != lnm_err_ok) {
|
||||||
|
lnm_http_route_free(route);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != NULL) {
|
||||||
|
*out = route;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lnm_http_route_err __lnm_http_router_route(lnm_http_route_match *out,
|
||||||
|
const lnm_http_router *router,
|
||||||
|
lnm_http_method method,
|
||||||
|
const char *path,
|
||||||
|
size_t path_index,
|
||||||
|
size_t matched_key_segments) {
|
||||||
|
if (path[path_index] == '\0') {
|
||||||
|
if (!router->represents_route) {
|
||||||
|
return lnm_http_route_err_unknown_route;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != NULL) {
|
||||||
|
out->route = router->routes[method];
|
||||||
|
}
|
||||||
|
|
||||||
|
return router->routes[method] == NULL ? lnm_http_route_err_unknown_method
|
||||||
|
: lnm_http_route_err_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_route_err res = lnm_http_route_err_unknown_route;
|
||||||
|
const lnm_http_router *exact_router =
|
||||||
|
router->exact_children[(unsigned char)path[path_index]];
|
||||||
|
|
||||||
|
if (exact_router != NULL) {
|
||||||
|
lnm_http_route_err sub_res = __lnm_http_router_route(
|
||||||
|
out, exact_router, method, path, path_index + 1, matched_key_segments);
|
||||||
|
|
||||||
|
if (sub_res == lnm_http_route_err_match) {
|
||||||
|
return lnm_http_route_err_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = LNM_MAX(res, sub_res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lnm_http_router *single_segment_router = router->single_segment_child;
|
||||||
|
|
||||||
|
if (single_segment_router != NULL) {
|
||||||
|
const char *next_slash_ptr = strchr(path + path_index, '/');
|
||||||
|
const char *new_path = next_slash_ptr == NULL
|
||||||
|
? strchr(path + path_index, '\0')
|
||||||
|
: next_slash_ptr;
|
||||||
|
size_t segment_len = new_path - (path + path_index);
|
||||||
|
|
||||||
|
if (segment_len > 0) {
|
||||||
|
lnm_http_route_err sub_res = __lnm_http_router_route(
|
||||||
|
out, single_segment_router, method, path, path_index + segment_len,
|
||||||
|
matched_key_segments + 1);
|
||||||
|
|
||||||
|
if (sub_res == lnm_http_route_err_match) {
|
||||||
|
// If match succeeds down the recursion, we can correctly set the
|
||||||
|
// matched segments when going back up the stack
|
||||||
|
if (out != NULL) {
|
||||||
|
out->key_segments[matched_key_segments].start = path_index;
|
||||||
|
out->key_segments[matched_key_segments].len = segment_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lnm_http_route_err_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = LNM_MAX(res, sub_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lnm_http_route *multi_segment_route =
|
||||||
|
router->multi_segment_routes[method];
|
||||||
|
|
||||||
|
if (multi_segment_route != NULL) {
|
||||||
|
const char *end_ptr = strchr(path + path_index, '\0');
|
||||||
|
size_t segment_len = end_ptr - (path + path_index);
|
||||||
|
|
||||||
|
// TODO do 405's make sense for star matches?
|
||||||
|
|
||||||
|
if (segment_len > 0) {
|
||||||
|
if (out != NULL) {
|
||||||
|
out->route = multi_segment_route;
|
||||||
|
out->key_segments[matched_key_segments].start = path_index;
|
||||||
|
out->key_segments[matched_key_segments].len = segment_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lnm_http_route_err_match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_route_err lnm_http_router_route(lnm_http_route_match *out,
|
||||||
|
const lnm_http_router *router,
|
||||||
|
lnm_http_method method,
|
||||||
|
const char *path) {
|
||||||
|
if (!lnm_is_ascii(path)) {
|
||||||
|
return lnm_http_route_err_unknown_route;
|
||||||
|
}
|
||||||
|
|
||||||
|
return __lnm_http_router_route(out, router, method, path, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lnm_http_router_conflicts(const lnm_http_router *r1,
|
||||||
|
const lnm_http_router *r2) {
|
||||||
|
// First check the literal routes for the routers
|
||||||
|
for (lnm_http_method m = 0; m < lnm_http_method_total; m++) {
|
||||||
|
if ((r1->routes[m] != NULL && r2->routes[m] != NULL) ||
|
||||||
|
(r1->multi_segment_routes[m] != NULL &&
|
||||||
|
r2->multi_segment_routes[m] != NULL)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((r1->single_segment_child != NULL && r2->single_segment_child != NULL) &&
|
||||||
|
lnm_http_router_conflicts(r1->single_segment_child,
|
||||||
|
r2->single_segment_child)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned char c = 0; c < 128; c++) {
|
||||||
|
if ((r1->exact_children[c] != NULL && r2->exact_children[c] != NULL) &&
|
||||||
|
lnm_http_router_conflicts(r1->exact_children[c],
|
||||||
|
r2->exact_children[c])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2) {
|
||||||
|
for (lnm_http_method m = 0; m < lnm_http_method_total; m++) {
|
||||||
|
if (r2->routes[m] != NULL) {
|
||||||
|
r1->routes[m] = r2->routes[m];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r2->multi_segment_routes[m] != NULL) {
|
||||||
|
r1->multi_segment_routes[m] = r2->multi_segment_routes[m];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r1->single_segment_child != NULL && r2->single_segment_child != NULL) {
|
||||||
|
__lnm_http_router_merge(r1->single_segment_child, r2->single_segment_child);
|
||||||
|
} else if (r2->single_segment_child != NULL) {
|
||||||
|
r1->single_segment_child = r2->single_segment_child;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned char c = 0; c < 128; c++) {
|
||||||
|
if (r1->exact_children[c] != NULL && r2->exact_children[c] != NULL) {
|
||||||
|
__lnm_http_router_merge(r1->exact_children[c], r2->exact_children[c]);
|
||||||
|
} else if (r2->exact_children[c] != NULL) {
|
||||||
|
r1->exact_children[c] = r2->exact_children[c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r1->represents_route = r1->represents_route || r2->represents_route;
|
||||||
|
|
||||||
|
free(r2);
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_err lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2) {
|
||||||
|
if (lnm_http_router_conflicts(r1, r2)) {
|
||||||
|
return lnm_err_overlapping_route;
|
||||||
|
}
|
||||||
|
|
||||||
|
__lnm_http_router_merge(r1, r2);
|
||||||
|
|
||||||
|
return lnm_err_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_err lnm_http_router_nest(lnm_http_router *parent, lnm_http_router *child,
|
||||||
|
const char *prefix) {
|
||||||
|
if (!lnm_is_ascii(prefix) || prefix[0] != '/') {
|
||||||
|
return lnm_err_invalid_route;
|
||||||
|
}
|
||||||
|
|
||||||
|
lnm_http_router *router = parent;
|
||||||
|
|
||||||
|
while (*prefix != '\0') {
|
||||||
|
unsigned char c = *prefix;
|
||||||
|
|
||||||
|
if (router->exact_children[c] == NULL) {
|
||||||
|
LNM_RES(lnm_http_router_init(&router->exact_children[c]));
|
||||||
|
}
|
||||||
|
|
||||||
|
router = router->exact_children[c];
|
||||||
|
prefix++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lnm_http_router_merge(router, child);
|
||||||
|
}
|
|
@ -55,3 +55,15 @@ uint64_t lnm_digits(uint64_t num) {
|
||||||
|
|
||||||
return digits;
|
return digits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool lnm_is_ascii(const char *s) {
|
||||||
|
bool valid = true;
|
||||||
|
|
||||||
|
while (valid && *s != '\0') {
|
||||||
|
valid = *s < 0;
|
||||||
|
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
#include "lnm/common.h"
|
||||||
|
#include "lnm/http/route.h"
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include "lnm/http/loop.h"
|
||||||
|
|
||||||
|
void test_routing_simple() {
|
||||||
|
lnm_http_router *router;
|
||||||
|
TEST_CHECK(lnm_http_router_init(&router) == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, router, lnm_http_method_get, "/test") == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, router, lnm_http_method_get, "/test/test2") == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, router, lnm_http_method_get, "/test/:hello") == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, router, lnm_http_method_get, "/test/:hello/:second") == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_route(NULL, router, lnm_http_method_get, "/test") == lnm_http_route_err_match);
|
||||||
|
TEST_CHECK(lnm_http_router_route(NULL, router, lnm_http_method_get, "/test2/t/e") == lnm_http_route_err_unknown_route);
|
||||||
|
TEST_CHECK(lnm_http_router_route(NULL, router, lnm_http_method_head, "/test/test2") == lnm_http_route_err_unknown_method);
|
||||||
|
TEST_CHECK(lnm_http_router_route(NULL, router, lnm_http_method_get, "/test/test2") == lnm_http_route_err_match);
|
||||||
|
|
||||||
|
lnm_http_route_match match;
|
||||||
|
TEST_CHECK(lnm_http_router_route(&match, router, lnm_http_method_get, "/test/test_var") == lnm_http_route_err_match);
|
||||||
|
TEST_CHECK(match.key_segments[0].start == 6);
|
||||||
|
TEST_CHECK(match.key_segments[0].len == 8);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_route(NULL, router, lnm_http_method_get, "/test/") == lnm_http_route_err_unknown_route);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_route(&match, router, lnm_http_method_get, "/test/test_var/secondvar") == lnm_http_route_err_match);
|
||||||
|
TEST_CHECK(match.key_segments[0].start == 6);
|
||||||
|
TEST_CHECK(match.key_segments[0].len == 8);
|
||||||
|
TEST_CHECK(match.key_segments[1].start == 15);
|
||||||
|
TEST_CHECK(match.key_segments[1].len == 9);
|
||||||
|
|
||||||
|
const lnm_http_route_match_segment *segment;
|
||||||
|
TEST_CHECK((segment = lnm_http_route_match_get(&match, "second")) != NULL);
|
||||||
|
TEST_CHECK(segment->start == 15);
|
||||||
|
TEST_CHECK(segment->len == 9);
|
||||||
|
TEST_CHECK((segment = lnm_http_route_match_get(&match, "yuhh")) == NULL);
|
||||||
|
TEST_CHECK((segment = lnm_http_route_match_get(&match, "hello")) != NULL);
|
||||||
|
TEST_CHECK(segment->start == 6);
|
||||||
|
TEST_CHECK(segment->len == 8);
|
||||||
|
|
||||||
|
lnm_http_router_free(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_routing_star() {
|
||||||
|
lnm_http_router *router;
|
||||||
|
TEST_CHECK(lnm_http_router_init(&router) == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, router, lnm_http_method_get, "/*key") == lnm_err_ok);
|
||||||
|
|
||||||
|
lnm_http_route_match match;
|
||||||
|
TEST_CHECK(lnm_http_router_route(&match, router, lnm_http_method_get, "/hello/world") == lnm_http_route_err_match);
|
||||||
|
TEST_CHECK(match.key_segments[0].start == 1);
|
||||||
|
TEST_CHECK(match.key_segments[0].len == 11);
|
||||||
|
|
||||||
|
lnm_http_router_free(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_routing_merge() {
|
||||||
|
lnm_http_router *rtr1, *rtr2;
|
||||||
|
lnm_http_route *rt1, *rt2;
|
||||||
|
lnm_http_route_match match;
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_init(&rtr1) == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_init(&rtr2) == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_add(&rt1, rtr1, lnm_http_method_get, "/*key") == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, rtr1, lnm_http_method_get, "/:key/hello") == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_add(&rt2, rtr2, lnm_http_method_get, "/test2") == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, rtr2, lnm_http_method_get, "/:key/hello2") == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_merge(rtr1, rtr2) == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_route(&match, rtr1, lnm_http_method_get, "/some/thing") == lnm_http_route_err_match);
|
||||||
|
TEST_CHECK(match.route == rt1);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_route(&match, rtr1, lnm_http_method_get, "/test2") == lnm_http_route_err_match);
|
||||||
|
TEST_CHECK(match.route == rt2);
|
||||||
|
|
||||||
|
lnm_http_router_free(rtr1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_routing_nest() {
|
||||||
|
lnm_http_router *r1, *r2;
|
||||||
|
lnm_http_route_match match;
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_init(&r1) == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_init(&r2) == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, r1, lnm_http_method_get, "/*key") == lnm_err_ok);
|
||||||
|
TEST_CHECK(lnm_http_router_add(NULL, r2, lnm_http_method_get, "/test/test2") == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_nest(r2, r1, "/test") == lnm_err_ok);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_route(&match, r2, lnm_http_method_get, "/test/test_var/secondvar") == lnm_http_route_err_match);
|
||||||
|
TEST_CHECK(match.key_segments[0].start == 6);
|
||||||
|
TEST_CHECK(match.key_segments[0].len == 18);
|
||||||
|
|
||||||
|
TEST_CHECK(lnm_http_router_route(&match, r2, lnm_http_method_get, "/test/test2") == lnm_http_route_err_match);
|
||||||
|
|
||||||
|
lnm_http_router_free(r2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_LIST = {
|
||||||
|
{ "routing simple", test_routing_simple },
|
||||||
|
{ "routing star", test_routing_star },
|
||||||
|
{ "routing merge", test_routing_merge },
|
||||||
|
{ "routing nest", test_routing_nest },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue