Compare commits

...

31 Commits

Author SHA1 Message Date
Jef Roosens 1ebeba2efc
feat: add http header param getter 2024-04-06 17:15:13 +02:00
Jef Roosens 421c5381e0
feat: rename some structs 2024-04-06 15:25:55 +02:00
Jef Roosens fbba7a5a14
feat: improve request header api functions 2024-04-06 15:17:36 +02:00
Jef Roosens f5c0db7733
fix: proper va_end usage 2024-04-06 15:16:14 +02:00
Jef Roosens 0e39ce3618
chore: refactor repository for cleaner development 2024-03-30 11:46:49 +01:00
Jef Roosens 5ff788c108
refactor(test): compile tests into single binary 2024-03-30 10:52:43 +01:00
Jef Roosens 195eb9eb48
chore: remove unneeded code; add throughput example 2024-03-09 20:28:50 +01:00
Jef Roosens 6eab5d616c
fix: flip is_ascii check 2024-03-02 22:51:40 +01:00
Jef Roosens 115baecde8
refactor(routing): moved some stuff, added some comments 2024-03-02 22:36:58 +01:00
Jef Roosens cf4451740f
feat(routing): implement router nesting 2024-03-02 11:26:51 +01:00
Jef Roosens 2ce49a3347
feat(routing): implement router merging 2024-03-02 11:11:54 +01:00
Jef Roosens 6eb965adcd
test(routing): add star matching test 2024-02-26 22:32:50 +01:00
Jef Roosens 115bf74456
feat(routing): multi-segment wildcard matches 2024-02-25 22:59:58 +01:00
Jef Roosens 3ae1b62dd0
feat(routing): make path segments easily accessible from request struct 2024-02-25 21:50:51 +01:00
Jef Roosens fbd41f7e4e
feat: implement router free function 2024-02-24 13:25:06 +01:00
Jef Roosens 9fa009ccf4
feat: provide access to key segments from step 2024-02-23 15:21:23 +01:00
Jef Roosens e29e02ff85
feat(routing): integrate new router into framework 2024-02-23 12:15:43 +01:00
Jef Roosens d739157fb1
refactor(routing): simply key segment api 2024-02-23 11:38:52 +01:00
Jef Roosens 9dbdb0b089
feat(routing): add api to access key segments by name 2024-02-22 22:31:00 +01:00
Jef Roosens f652fa08c1
feat(routing): support matching key segments in routes 2024-02-22 22:15:19 +01:00
Jef Roosens de4e509c9c
chore: started routing tests 2024-02-22 18:17:26 +01:00
Jef Roosens 386d83ec93
feat: add initial routing function 2024-02-22 17:58:39 +01:00
Jef Roosens 0e1d5d3f23
chore: add test folder 2024-02-22 17:43:30 +01:00
Jef Roosens 71cf5a5981
feat: initial trie routing structure 2024-02-22 11:42:56 +01:00
Jef Roosens 1f63f06e0c
fix: transparently support blocking work without worker threads 2024-02-14 12:43:58 +01:00
Jef Roosens 1b8ba305b5
chore: rename event loop states 2024-02-14 12:07:14 +01:00
Jef Roosens 64601b5f21
fix: use send instead of write to prevent SIGPIPE 2024-02-01 18:51:05 +01:00
Jef Roosens a96ae39523
feat: add blocking step support to http loop 2024-02-01 18:42:29 +01:00
Jef Roosens 9c01548256
feat: change lnm loop multithreading api 2024-02-01 15:37:35 +01:00
Jef Roosens ccb9b77cc8
feat: further lay groundwork for worker threads 2024-02-01 14:00:25 +01:00
Jef Roosens 3490c2dd42
feat: implement concurrent worker queue 2024-02-01 11:15:34 +01:00
34 changed files with 3438 additions and 438 deletions

View File

