Compare commits

...

2 Commits

Author SHA1 Message Date
Jef Roosens 4c2b85d436
feat(lnm): add request parser
ci/woodpecker/push/build Pipeline was successful Details
2023-11-27 13:50:08 +01:00
Jef Roosens e59ee38ae2
feat(lnm): write server init code 2023-11-24 10:39:22 +01:00
9 changed files with 316 additions and 5 deletions

View File

@ -6,7 +6,7 @@
LIB := $(BUILD_DIR)/$(LIB_FILENAME) LIB := $(BUILD_DIR)/$(LIB_FILENAME)
SRCS != find '$(SRC_DIR)' -iname '*.c' SRCS != find '$(SRC_DIR)' -iname '*.c'
SRCS_H != find $(INC_DIRS) -iname '*.h' SRCS_H != find include -iname '*.h'
SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h'
SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c'
SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c' SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c'
@ -91,11 +91,11 @@ $(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c
# =====MAINTENANCE===== # =====MAINTENANCE=====
.PHONY: lint .PHONY: lint
lint: lint:
clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL)
.PHONY: fmt .PHONY: fmt
fmt: fmt:
clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL)
.PHONY: check .PHONY: check
check: check:

View File

@ -23,6 +23,7 @@ typedef enum {
lnm_err_failed_network, lnm_err_failed_network,
lnm_err_failed_poll, lnm_err_failed_poll,
lnm_err_not_setup, lnm_err_not_setup,
lnm_err_bad_regex
} lnm_err; } lnm_err;
#endif #endif

View File

@ -12,7 +12,7 @@ typedef enum lnm_http_method {
http_method_put, http_method_put,
http_method_patch, http_method_patch,
http_method_delete http_method_delete
} http_method; } lnm_http_method;
extern const char *lnm_http_status_names[][32]; extern const char *lnm_http_status_names[][32];

View File

@ -0,0 +1,75 @@
#ifndef LNM_HTTP_LOOP
#define LNM_HTTP_LOOP
#include "lnm/common.h"
#define LNM_HTTP_MAX_REQ_HEADERS 32
typedef struct lnm_loop lnm_http_loop;
typedef struct lnm_conn lnm_http_conn;
typedef struct lnm_http_step lnm_http_step;
typedef struct lnm_http_route lnm_http_route;
typedef lnm_err (*lnm_http_step_fn)(lnm_http_conn *conn);
/**
* Initialize a new `lnm_http_loop`.
*
* @param out where to store pointer to new `lnm_http_loop`
*/
lnm_err lnm_http_loop_init(lnm_http_loop **out);
/**
* Append the given step fn to the step.
*
* @param out where to store pointer to new `lnm_http_step`
* @param step step to append new step to
* @param fn step funcitonn
*/
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, 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, 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
*/
void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route);
/**
* Represents what state an HTTP loop request is currently in.
*/
typedef enum lnm_http_loop_state {
lnm_http_loop_state_parse_req = 0,
} lnm_http_loop_state;
typedef struct lnm_http_loop_ctx {
lnm_http_loop_state state;
} lnm_http_loop_ctx;
#endif

View File

@ -0,0 +1,48 @@
#ifndef LNM_HTTP_REQ
#define LNM_HTTP_REQ
#include <stdlib.h>
#include "picohttpparser.h"
#include "lnm/http/consts.h"
#include "lnm/http/loop.h"
/**
* Represents the parsed HTTP request
*/
typedef struct lnm_http_req {
size_t len;
int minor_version;
lnm_http_method method;
struct {
const char *s;
size_t len;
} path;
struct {
const char *s;
size_t len;
} query;
struct {
struct phr_header arr[LNM_HTTP_MAX_REQ_HEADERS];
size_t len;
} headers;
} lnm_http_req;
typedef enum lnm_http_parse_err {
lnm_http_parse_err_ok = 0,
lnm_http_parse_err_incomplete,
lnm_http_parse_err_invalid,
lnm_http_parse_err_unknown_method,
} lnm_http_parse_err;
/**
* Try to parse the given buffer into an HTTP request.
*
* @param req request to store parsed data in
* @param buf buffer to parse; might be modified
* @param len length of buf
*/
lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len);
#endif

View File

@ -30,7 +30,7 @@ typedef struct {
} w; } w;
} lnm_loop_conn; } lnm_loop_conn;
typedef struct { typedef struct lnm_loop {
int listen_fd; int listen_fd;
struct { struct {
lnm_loop_conn **arr; lnm_loop_conn **arr;

View File

@ -0,0 +1,43 @@
#ifndef LNM_HTTP_LOOP_INTERNAL
#define LNM_HTTP_LOOP_INTERNAL
#include <regex.h>
#include "lnm/http/loop.h"
typedef struct lnm_http_step {
lnm_http_step_fn fn;
struct lnm_http_step *next;
} 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_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.
*
* @param out where to store pointer to new `lnm_http_step`
* @param fn step function associated with the step
*/
lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn);
#endif

View File

@ -0,0 +1,86 @@
#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) {
lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop));
if (hl == NULL) {
return lnm_err_failed_alloc;
}
*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));
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, const char *path,
lnm_http_step *step) {
LNM_RES(lnm_http_route_init(out));
(*out)->type = lnm_http_route_type_literal;
(*out)->route.s = path;
(*out)->step = step;
return lnm_err_ok;
}
lnm_err lnm_http_route_init_regex(lnm_http_route **out, 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)->type = lnm_http_route_type_regex;
(*out)->route.regex = regex;
(*out)->regex_group_count = regex_group_count;
(*out)->step = step;
return lnm_err_ok;
}

58
lnm/src/http/req.c 100644
View File

@ -0,0 +1,58 @@
#include <stdbool.h>
#include <string.h>
#include "lnm/http/consts.h"
#include "lnm/http/loop.h"
#include "lnm/http/req.h"
lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf,
size_t len) {
const char *method;
char *path;
size_t method_len, path_len;
req->headers.len = LNM_HTTP_MAX_REQ_HEADERS;
int req_len = phr_parse_request(
buf, len, &method, &method_len, (const char **)&path, &path_len,
&req->minor_version, req->headers.arr, &req->headers.len, 0);
if (req_len == -1) {
return lnm_http_parse_err_invalid;
} else if (req_len == -2) {
return lnm_http_parse_err_incomplete;
}
bool known_method = false;
for (size_t i = 0; i < lnm_http_method_names_len && !known_method; i++) {
if (strncmp(method, lnm_http_method_names[i], method_len) == 0) {
req->method = i;
known_method = true;
}
}
if (!known_method) {
return lnm_http_parse_err_unknown_method;
}
char *question_mark = strchr(path, '?');
// Only store query if the path doesn't simply end with a question mark
if ((question_mark != NULL) && (path_len - (question_mark + 1 - path) > 0)) {
req->query.s = question_mark + 1;
req->query.len = path_len - (question_mark + 1 - path);
path_len = question_mark - path;
}
// All parsed strings should be null-terminated. This character is either a
// newline (if at the end of the path), or a question mark (if a query is
// present).
path[path_len] = '\0';
req->path.len = path_len;
req->path.s = path;
req->len = req_len;
return lnm_http_parse_err_ok;
}