@ -3,7 +3,7 @@
-include config.mk -include config.mk
LIB := $(BUILD_DIR)/$(LIB_FILENAME) LIB_ARCHIVE := $(BUILD_DIR)/lib$(LIB).a
SRCS != find '$(SRC_DIR)' -iname '*.c' SRCS != find '$(SRC_DIR)' -iname '*.c'
SRCS_H != find include -iname '*.h' SRCS_H != find include -iname '*.h'
@ -17,54 +17,38 @@ OBJS_EXAMPLE := $(SRCS_EXAMPLE:%=$(BUILD_DIR)/%.o)
DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d)
BINS_TEST := $(OBJS_TEST:%.c.o=%) BIN_TEST := $(BUILD_DIR)/$(TEST_DIR)/runner
BINS_EXAMPLE := $(OBJS_EXAMPLE:%.c.o=%) BINS_EXAMPLE := $(OBJS_EXAMPLE:%.c.o=%)
TARGETS_TEST := $(BINS_TEST:%=test-%)
TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%)
TARGETS_EXAMPLE := $(BINS_EXAMPLE:%=example-%)
_CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra _CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra
.PHONY: all
all: lib
# =====COMPILATION===== # =====COMPILATION=====
# Utility used by the CI to lint # Utility used by the CI to lint
.PHONY: lib
$(LIB_ARCHIVE): $(OBJS)
ar -rcs $@ $^
.PHONY: objs .PHONY: objs
objs: $(OBJS) objs: $(OBJS)
.PHONY: lib
lib: $(LIB)
$(LIB): $(OBJS)
ar -rcs $@ $(OBJS)
$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c
mkdir -p $(dir $@) mkdir -p $(dir $@)
$(CC) -c $(_CFLAGS) $< -o $@ $(CC) -c $(_CFLAGS) $< -o $@
# =====TESTING===== # =====TESTING=====
.PHONY: test .PHONY: test
test: $(TARGETS_TEST) test: $(BIN_TEST)
'./$^'
.PHONY: test-mem .PHONY: test-mem
test-mem: $(TARGETS_MEM_TEST) test-mem: $(BIN_TEST)
valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full './$^'
.PHONY: $(TARGETS_TEST)
$(TARGETS_TEST): test-%: %
./$^
.PHONY: $(TARGETS_MEM_TEST)
$(TARGETS_MEM_TEST): test-mem-%: %
valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^
.PHONY: build-test .PHONY: build-test
build-test: $(BINS_TEST) build-test: $(BIN_TEST)
$(BINS_TEST): %: %.c.o $(LIB) $(BIN_TEST): $(OBJS_TEST) $(LIB_ARCHIVE)
$(CC) \ $(CC) -o $@ $^ $(_LDFLAGS)
$^ -o $@
# Along with the include directory, each test includes $(TEST_DIR) (which # Along with the include directory, each test includes $(TEST_DIR) (which
# contains the acutest.h header file), and the src directory of the module it's # contains the acutest.h header file), and the src directory of the module it's
@ -73,15 +57,15 @@ $(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=====
.PHONY: build-example .PHONY: build-example
build-example: $(BINS_EXAMPLE) build-example: $(BINS_EXAMPLE)
$(BINS_EXAMPLE): %: %.c.o $(LIB) $(BINS_EXAMPLE): %: %.c.o $(LIB_ARCHIVE)
$(CC) \ $(CC) $(LDFLAGS) \
$^ -o $@ $^ -o $@
# Example binaries link the resulting library # Example binaries link the resulting library
@ -92,22 +76,22 @@ $(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c
# =====MAINTENANCE===== # =====MAINTENANCE=====
.PHONY: lint .PHONY: lint
lint: lint:
clang-format -n --Werror \ @ clang-format -n --Werror \
$(filter-out $(THIRDPARTY),$(SRCS)) \ $(shell find '$(SRC_DIR)/$(LIB)' -iname '*.c') \
$(filter-out $(THIRDPARTY),$(SRCS_H)) \ $(shell find '$(SRC_DIR)/_include/$(LIB)' -iname '*.h') \
$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) $(shell find 'include/$(LIB)' -iname '*.h')
.PHONY: fmt .PHONY: fmt
fmt: fmt:
clang-format -i \ @ clang-format -i \
$(filter-out $(THIRDPARTY),$(SRCS)) \ $(shell find '$(SRC_DIR)/$(LIB)' -iname '*.c') \
$(filter-out $(THIRDPARTY),$(SRCS_H)) \ $(shell find '$(SRC_DIR)/_include/$(LIB)' -iname '*.h') \
$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) $(shell find 'include/$(LIB)' -iname '*.h')
.PHONY: check .PHONY: check
check: check:
mkdir -p $(BUILD_DIR)/cppcheck @ mkdir -p $(BUILD_DIR)/cppcheck
cppcheck \ @ cppcheck \
$(addprefix -I,$(INC_DIRS)) \ $(addprefix -I,$(INC_DIRS)) \
--cppcheck-build-dir=$(BUILD_DIR)/cppcheck \ --cppcheck-build-dir=$(BUILD_DIR)/cppcheck \
--error-exitcode=1 \ --error-exitcode=1 \
@ -116,7 +100,7 @@ check:
--check-level=exhaustive \ --check-level=exhaustive \
--quiet \ --quiet \
-j$(shell nproc) \ -j$(shell nproc) \
$(filter-out $(THIRDPARTY),$(SRCS)) $(shell find '$(SRC_DIR)/$(LIB)' -iname '*.c')
.PHONY: clean .PHONY: clean
clean: clean:

View File

@ -1,10 +1,9 @@
LIB_FILENAME = liblnm.a LIB = lnm
BUILD_DIR = build BUILD_DIR = build
SRC_DIR = src SRC_DIR = src
TEST_DIR = test TEST_DIR = test
EXAMPLE_DIR = example EXAMPLE_DIR = example
THIRDPARTY = src/picohttpparser.c include/picohttpparser.h
PUB_INC_DIR = include PUB_INC_DIR = include
INC_DIRS = $(PUB_INC_DIR) src/_include INC_DIRS = $(PUB_INC_DIR) src/_include

View File

@ -21,18 +21,22 @@ 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_init(&step, slow_step); 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);
lnm_http_loop_run(hl, 8080, 1); printf("res = %i\n", lnm_http_loop_run(hl, 8080, 1, 2));
} }

70
example/routing.c 100644
View File

@ -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));
}

View File

@ -0,0 +1,53 @@
#include "lnm/http/res.h"
#include "lnm/log.h"
#include "lnm/http/loop.h"
#include "lnm/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_err data_streamer(uint64_t *written, char *buf,
lnm_http_conn *conn, uint64_t offset,
uint64_t len) {
// Don't do anything, just let the application return random data stored in
// the read buffer. The goal is to benchmark the networking pipeline
*written = len;
return lnm_err_ok;
}
lnm_http_step_err step_fn(lnm_http_conn *conn) {
lnm_http_loop_ctx *ctx = conn->ctx;
uint64_t len = 1 << 30;
lnm_http_res_body_set_fn(&ctx->res, data_streamer, len);
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, "/");
lnm_http_route_step_append(route, step_fn, false);
lnm_http_loop_router_set(hl, router);
lnm_log_init_global();
lnm_log_register_stdout(lnm_log_level_warning);
printf("res = %i\n", lnm_http_loop_run(hl, 8080, 1, 0));
}

View File

@ -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,24 @@ 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);
/**
* Find the first case-insensitive occurence of s2 in s1.
*
* @param s1 pointer to string to look in
* @param s1_len length of s1
* @param s2 string to search for in s1
* @param s2_len length of s2
* @return pointer to start of matched string if found, or NULL otherwise
*/
const char *lnm_stristr(const char *s1, size_t s1_len, const char *s2,
size_t s2_len);
#endif #endif

View File

@ -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];

View File

@ -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,56 +24,10 @@ 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);
* Initialize a new step.
*
* @param out where to store pointer to new `lnm_http_step`
* @param fn step function
*/
lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn);
/** lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,
* Append the given step fn to the step. size_t epoll_threads, size_t worker_threads);
*
* @param out where to store pointer to new `lnm_http_step`
* @param step step to append new step to
* @param fn step function
*/
lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step,
lnm_http_step_fn fn);
/**
* 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, int thread_count);
void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key); void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key);
@ -112,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;
@ -128,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;

View File

@ -9,11 +9,16 @@
#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
typedef struct lnm_http_req_header { /**
* Internal representation of a header in a request, defined using offsets
* relative to the full buffer.
*/
typedef struct lnm_http_req_ihdr {
struct { struct {
size_t o; size_t o;
size_t len; size_t len;
@ -22,7 +27,7 @@ typedef struct lnm_http_req_header {
size_t o; size_t o;
size_t len; size_t len;
} value; } value;
} lnm_http_req_header; } lnm_http_req_ihhr;
/** /**
* Represents the parsed HTTP request * Represents the parsed HTTP request
@ -35,17 +40,17 @@ 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;
regmatch_t groups[LNM_HTTP_MAX_REGEX_GROUPS];
} path; } path;
struct { struct {
size_t o; size_t o;
size_t len; size_t len;
} query; } query;
struct { struct {
lnm_http_req_header arr[LNM_HTTP_MAX_REQ_HEADERS]; lnm_http_req_ihhr arr[LNM_HTTP_MAX_REQ_HEADERS];
size_t len; size_t len;
} headers; } headers;
struct { struct {
@ -81,31 +86,67 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len);
void lnm_http_req_reset(lnm_http_req *req); void lnm_http_req_reset(lnm_http_req *req);
/** /**
* Retrieve a specific header from the request. * Represents an actual header value, with offsets already resolved.
*/
typedef struct lnm_http_req_hdr {
struct {
const char *s;
size_t len;
} name;
struct {
const char *s;
size_t len;
} value;
} lnm_http_req_hdr;
/**
* Retrieve a known type header from the request.
* *
* Pointers retrieved from this function should never be used between step * Pointers retrieved from this function should never be used between step
* functions; simply request the header again if you need to. * functions; simply request the header again if you need to.
* *
* @param out where to write pointer to header value * @param out where to store pointer to struct representing header
* @param out_len where to store length of out value
* @param req request to look for header in * @param req request to look for header in
* @param type type of header to look for * @param type type of header to look for
*/ */
lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, lnm_http_header type); lnm_http_header type);
/** /**
* Retrieve a specific header from the request by specifying its name. * Retrieve a header from the request using a case-insensitive name.
* *
* Pointers retrieved from this function should never be used between step * Pointers retrieved from this function should never be used between step
* functions; simply request the header again if you need to. * functions; simply request the header again if you need to.
* *
* @param out where to write pointer to header value * @param out where to store pointer to struct representing header
* @param out_len where to store length of out value
* @param req request to look for header in * @param req request to look for header in
* @param name name of the header; matches case-insensitive * @param name name of the header; matches case-insensitive
*/ */
lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get_s(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, const char *name); 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);
/**
* Retrieve a specific key-value parameter from a header.
*
* Pointers retrieved from this function should never be used between step
* functions; simply request the header again if you need to.
*
* @param out output struct
* @param header header to look in
* @param key name of the parameter to return from the header
*/
size_t lnm_http_req_header_param(const char **out,
const lnm_http_req_hdr *header,
const char *key);
#endif #endif

View File

@ -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

View File

@ -1,6 +1,7 @@
#ifndef LNM_LOOP #ifndef LNM_LOOP
#define LNM_LOOP #define LNM_LOOP
#include <pthread.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@ -8,13 +9,19 @@
#include "lnm/common.h" #include "lnm/common.h"
#define LNM_LOOP_BUF_SIZE 2048 #define LNM_LOOP_BUF_SIZE 2048
#define LNM_QUEUE_MULTIPLIER 8
typedef enum { typedef enum lnm_loop_state {
lnm_loop_state_req = 0, lnm_loop_state_req_io = 0,
lnm_loop_state_res, lnm_loop_state_res_io,
lnm_loop_state_end, lnm_loop_state_end,
lnm_loop_state_req_work,
lnm_loop_state_res_work,
} lnm_loop_state; } lnm_loop_state;
/**
* State for a currently active connection
*/
typedef struct lnm_loop_conn { typedef struct lnm_loop_conn {
int fd; int fd;
lnm_loop_state state; lnm_loop_state state;
@ -30,6 +37,38 @@ typedef struct lnm_loop_conn {
} w; } w;
} lnm_loop_conn; } lnm_loop_conn;
/**
* Concurrent fixed-size queue used to distribute work among worker threads
*/
typedef struct lnm_loop_queue {
struct {
lnm_loop_conn **arr;
size_t len;
} buf;
size_t head;
size_t tail;
bool empty;
pthread_mutex_t mutex;
pthread_cond_t cond;
} lnm_loop_queue;
/**
* Initialize a new queue with the specified fixed capacity
*/
lnm_err lnm_loop_queue_init(lnm_loop_queue **out, size_t cap);
/**
* Queue the given connection. If the queue is currently full, this action
* blocks until there is space available.
*/
void lnm_loop_queue_push(lnm_loop_queue *q, lnm_loop_conn *conn);
/**
* Pop a connection from the queue. This action blocks until a connection is
* available.
*/
lnm_loop_conn *lnm_loop_queue_pop(lnm_loop_queue *q);
typedef struct lnm_loop { typedef struct lnm_loop {
int listen_fd; int listen_fd;
int epoll_fd; int epoll_fd;
@ -39,6 +78,13 @@ typedef struct lnm_loop {
void (*ctx_free)(void *ctx); void (*ctx_free)(void *ctx);
void (*data_read)(lnm_loop_conn *conn); void (*data_read)(lnm_loop_conn *conn);
void (*data_write)(lnm_loop_conn *conn); void (*data_write)(lnm_loop_conn *conn);
lnm_loop_queue *wq;
struct {
// Mutex shared between all threads; used for counting thread IDs
pthread_mutex_t mutex;
size_t worker_count;
size_t epoll_count;
} threads;
} lnm_loop; } lnm_loop;
lnm_err lnm_loop_init(lnm_loop **out, void *gctx, lnm_err lnm_loop_init(lnm_loop **out, void *gctx,
@ -49,6 +95,46 @@ lnm_err lnm_loop_init(lnm_loop **out, void *gctx,
lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port); lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port);
lnm_err lnm_loop_run(lnm_loop *l, int thread_count); /**
* Run a single epoll thread of the event loop.
*/
lnm_err lnm_loop_run(lnm_loop *l);
/**
* Run a multithreaded event loop with the configured number of threads.
*/
lnm_err lnm_loop_run_multi(lnm_loop *l, size_t epoll_threads,
size_t worker_threads);
/**
* Advance the processing of the given connection.
*
* Behavior of this function depends on both the connection state and whether
* worker threads are enabled.
*
* For IO states, this function will perform network I/O along with executing
* the loop's respective processing functions.
*
* For work states, the respective processing functions are executed without
* performing any network I/O. If no worker queue is present, this function
* performs all blocking work until an I/O or the end state is reached. If there
* is a worker queue present, only one block of work is done before exiting,
* allowing further blocks of work to be scheduled on other worker threads.
*
* If no worker queue is present, this function will only exit once an I/O or
* end state is reached.
*/
void lnm_loop_conn_advance(lnm_loop *l, lnm_loop_conn *conn);
/**
* Reschedule the given connection, either on the event loop for network IO or
* on a worker thread for blocking work. Connections are terminated as needed.
*/
void lnm_loop_conn_schedule(lnm_loop *l, lnm_loop_conn *conn);
/**
* Main loop executed on the worker threads.
*/
void lnm_loop_worker_run(void *arg);
#endif #endif

View File

@ -5,41 +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;
} 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.

View File

@ -9,6 +9,6 @@ void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn);
lnm_err lnm_loop_accept(lnm_loop *l); lnm_err lnm_loop_accept(lnm_loop *l);
void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn); void lnm_loop_conn_advance(lnm_loop *l, lnm_loop_conn *conn);
#endif #endif

View File

@ -1,138 +0,0 @@
#include <stdlib.h>
#include "lnm/common.h"
#include "lnm/http/loop.h"
#include "lnm/http/loop_internal.h"
#include "lnm/loop_internal.h"
lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx,
lnm_http_ctx_init_fn ctx_init,
lnm_http_ctx_reset_fn ctx_reset,
lnm_http_ctx_free_fn ctx_free) {
lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop));
if (hl == NULL) {
return lnm_err_failed_alloc;
}
LNM_RES2(lnm_http_loop_gctx_init((lnm_http_loop_gctx **)&hl->gctx, c_gctx,
ctx_init, ctx_reset, ctx_free),
free(hl));
hl->data_read = lnm_http_loop_process;
hl->data_write = lnm_http_loop_process;
hl->ctx_init = (lnm_err(*)(void **, void *))lnm_http_loop_ctx_init;
hl->ctx_free = (void (*)(void *))lnm_http_loop_ctx_free;
*out = hl;
return lnm_err_ok;
}
lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn) {
lnm_http_step *step = calloc(1, sizeof(lnm_http_step));
if (step == NULL) {
return lnm_err_failed_alloc;
}
step->fn = fn;
*out = step;
return lnm_err_ok;
}
lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step,
lnm_http_step_fn fn) {
LNM_RES(lnm_http_step_init(out, fn));
if (step != NULL) {
step->next = *out;
}
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_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, int thread_count) {
LNM_RES(lnm_loop_setup(hl, port));
return lnm_loop_run(hl, thread_count);
}
void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key) {
lnm_http_loop_gctx *gctx = hl->gctx;
gctx->api_key = api_key;
}
void lnm_http_loop_set_server(lnm_http_loop *hl, const char *server) {
lnm_http_loop_gctx *gctx = hl->gctx;
gctx->server = server;
}

View File

@ -0,0 +1,50 @@
#include <stdlib.h>
#include "lnm/common.h"
#include "lnm/http/loop.h"
#include "lnm/http/loop_internal.h"
#include "lnm/loop_internal.h"
lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx,
lnm_http_ctx_init_fn ctx_init,
lnm_http_ctx_reset_fn ctx_reset,
lnm_http_ctx_free_fn ctx_free) {
lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop));
if (hl == NULL) {
return lnm_err_failed_alloc;
}
LNM_RES2(lnm_http_loop_gctx_init((lnm_http_loop_gctx **)&hl->gctx, c_gctx,
ctx_init, ctx_reset, ctx_free),
free(hl));
hl->data_read = lnm_http_loop_process;
hl->data_write = lnm_http_loop_process;
hl->ctx_init = (lnm_err(*)(void **, void *))lnm_http_loop_ctx_init;
hl->ctx_free = (void (*)(void *))lnm_http_loop_ctx_free;
*out = hl;
return lnm_err_ok;
}
void lnm_http_loop_router_set(lnm_http_loop *hl, lnm_http_router *router) {
lnm_http_loop_gctx *gctx = hl->gctx;
gctx->router = router;
}
lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,
size_t epoll_threads, size_t worker_threads) {
LNM_RES(lnm_loop_setup(hl, port));
return lnm_loop_run_multi(hl, epoll_threads, worker_threads);
}
void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key) {
lnm_http_loop_gctx *gctx = hl->gctx;
gctx->api_key = api_key;
}
void lnm_http_loop_set_server(lnm_http_loop *hl, const char *server) {
lnm_http_loop_gctx *gctx = hl->gctx;
gctx->server = server;
}

View File

@ -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;
} }

View File

@ -8,7 +8,6 @@
#include "lnm/http/req.h" #include "lnm/http/req.h"
#include "lnm/log.h" #include "lnm/log.h"
#include "lnm/loop.h" #include "lnm/loop.h"
#include "lnm/loop_internal.h"
static const char *section = "http"; static const char *section = "http";
@ -56,55 +55,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;
case lnm_http_route_type_regex:
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; break;
case 2: case lnm_http_route_err_unknown_method:
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;
} }
} }
@ -113,11 +79,10 @@ void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) {
lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_ctx *ctx = conn->ctx;
lnm_http_req *req = &ctx->req; lnm_http_req *req = &ctx->req;
const char *value; lnm_http_req_hdr header;
size_t value_len; if (lnm_http_req_header_get(&header, req, lnm_http_header_content_length) ==
if (lnm_http_req_header_get(&value, &value_len, req, lnm_err_ok) {
lnm_http_header_content_length) == lnm_err_ok) { req->body.expected_len = lnm_atoi(header.value.s, header.value.len);
req->body.expected_len = lnm_atoi(value, value_len);
} }
ctx->state = lnm_http_loop_state_steps; ctx->state = lnm_http_loop_state_steps;
@ -133,11 +98,19 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) {
while ((ctx->cur_step != NULL) && (step != ctx->cur_step)) { while ((ctx->cur_step != NULL) && (step != ctx->cur_step)) {
step = ctx->cur_step; step = ctx->cur_step;
if (step->blocking && (conn->state != lnm_loop_state_req_work)) {
conn->state = lnm_loop_state_req_work;
break;
}
switch (step->fn(conn)) { switch (step->fn(conn)) {
case lnm_http_step_err_done: case lnm_http_step_err_done:
ctx->cur_step = ctx->cur_step->next; ctx->cur_step = ctx->cur_step->next;
break; break;
case lnm_http_step_err_io_needed: case lnm_http_step_err_io_needed:
// Ensure steps that require more I/O are executed on the event loop
conn->state = lnm_loop_state_req_io;
break; break;
case lnm_http_step_err_close: case lnm_http_step_err_close:
conn->state = lnm_loop_state_end; conn->state = lnm_loop_state_end;
@ -149,6 +122,7 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) {
} }
if (ctx->cur_step == NULL) { if (ctx->cur_step == NULL) {
conn->state = lnm_loop_state_res_io;
ctx->state = lnm_http_loop_state_add_headers; ctx->state = lnm_http_loop_state_add_headers;
} }
} }
@ -325,23 +299,23 @@ void (*process_fns[])(lnm_http_conn *conn) = {
lnm_loop_state state_map[] = { lnm_loop_state state_map[] = {
// parse_req // parse_req
lnm_loop_state_req, lnm_loop_state_req_io,
// route // route
lnm_loop_state_req, lnm_loop_state_req_io,
// parse_headers // parse_headers
lnm_loop_state_req, lnm_loop_state_req_io,
// steps // steps
lnm_loop_state_req, lnm_loop_state_req_io,
// add_headers // add_headers
lnm_loop_state_req, lnm_loop_state_req_io,
// write_status_line // write_status_line
lnm_loop_state_res, lnm_loop_state_res_io,
// write_headers // write_headers
lnm_loop_state_res, lnm_loop_state_res_io,
// write_body // write_body
lnm_loop_state_res, lnm_loop_state_res_io,
// finish // finish
lnm_loop_state_res, lnm_loop_state_res_io,
}; };
void lnm_http_loop_process(lnm_http_conn *conn) { void lnm_http_loop_process(lnm_http_conn *conn) {
@ -369,7 +343,7 @@ void lnm_http_loop_process(lnm_http_conn *conn) {
// We move the request to a dedicated buffer if the read buffer needs to be // We move the request to a dedicated buffer if the read buffer needs to be
// reused // reused
if ((conn->state == lnm_loop_state_req) && (conn->state == loop_state) && if ((conn->state == lnm_loop_state_req_io) && (conn->state == loop_state) &&
(!ctx->req.buf.owned) && (ctx->req.buf.len > 0)) { (!ctx->req.buf.owned) && (ctx->req.buf.len > 0)) {
char *buf = malloc(ctx->req.buf.len); char *buf = malloc(ctx->req.buf.len);

View File

@ -30,13 +30,13 @@ lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn) {
// If there's no API key, requests are always authorized // If there's no API key, requests are always authorized
bool authorized = ctx->g->api_key == NULL; bool authorized = ctx->g->api_key == NULL;
const char *value; lnm_http_req_hdr header;
size_t value_len;
if (!authorized && lnm_http_req_header_get_s(&value, &value_len, &ctx->req, if (!authorized && lnm_http_req_header_get_s(&header, &ctx->req,
"X-Api-Key") == lnm_err_ok) { "X-Api-Key") == lnm_err_ok) {
authorized = (value_len == strlen(ctx->g->api_key)) && authorized =
(memcmp(value, ctx->g->api_key, value_len) == 0); (header.value.len == strlen(ctx->g->api_key)) &&
(memcmp(header.value.s, ctx->g->api_key, header.value.len) == 0);
} }
ctx->res.status = authorized ? ctx->res.status : lnm_http_status_unauthorized; ctx->res.status = authorized ? ctx->res.status : lnm_http_status_unauthorized;

View File

@ -88,23 +88,24 @@ void lnm_http_req_reset(lnm_http_req *req) {
memset(req, 0, sizeof(lnm_http_req)); memset(req, 0, sizeof(lnm_http_req));
} }
lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, lnm_http_header type) { lnm_http_header type) {
return lnm_http_req_header_get_s(out, out_len, req, return lnm_http_req_header_get_s(out, req, lnm_http_header_names[type]);
lnm_http_header_names[type]);
} }
lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, lnm_err lnm_http_req_header_get_s(lnm_http_req_hdr *out, lnm_http_req *req,
lnm_http_req *req, const char *name) { const char *name) {
size_t name_len = strlen(name); size_t name_len = strlen(name);
for (size_t i = 0; i < req->headers.len; i++) { for (size_t i = 0; i < req->headers.len; i++) {
const lnm_http_req_header *header = &req->headers.arr[i]; const lnm_http_req_ihhr *header = &req->headers.arr[i];
if (lnm_strnieq(req->buf.s + header->name.o, header->name.len, name, if (lnm_strnieq(req->buf.s + header->name.o, header->name.len, name,
name_len)) { name_len)) {
*out = req->buf.s + header->value.o; out->name.s = req->buf.s + header->name.o;
*out_len = header->value.len; out->name.len = header->name.len;
out->value.s = req->buf.s + header->value.o;
out->value.len = header->value.len;
return lnm_err_ok; return lnm_err_ok;
} }
@ -112,3 +113,66 @@ 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;
}
size_t lnm_http_req_header_param(const char **out,
const lnm_http_req_hdr *header,
const char *key) {
size_t key_len = strlen(key);
size_t remaining_len = header->value.len;
// The -1 ensures there's still space for an equals sign after the match
const char *s = lnm_stristr(header->value.s, remaining_len - 1, key, key_len);
// Skip any accidental matches of the key inside another part of the value
while (s != NULL && s[key_len] != '=') {
remaining_len -= key_len;
s = lnm_stristr(s + key_len, remaining_len - 1, key, key_len);
};
// Edge case where empty value is at the end of the string
if (s == NULL || s + 2 == header->value.s + header->value.len) {
return 0;
}
*out = s + 2;
const char *value_end = s + 2;
// Handle a quoted string
if (**out == '"') {
value_end++;
while (value_end < header->value.s + header->value.len &&
(*value_end != '"')) {
value_end++;
}
// If we found a matching quote we trim the quotes, otherwise the quote is
// part of the value
if (value_end < header->value.s + header->value.len) {
// We skip the initial quote
(*out)++;
}
} else {
while (value_end < header->value.s + header->value.len &&
(*value_end != ',') && (*value_end != ';')) {
value_end++;
}
}
return value_end - *out;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -65,6 +65,9 @@ void lnm_vlog(lnm_log_level level, const char *section, const char *fmt,
continue; continue;
} }
va_list aq;
va_copy(aq, ap);
switch (stream->type) { switch (stream->type) {
case lnm_logger_stream_type_file: case lnm_logger_stream_type_file:
fprintf(stream->ptr, "[%s][%s][%s] ", date_str, fprintf(stream->ptr, "[%s][%s][%s] ", date_str,
@ -74,7 +77,7 @@ void lnm_vlog(lnm_log_level level, const char *section, const char *fmt,
break; break;
} }
va_end(ap); va_end(aq);
} }
} }

View File

@ -55,3 +55,29 @@ 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;
}
const char *lnm_stristr(const char *s1, size_t s1_len, const char *s2,
size_t s2_len) {
while (s1_len >= s2_len) {
if (lnm_strnieq(s1, s1_len, s2, s2_len)) {
return s1;
}
s1_len--;
s1++;
}
return NULL;
}

View File

@ -30,6 +30,8 @@ lnm_err lnm_loop_init(lnm_loop **out, void *gctx,
l->data_read = data_read; l->data_read = data_read;
l->data_write = data_write; l->data_write = data_write;
pthread_mutex_init(&l->threads.mutex, NULL);
*out = l; *out = l;
return lnm_err_ok; return lnm_err_ok;
@ -53,7 +55,7 @@ lnm_err lnm_loop_accept(lnm_loop *l) {
LNM_RES2(lnm_loop_conn_init(&conn, l), close(conn_fd)); LNM_RES2(lnm_loop_conn_init(&conn, l), close(conn_fd));
conn->fd = conn_fd; conn->fd = conn_fd;
conn->state = lnm_loop_state_req; conn->state = lnm_loop_state_req_io;
struct epoll_event event = {.data.ptr = conn, struct epoll_event event = {.data.ptr = conn,
.events = EPOLLIN | EPOLLET | EPOLLONESHOT}; .events = EPOLLIN | EPOLLET | EPOLLONESHOT};
@ -62,6 +64,10 @@ lnm_err lnm_loop_accept(lnm_loop *l) {
l->open++; l->open++;
// Make sure to re-arm the listening socket after accepting
event.data.ptr = NULL;
epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, l->listen_fd, &event);
lnm_ldebug(section, "connection opened with fd %i", conn_fd); lnm_ldebug(section, "connection opened with fd %i", conn_fd);
return lnm_err_ok; return lnm_err_ok;
@ -123,19 +129,51 @@ lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port) {
return lnm_err_ok; return lnm_err_ok;
} }
typedef struct lnm_loop_thread_args { void lnm_loop_conn_schedule(lnm_loop *l, lnm_loop_conn *conn) {
lnm_loop *l; switch (conn->state) {
int id; // IO states get rescheduled in the epoll loop
int thread_count; case lnm_loop_state_req_io:
} lnm_loop_thread_args; case lnm_loop_state_res_io: {
struct epoll_event event = {
.data.ptr = conn,
.events = (conn->state == lnm_loop_state_req_io ? EPOLLIN : EPOLLOUT) |
EPOLLET | EPOLLONESHOT};
lnm_err lnm_loop_run_thread(lnm_loop_thread_args *args) { epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, conn->fd, &event);
lnm_loop *l = args->l; } break;
int thread_id = args->id; case lnm_loop_state_req_work:
int thread_count = args->thread_count; case lnm_loop_state_res_work:
lnm_loop_queue_push(l->wq, conn);
break;
case lnm_loop_state_end: {
int conn_fd = conn->fd;
lnm_loop_conn_free(l, conn);
close(conn_fd);
l->open--;
epoll_ctl(l->epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL);
lnm_ldebug(section, "connection closed with fd %i", conn_fd);
} break;
}
}
lnm_err lnm_loop_run(lnm_loop *l) {
if (l->epoll_fd == 0) {
return lnm_err_not_setup;
}
// Get thread ID by incrementing counter
pthread_mutex_lock(&l->threads.mutex);
int thread_id = l->threads.epoll_count;
l->threads.epoll_count++;
pthread_mutex_unlock(&l->threads.mutex);
struct epoll_event *events = calloc(1, sizeof(struct epoll_event)); struct epoll_event *events = calloc(1, sizeof(struct epoll_event));
int events_cap = 1; size_t events_cap = 1;
if (events == NULL) { if (events == NULL) {
return lnm_err_failed_alloc; return lnm_err_failed_alloc;
@ -143,9 +181,6 @@ lnm_err lnm_loop_run_thread(lnm_loop_thread_args *args) {
lnm_linfo(section, "thread %i started", thread_id); lnm_linfo(section, "thread %i started", thread_id);
struct epoll_event listen_event = {
.data.ptr = NULL, .events = EPOLLIN | EPOLLET | EPOLLONESHOT};
while (1) { while (1) {
int polled = epoll_wait(l->epoll_fd, events, events_cap, -1); int polled = epoll_wait(l->epoll_fd, events, events_cap, -1);
lnm_ldebug(section, "polled (thread %i): %i", thread_id, polled); lnm_ldebug(section, "polled (thread %i): %i", thread_id, polled);
@ -157,36 +192,19 @@ lnm_err lnm_loop_run_thread(lnm_loop_thread_args *args) {
for (int i = 0; i < polled; i++) { for (int i = 0; i < polled; i++) {
if (events[i].data.ptr == NULL) { if (events[i].data.ptr == NULL) {
lnm_loop_accept(l); lnm_loop_accept(l);
epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, l->listen_fd, &listen_event);
} else { } else {
lnm_loop_conn *conn = events[i].data.ptr; lnm_loop_conn *conn = events[i].data.ptr;
lnm_loop_conn_io(l, conn);
if (conn->state == lnm_loop_state_end) { // At this point, state is always an IO state
int conn_fd = conn->fd; lnm_loop_conn_advance(l, conn);
lnm_loop_conn_schedule(l, conn);
lnm_loop_conn_free(l, conn);
close(conn_fd);
l->open--;
epoll_ctl(l->epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL);
lnm_ldebug(section, "connection closed with fd %i", conn_fd);
} else {
struct epoll_event event = {
.data.ptr = conn,
.events =
(conn->state == lnm_loop_state_req ? EPOLLIN : EPOLLOUT) |
EPOLLET | EPOLLONESHOT};
epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, conn->fd, &event);
}
} }
} }
int open = l->open; size_t open = l->open;
int cap_per_thread = size_t cap_per_thread = open + 1 > l->threads.epoll_count
open + 1 > thread_count ? (open + 1) / thread_count : 1; ? (open + 1) / l->threads.epoll_count
: 1;
if (cap_per_thread > events_cap) { if (cap_per_thread > events_cap) {
struct epoll_event *new_events = struct epoll_event *new_events =
@ -205,28 +223,21 @@ lnm_err lnm_loop_run_thread(lnm_loop_thread_args *args) {
return lnm_err_ok; return lnm_err_ok;
} }
lnm_err lnm_loop_run(lnm_loop *l, int thread_count) { lnm_err lnm_loop_run_multi(lnm_loop *l, size_t epoll_threads,
if (l->epoll_fd == 0) { size_t worker_threads) {
return lnm_err_not_setup; if (worker_threads > 0) {
LNM_RES(lnm_loop_queue_init(&l->wq, LNM_QUEUE_MULTIPLIER * worker_threads));
} }
lnm_loop_thread_args args[thread_count]; pthread_t t;
for (int i = 1; i < thread_count; i++) { for (size_t i = 1; i < epoll_threads; i++) {
args[i].l = l; pthread_create(&t, NULL, (void *(*)(void *))lnm_loop_run, l);
args[i].id = i;
args[i].thread_count = thread_count;
pthread_t thread;
pthread_create(&thread, NULL, (void *(*)(void *))lnm_loop_run_thread,
&args[i]);
} }
args[0].l = l; for (size_t i = 0; i < worker_threads; i++) {
args[0].id = 0; pthread_create(&t, NULL, (void *(*)(void *))lnm_loop_worker_run, l);
args[0].thread_count = thread_count; }
lnm_loop_run_thread(&args[0]); return lnm_loop_run(l);
return lnm_err_ok;
} }

View File

@ -1,5 +1,6 @@
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include "lnm/loop.h" #include "lnm/loop.h"
@ -33,7 +34,7 @@ void lnm_loop_conn_io_req(lnm_loop *l, lnm_loop_conn *conn) {
conn->r.size += res; conn->r.size += res;
l->data_read(conn); l->data_read(conn);
} while (conn->state == lnm_loop_state_req); } while (conn->state == lnm_loop_state_req_io);
} }
void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) { void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) {
@ -43,7 +44,9 @@ void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) {
ssize_t res; ssize_t res;
do { do {
res = write(conn->fd, conn->w.buf, conn->w.size); // Send with MSG_NOSIGNAL prevents closed pipes from exiting the program
// with SIGPIPE
res = send(conn->fd, conn->w.buf, conn->w.size, MSG_NOSIGNAL);
} while (res < 0 && errno == EINTR); } while (res < 0 && errno == EINTR);
// Write can't be performed without blocking; we come back later // Write can't be performed without blocking; we come back later
@ -61,17 +64,33 @@ void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) {
// writer function more space to work with // writer function more space to work with
memmove(conn->w.buf, &conn->w.buf[res], conn->w.size - res); memmove(conn->w.buf, &conn->w.buf[res], conn->w.size - res);
conn->w.size -= res; conn->w.size -= res;
} while (conn->state == lnm_loop_state_res); } while (conn->state == lnm_loop_state_res_io);
} }
void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn) { void lnm_loop_conn_advance(lnm_loop *l, lnm_loop_conn *conn) {
switch (conn->state) { do {
case lnm_loop_state_req:
lnm_loop_conn_io_req(l, conn); switch (conn->state) {
break; case lnm_loop_state_req_io:
case lnm_loop_state_res: lnm_loop_conn_io_req(l, conn);
lnm_loop_conn_io_res(l, conn); break;
break; case lnm_loop_state_res_io:
default:; lnm_loop_conn_io_res(l, conn);
break;
case lnm_loop_state_req_work:
do {
l->data_read(conn);
} while (conn->state == lnm_loop_state_req_work);
break;
case lnm_loop_state_res_work:
do {
l->data_write(conn);
} while (conn->state == lnm_loop_state_res_work);
break;
default:;
}
} }
// Execute all blocking work if we're running in single-threaded mode
while (l->wq == NULL && (conn->state == lnm_loop_state_req_work ||
conn->state == lnm_loop_state_res_work));
} }

View File

@ -0,0 +1,92 @@
#include <sys/epoll.h>
#include "lnm/log.h"
#include "lnm/loop.h"
lnm_err lnm_loop_queue_init(lnm_loop_queue **out, size_t cap) {
lnm_loop_conn **arr = calloc(cap, sizeof(lnm_loop_conn *));
if (arr == NULL) {
return lnm_err_failed_alloc;
}
lnm_loop_queue *q = calloc(1, sizeof(lnm_loop_queue));
if (q == NULL) {
free(arr);
return lnm_err_failed_alloc;
}
q->buf.arr = arr;
q->buf.len = cap;
q->tail = 0;
q->head = 0;
q->empty = true;
pthread_mutex_init(&q->mutex, NULL);
pthread_cond_init(&q->cond, NULL);
*out = q;
return lnm_err_ok;
}
void lnm_loop_queue_push(lnm_loop_queue *q, lnm_loop_conn *conn) {
pthread_mutex_lock(&q->mutex);
while (q->head == q->tail && !q->empty) {
pthread_cond_wait(&q->cond, &q->mutex);
}
q->buf.arr[q->head] = conn;
// Make sure the index wraps around
q->head = (q->head + 1) % q->buf.len;
q->empty = false;
// Unlock mutex and signal to waiting threads
pthread_mutex_unlock(&q->mutex);
pthread_cond_signal(&q->cond);
}
lnm_loop_conn *lnm_loop_queue_pop(lnm_loop_queue *q) {
pthread_mutex_lock(&q->mutex);
while (q->empty) {
pthread_cond_wait(&q->cond, &q->mutex);
}
lnm_loop_conn *out = q->buf.arr[q->tail];
q->tail = (q->tail + 1) % q->buf.len;
q->empty = q->tail == q->head;
// Unlock mutex and signal to waiting threads
pthread_mutex_unlock(&q->mutex);
pthread_cond_signal(&q->cond);
return out;
}
void lnm_loop_worker_run(void *arg) {
lnm_loop *l = arg;
lnm_loop_queue *q = l->wq;
// Get thread ID by incrementing counter
pthread_mutex_lock(&l->threads.mutex);
int thread_id = l->threads.worker_count;
l->threads.worker_count++;
pthread_mutex_unlock(&l->threads.mutex);
while (1) {
lnm_loop_conn *conn = lnm_loop_queue_pop(q);
lnm_ldebug("loop", "worker %i processing fd %i", thread_id, conn->fd);
lnm_loop_conn_advance(l, conn);
lnm_loop_conn_schedule(l, conn);
}
}

1994
test/acutest.h 100644

File diff suppressed because it is too large Load Diff

104
test/lnm/routing.c 100644
View File

@ -0,0 +1,104 @@
#define TEST_NO_MAIN
#include "acutest.h"
#include "tests.h"
#include "lnm/common.h"
#include "lnm/http/route.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);
}

10
test/runner.c 100644
View File

@ -0,0 +1,10 @@
#include "acutest.h"
#include "tests.h"
TEST_LIST = {
{ "routing simple", test_routing_simple },
{ "routing star", test_routing_star },
{ "routing merge", test_routing_merge },
{ "routing nest", test_routing_nest },
{ NULL, NULL }
};

9
test/tests.h 100644
View File

@ -0,0 +1,9 @@
#ifndef TEST_TESTS
#define TEST_TESTS
void test_routing_simple();
void test_routing_star();
void test_routing_merge();
void test_routing_nest();
#endif