chore: copy over original source files
commit
2681801a1e
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
.cache/
|
|
@ -0,0 +1,128 @@
|
|||
# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great
|
||||
# base for this Makefile
|
||||
|
||||
-include config.mk
|
||||
|
||||
LIB := $(BUILD_DIR)/$(LIB_FILENAME)
|
||||
|
||||
SRCS != find '$(SRC_DIR)' -iname '*.c'
|
||||
SRCS_H != find include -iname '*.h'
|
||||
SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h'
|
||||
SRCS_TEST != find '$(TEST_DIR)' -iname '*.c'
|
||||
|
||||
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
|
||||
OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o)
|
||||
DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d)
|
||||
|
||||
BINS_TEST := $(OBJS_TEST:%.c.o=%)
|
||||
TARGETS_TEST := $(BINS_TEST:%=test-%)
|
||||
TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%)
|
||||
|
||||
_CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra
|
||||
|
||||
.PHONY: all
|
||||
all: lib
|
||||
|
||||
|
||||
# =====COMPILATION=====
|
||||
# Utility used by the CI to lint
|
||||
.PHONY: objs
|
||||
objs: $(OBJS)
|
||||
|
||||
.PHONY: lib
|
||||
lib: $(LIB)
|
||||
$(LIB): $(OBJS)
|
||||
ar -rcs $@ $(OBJS)
|
||||
|
||||
$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) -c $(_CFLAGS) $< -o $@
|
||||
|
||||
# =====TESTING=====
|
||||
.PHONY: test
|
||||
test: $(TARGETS_TEST)
|
||||
|
||||
.PHONY: test-mem
|
||||
test-mem: $(TARGETS_MEM_TEST)
|
||||
|
||||
.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
|
||||
build-test: $(BINS_TEST)
|
||||
|
||||
$(BINS_TEST): %: %.c.o $(LIB)
|
||||
$(CC) \
|
||||
$^ -o $@
|
||||
|
||||
# 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
|
||||
# testing. This allows tests to access internal methods, which aren't publicly
|
||||
# exposed.
|
||||
$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) $(_CFLAGS) -I$(TEST_DIR) \
|
||||
-I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \
|
||||
-c $< -o $@
|
||||
|
||||
# =====EXAMPLES=====
|
||||
.PHONY: build-example
|
||||
build-example: $(BINS_EXAMPLE)
|
||||
|
||||
$(BINS_EXAMPLE): %: %.c.o $(LIB)
|
||||
$(CC) \
|
||||
$^ -o $@
|
||||
|
||||
# Example binaries link the resulting library
|
||||
$(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) $(_CFLAGS) -I$(PUB_INC_DIR) -c $< -o $@
|
||||
|
||||
# =====MAINTENANCE=====
|
||||
.PHONY: lint
|
||||
lint:
|
||||
clang-format -n --Werror \
|
||||
$(filter-out $(THIRDPARTY),$(SRCS)) \
|
||||
$(filter-out $(THIRDPARTY),$(SRCS_H)) \
|
||||
$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL))
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
clang-format -i \
|
||||
$(filter-out $(THIRDPARTY),$(SRCS)) \
|
||||
$(filter-out $(THIRDPARTY),$(SRCS_H)) \
|
||||
$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL))
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
mkdir -p $(BUILD_DIR)/cppcheck
|
||||
cppcheck \
|
||||
$(addprefix -I,$(INC_DIRS)) \
|
||||
--cppcheck-build-dir=$(BUILD_DIR)/cppcheck \
|
||||
--error-exitcode=1 \
|
||||
--enable=warning,style \
|
||||
--inline-suppr \
|
||||
--check-level=exhaustive \
|
||||
--quiet \
|
||||
-j$(shell nproc) \
|
||||
$(filter-out $(THIRDPARTY),$(SRCS))
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf '$(BUILD_DIR)'
|
||||
|
||||
|
||||
.PHONY: bear
|
||||
bear: clean
|
||||
bear -- make
|
||||
bear --append -- make build-test
|
||||
bear --append -- make build-example
|
||||
|
||||
|
||||
# Make make aware of the .d files
|
||||
-include $(DEPS)
|
|
@ -0,0 +1,16 @@
|
|||
LIB_FILENAME = liblnm.a
|
||||
|
||||
BUILD_DIR = build
|
||||
SRC_DIR = src
|
||||
TEST_DIR = test
|
||||
THIRDPARTY = src/picohttpparser.c include/picohttpparser.h
|
||||
|
||||
PUB_INC_DIR = include
|
||||
INC_DIRS = $(PUB_INC_DIR) src/_include
|
||||
|
||||
# -MMD: generate a .d file for every source file. This file can be imported by
|
||||
# make and makes make aware that a header file has been changed, ensuring an
|
||||
# object file is also recompiled if only a header is changed.
|
||||
# -MP: generate a dummy target for every header file (according to the docs it
|
||||
# prevents some errors when removing header files)
|
||||
CFLAGS ?= -MMD -MP -g
|
|
@ -0,0 +1,89 @@
|
|||
#ifndef LNM_COMMON
|
||||
#define LNM_COMMON
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LNM_RES(x) \
|
||||
{ \
|
||||
lnm_err res = x; \
|
||||
if (res != lnm_err_ok) \
|
||||
return res; \
|
||||
}
|
||||
|
||||
#define LNM_RES2(x, e) \
|
||||
{ \
|
||||
lnm_err res = x; \
|
||||
if (res != lnm_err_ok) { \
|
||||
e; \
|
||||
return res; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LNM_MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
#define LNM_MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
typedef enum lnm_err {
|
||||
lnm_err_ok = 0,
|
||||
lnm_err_failed_alloc,
|
||||
lnm_err_failed_network,
|
||||
lnm_err_failed_poll,
|
||||
lnm_err_not_setup,
|
||||
lnm_err_bad_regex,
|
||||
lnm_err_not_found,
|
||||
} lnm_err;
|
||||
|
||||
typedef struct lnm_loop lnm_http_loop;
|
||||
|
||||
typedef struct lnm_loop_conn lnm_http_conn;
|
||||
|
||||
typedef struct lnm_http_step lnm_http_step;
|
||||
|
||||
typedef struct lnm_http_route lnm_http_route;
|
||||
|
||||
/**
|
||||
* Returns whether the two strings are equal.
|
||||
*
|
||||
* @param s1 first string to compare
|
||||
* @param s1_len length of `s1`
|
||||
* @param s2 second string to compare
|
||||
* @param s2_len length of `s2`
|
||||
*/
|
||||
bool lnm_strneq(const char *s1, size_t s1_len, const char *s2, size_t s2_len);
|
||||
|
||||
/**
|
||||
* Returns whether the two strings are equal, ignoring capitalisation.
|
||||
*
|
||||
* @param s1 first string to compare
|
||||
* @param s1_len length of `s1`
|
||||
* @param s2 second string to compare
|
||||
* @param s2_len length of `s2`
|
||||
*/
|
||||
bool lnm_strnieq(const char *s1, size_t s1_len, const char *s2, size_t s2_len);
|
||||
|
||||
/**
|
||||
* Calculate integer exponentation.
|
||||
*
|
||||
* @param base
|
||||
* @param power
|
||||
*/
|
||||
uint64_t lnm_ipow(uint64_t base, uint64_t power);
|
||||
|
||||
/**
|
||||
* Parse the given string into a number.
|
||||
*
|
||||
* @param s string to parse
|
||||
* @param len length of s
|
||||
* @return the parsed number, or 0 if the number is invalid
|
||||
*/
|
||||
uint64_t lnm_atoi(const char *s, size_t len);
|
||||
|
||||
/**
|
||||
* Calculate how many base 10 digits the given number consists of.
|
||||
*
|
||||
* @param num number to use
|
||||
*/
|
||||
uint64_t lnm_digits(uint64_t num);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,98 @@
|
|||
#ifndef LNM_HTTP_CONSTS
|
||||
#define LNM_HTTP_CONSTS
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
extern const char *lnm_http_method_names[];
|
||||
extern const size_t lnm_http_method_names_len;
|
||||
|
||||
typedef enum lnm_http_method {
|
||||
lnm_http_method_get = 0,
|
||||
lnm_http_method_post,
|
||||
lnm_http_method_put,
|
||||
lnm_http_method_patch,
|
||||
lnm_http_method_delete,
|
||||
lnm_http_method_head,
|
||||
} lnm_http_method;
|
||||
|
||||
extern const char *lnm_http_status_names[][32];
|
||||
|
||||
typedef enum lnm_http_status {
|
||||
// 1xx
|
||||
lnm_http_status_continue = 100,
|
||||
lnm_http_status_switching_protocols = 101,
|
||||
lnm_http_status_processing = 102,
|
||||
lnm_http_status_early_hints = 103,
|
||||
// 2xx
|
||||
lnm_http_status_ok = 200,
|
||||
lnm_http_status_created = 201,
|
||||
lnm_http_status_accepted = 202,
|
||||
lnm_http_status_non_authoritative_information = 203,
|
||||
lnm_http_status_no_content = 204,
|
||||
lnm_http_status_reset_content = 205,
|
||||
lnm_http_status_partial_content = 206,
|
||||
lnm_http_status_multi_status = 207,
|
||||
lnm_http_status_already_reported = 208,
|
||||
// 3xx
|
||||
lnm_http_status_multiple_choices = 300,
|
||||
lnm_http_status_moved_permanently = 301,
|
||||
lnm_http_status_found = 302,
|
||||
lnm_http_status_see_other = 303,
|
||||
lnm_http_status_not_modified = 304,
|
||||
lnm_http_status_temporary_redirect = 307,
|
||||
lnm_http_status_permanent_redirect = 308,
|
||||
// 4xx
|
||||
lnm_http_status_bad_request = 400,
|
||||
lnm_http_status_unauthorized = 401,
|
||||
lnm_http_status_payment_required = 402,
|
||||
lnm_http_status_forbidden = 403,
|
||||
lnm_http_status_not_found = 404,
|
||||
lnm_http_status_method_not_allowed = 405,
|
||||
lnm_http_status_not_acceptable = 406,
|
||||
lnm_http_status_proxy_authentication_required = 407,
|
||||
lnm_http_status_request_timeout = 408,
|
||||
lnm_http_status_conflict = 409,
|
||||
lnm_http_status_gone = 410,
|
||||
lnm_http_status_length_required = 411,
|
||||
lnm_http_status_precondition_failed = 412,
|
||||
lnm_http_status_content_too_large = 413,
|
||||
lnm_http_status_uri_too_long = 414,
|
||||
lnm_http_status_unsupported_media_type = 415,
|
||||
lnm_http_status_range_not_satisfiable = 416,
|
||||
lnm_http_status_expection_failed = 417,
|
||||
lnm_http_status_im_a_teapot = 418,
|
||||
lnm_http_status_misdirected_request = 421,
|
||||
lnm_http_status_unprocessable_content = 422,
|
||||
lnm_http_status_locked = 423,
|
||||
lnm_http_status_failed_dependency = 424,
|
||||
lnm_http_status_too_early = 425,
|
||||
lnm_http_status_upgrade_required = 426,
|
||||
lnm_http_status_precondition_required = 428,
|
||||
lnm_http_status_too_many_requests = 429,
|
||||
lnm_http_status_request_header_fields_too_large = 431,
|
||||
// 5xx
|
||||
lnm_http_status_internal_server_error = 500,
|
||||
lnm_http_status_method_not_implemented = 501,
|
||||
lnm_http_status_bad_gateway = 502,
|
||||
lnm_http_status_service_unavailable = 503,
|
||||
lnm_http_status_gateway_timeout = 504,
|
||||
lnm_http_status_http_status_version_not_supported = 505,
|
||||
lnm_http_status_variant_also_negotiates = 506,
|
||||
lnm_http_status_insufficient_storage = 507,
|
||||
lnm_http_status_loop_detected = 508,
|
||||
lnm_http_status_not_extended = 510,
|
||||
lnm_http_status_network_authentication_required = 511
|
||||
} lnm_http_status;
|
||||
|
||||
extern const char *lnm_http_header_names[];
|
||||
|
||||
typedef enum lnm_http_header {
|
||||
lnm_http_header_connection = 0,
|
||||
lnm_http_header_location,
|
||||
lnm_http_header_content_type,
|
||||
lnm_http_header_content_disposition,
|
||||
lnm_http_header_server,
|
||||
lnm_http_header_content_length
|
||||
} lnm_http_header;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,141 @@
|
|||
#ifndef LNM_HTTP_LOOP
|
||||
#define LNM_HTTP_LOOP
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lnm/common.h"
|
||||
#include "lnm/http/req.h"
|
||||
#include "lnm/http/res.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 void (*lnm_http_ctx_reset_fn)(void *c_ctx);
|
||||
|
||||
typedef void (*lnm_http_ctx_free_fn)(void *c_ctx);
|
||||
|
||||
/**
|
||||
* 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, void *c_gctx,
|
||||
lnm_http_ctx_init_fn ctx_init,
|
||||
lnm_http_ctx_reset_fn ctx_reset,
|
||||
lnm_http_ctx_free_fn ctx_free);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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 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_server(lnm_http_loop *hl, const char *value);
|
||||
|
||||
/**
|
||||
* Represents what state an HTTP loop request is currently in.
|
||||
*/
|
||||
typedef enum lnm_http_loop_state {
|
||||
// Parse the HTTP request
|
||||
lnm_http_loop_state_parse_req = 0,
|
||||
// Route the request
|
||||
lnm_http_loop_state_route,
|
||||
// Parse specific headers (e.g. Content-Length)
|
||||
lnm_http_loop_state_parse_headers,
|
||||
// Execute the various steps defined for the route
|
||||
lnm_http_loop_state_steps,
|
||||
// Add certain automatically added headers
|
||||
lnm_http_loop_state_add_headers,
|
||||
// Write the response status line
|
||||
lnm_http_loop_state_write_status_line,
|
||||
// Write the various response headers
|
||||
lnm_http_loop_state_write_headers,
|
||||
// Write the request body
|
||||
lnm_http_loop_state_write_body,
|
||||
// Clean up the request and reset the state for a next request
|
||||
lnm_http_loop_state_finish,
|
||||
} lnm_http_loop_state;
|
||||
|
||||
typedef struct lnm_http_loop_gctx {
|
||||
struct {
|
||||
lnm_http_route **arr;
|
||||
size_t len;
|
||||
} routes;
|
||||
lnm_http_ctx_init_fn ctx_init;
|
||||
lnm_http_ctx_reset_fn ctx_reset;
|
||||
lnm_http_ctx_free_fn ctx_free;
|
||||
const char *api_key;
|
||||
const char *server;
|
||||
void *c;
|
||||
} lnm_http_loop_gctx;
|
||||
|
||||
typedef struct lnm_http_loop_ctx {
|
||||
lnm_http_loop_state state;
|
||||
lnm_http_req req;
|
||||
lnm_http_res res;
|
||||
lnm_http_route *route;
|
||||
lnm_http_step *cur_step;
|
||||
lnm_http_loop_gctx *g;
|
||||
void *c;
|
||||
} lnm_http_loop_ctx;
|
||||
|
||||
lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn);
|
||||
|
||||
lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,111 @@
|
|||
#ifndef LNM_HTTP_REQ
|
||||
#define LNM_HTTP_REQ
|
||||
|
||||
#include <regex.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "picohttpparser.h"
|
||||
|
||||
#include "lnm/common.h"
|
||||
#include "lnm/http/consts.h"
|
||||
|
||||
#define LNM_HTTP_MAX_REQ_HEADERS 32
|
||||
#define LNM_HTTP_MAX_REGEX_GROUPS 4
|
||||
|
||||
typedef struct lnm_http_req_header {
|
||||
struct {
|
||||
size_t o;
|
||||
size_t len;
|
||||
} name;
|
||||
struct {
|
||||
size_t o;
|
||||
size_t len;
|
||||
} value;
|
||||
} lnm_http_req_header;
|
||||
|
||||
/**
|
||||
* Represents the parsed HTTP request
|
||||
*/
|
||||
typedef struct lnm_http_req {
|
||||
struct {
|
||||
char *s;
|
||||
size_t len;
|
||||
bool owned;
|
||||
} buf;
|
||||
int minor_version;
|
||||
lnm_http_method method;
|
||||
struct {
|
||||
size_t o;
|
||||
size_t len;
|
||||
regmatch_t groups[LNM_HTTP_MAX_REGEX_GROUPS];
|
||||
} path;
|
||||
struct {
|
||||
size_t o;
|
||||
size_t len;
|
||||
} query;
|
||||
struct {
|
||||
lnm_http_req_header arr[LNM_HTTP_MAX_REQ_HEADERS];
|
||||
size_t len;
|
||||
} headers;
|
||||
struct {
|
||||
uint64_t expected_len;
|
||||
uint64_t len;
|
||||
char *buf;
|
||||
bool owned;
|
||||
} body;
|
||||
} 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);
|
||||
|
||||
/**
|
||||
* Reset the given request object, free'ing all its relevant parts and allowing
|
||||
* it to be reused as a new object.
|
||||
*
|
||||
* @param req object to reset
|
||||
*/
|
||||
void lnm_http_req_reset(lnm_http_req *req);
|
||||
|
||||
/**
|
||||
* Retrieve a specific header from the request.
|
||||
*
|
||||
* Pointers retrieved from this function should never be used between step
|
||||
* functions; simply request the header again if you need to.
|
||||
*
|
||||
* @param out where to write pointer to header value
|
||||
* @param out_len where to store length of out value
|
||||
* @param req request to look for header in
|
||||
* @param type type of header to look for
|
||||
*/
|
||||
lnm_err lnm_http_req_header_get(const char **out, size_t *out_len,
|
||||
lnm_http_req *req, lnm_http_header type);
|
||||
|
||||
/**
|
||||
* Retrieve a specific header from the request by specifying its name.
|
||||
*
|
||||
* Pointers retrieved from this function should never be used between step
|
||||
* functions; simply request the header again if you need to.
|
||||
*
|
||||
* @param out where to write pointer to header value
|
||||
* @param out_len where to store length of out value
|
||||
* @param req request to look for header in
|
||||
* @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_http_req *req, const char *name);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,119 @@
|
|||
#ifndef LNM_HTTP_RES
|
||||
#define LNM_HTTP_RES
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "lnm/common.h"
|
||||
#include "lnm/http/consts.h"
|
||||
|
||||
typedef lnm_err (*data_fn)(uint64_t *written, char *buf, lnm_http_conn *conn,
|
||||
uint64_t offset, uint64_t len);
|
||||
|
||||
/**
|
||||
* Linked list elements used to store the response headers
|
||||
*/
|
||||
typedef struct lnm_http_res_header {
|
||||
struct {
|
||||
char *s;
|
||||
size_t len;
|
||||
bool owned;
|
||||
} name;
|
||||
struct {
|
||||
char *s;
|
||||
size_t len;
|
||||
bool owned;
|
||||
} value;
|
||||
struct lnm_http_res_header *next;
|
||||
} lnm_http_res_header;
|
||||
|
||||
typedef enum lnm_http_res_body_type {
|
||||
lnm_http_res_body_type_file = 0,
|
||||
lnm_http_res_body_type_buf,
|
||||
lnm_http_res_body_type_fn,
|
||||
} lnm_http_res_body_type;
|
||||
|
||||
typedef struct lnm_http_res {
|
||||
lnm_http_status status;
|
||||
struct {
|
||||
lnm_http_res_header *head;
|
||||
lnm_http_res_header *current;
|
||||
} headers;
|
||||
struct {
|
||||
struct {
|
||||
char *buf;
|
||||
FILE *f;
|
||||
data_fn fn;
|
||||
} data;
|
||||
uint64_t len;
|
||||
bool owned;
|
||||
lnm_http_res_body_type type;
|
||||
} body;
|
||||
// General-purpose; meaning depends on the current state
|
||||
uint64_t written;
|
||||
} lnm_http_res;
|
||||
|
||||
/**
|
||||
* Add a new header of a known type to the response
|
||||
*
|
||||
* @param type type of header
|
||||
* @param value null-terminated string containing the value of the header
|
||||
* @param value_owned whether to take ownership of the value pointer; if false,
|
||||
* free'ing the buffer is the caller's responsibility
|
||||
*/
|
||||
lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type,
|
||||
char *value, bool value_owned);
|
||||
|
||||
/**
|
||||
* Add a new header of a known type to the response with a given value length.
|
||||
*
|
||||
* @param type type of header
|
||||
* @param value string of length `value_len` containing the value of the header
|
||||
* @param value_len length of value
|
||||
* @param value_owned whether to take ownership of the value pointer; if false,
|
||||
* free'ing the buffer is the caller's responsibility
|
||||
*/
|
||||
lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type,
|
||||
char *value, size_t value_len,
|
||||
bool value_owned);
|
||||
|
||||
/**
|
||||
* Set the request body to the given file pointer.
|
||||
*
|
||||
* @param res response to modify
|
||||
* @param f file pointer to use as data
|
||||
* @param len expected length of the file
|
||||
* @param owned whether to take ownership of the file pointer
|
||||
*/
|
||||
void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len,
|
||||
bool owned);
|
||||
|
||||
/**
|
||||
* Set the request body to the given buffer.
|
||||
*
|
||||
* @param res response to modify
|
||||
* @param buf buffer to use as data
|
||||
* @param len length of the buffer
|
||||
* @param owned whether to take ownership of the file pointer
|
||||
*/
|
||||
void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len,
|
||||
bool owned);
|
||||
|
||||
/**
|
||||
* Set the request body to be read from the given data function.
|
||||
*
|
||||
* @param res response to modify
|
||||
* @param fn data reader function
|
||||
* @param len expected length of the response
|
||||
*/
|
||||
void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len);
|
||||
|
||||
/**
|
||||
* Reset the given response object, properly free'ing any allocated buffers,
|
||||
* allowing it to be reused for later connections.
|
||||
*
|
||||
* @param res res to reset
|
||||
*/
|
||||
void lnm_http_res_reset(lnm_http_res *res);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef LNM_LOG
|
||||
#define LOG
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "lnm/common.h"
|
||||
|
||||
typedef struct lnm_logger lnm_logger;
|
||||
|
||||
typedef enum lnm_log_level {
|
||||
lnm_log_level_debug = 0,
|
||||
lnm_log_level_info,
|
||||
lnm_log_level_notice,
|
||||
lnm_log_level_warning,
|
||||
lnm_log_level_error,
|
||||
lnm_log_level_critical
|
||||
} lnm_log_level;
|
||||
|
||||
extern const char *lnm_log_level_names[];
|
||||
|
||||
/**
|
||||
* Initialize the global logger.
|
||||
*/
|
||||
lnm_err lnm_log_init_global();
|
||||
|
||||
/**
|
||||
* Register stdout as one of the streams for the global logger.
|
||||
*/
|
||||
lnm_err lnm_log_register_stdout(lnm_log_level level);
|
||||
|
||||
void lnm_log(lnm_log_level level, const char *section, const char *fmt, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
||||
|
||||
#define lnm_ldebug(section, fmt, ...) \
|
||||
lnm_log(lnm_log_level_debug, section, fmt, __VA_ARGS__)
|
||||
#define lnm_linfo(section, fmt, ...) \
|
||||
lnm_log(lnm_log_level_info, section, fmt, __VA_ARGS__)
|
||||
#define lnm_lnotice(section, fmt, ...) \
|
||||
lnm_log(lnm_log_level_notice, section, fmt, __VA_ARGS__)
|
||||
#define lnm_lwarning(section, fmt, ...) \
|
||||
lnm_log(lnm_log_level_warning, section, fmt, __VA_ARGS__)
|
||||
#define lnm_lerror(section, fmt, ...) \
|
||||
lnm_log(lnm_log_level_error, section, fmt, __VA_ARGS__)
|
||||
#define lnm_lcritical(section, fmt, ...) \
|
||||
lnm_log(lnm_log_level_critical, section, fmt, __VA_ARGS__)
|
||||
|
||||
#endif
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef LNM_LOOP
|
||||
#define LNM_LOOP
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lnm/common.h"
|
||||
|
||||
#define LNM_LOOP_BUF_SIZE 2048
|
||||
|
||||
typedef enum {
|
||||
lnm_loop_state_req = 0,
|
||||
lnm_loop_state_res,
|
||||
lnm_loop_state_end,
|
||||
} lnm_loop_state;
|
||||
|
||||
typedef struct lnm_loop_conn {
|
||||
int fd;
|
||||
lnm_loop_state state;
|
||||
void *ctx;
|
||||
struct {
|
||||
char buf[LNM_LOOP_BUF_SIZE];
|
||||
size_t size;
|
||||
size_t read;
|
||||
} r;
|
||||
struct {
|
||||
char buf[LNM_LOOP_BUF_SIZE];
|
||||
size_t size;
|
||||
} w;
|
||||
} lnm_loop_conn;
|
||||
|
||||
typedef struct lnm_loop {
|
||||
int listen_fd;
|
||||
int epoll_fd;
|
||||
atomic_int open;
|
||||
void *gctx;
|
||||
lnm_err (*ctx_init)(void **out, void *gctx);
|
||||
void (*ctx_free)(void *ctx);
|
||||
void (*data_read)(lnm_loop_conn *conn);
|
||||
void (*data_write)(lnm_loop_conn *conn);
|
||||
} lnm_loop;
|
||||
|
||||
lnm_err lnm_loop_init(lnm_loop **out, void *gctx,
|
||||
lnm_err (*ctx_init)(void **out, void *gctx),
|
||||
void (*ctx_free)(void *ctx),
|
||||
void (*data_read)(lnm_loop_conn *conn),
|
||||
void (*data_write)(lnm_loop_conn *conn));
|
||||
|
||||
lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port);
|
||||
|
||||
lnm_err lnm_loop_run(lnm_loop *l, int thread_count);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef picohttpparser_h
|
||||
#define picohttpparser_h
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define ssize_t intptr_t
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* contains name and value of a header (name == NULL if is a continuing line
|
||||
* of a multiline header */
|
||||
struct phr_header {
|
||||
const char *name;
|
||||
size_t name_len;
|
||||
const char *value;
|
||||
size_t value_len;
|
||||
};
|
||||
|
||||
/* returns number of bytes consumed if successful, -2 if request is partial,
|
||||
* -1 if failed */
|
||||
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
|
||||
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||
struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* should be zero-filled before start */
|
||||
struct phr_chunked_decoder {
|
||||
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
|
||||
char consume_trailer; /* if trailing headers should be consumed */
|
||||
char _hex_count;
|
||||
char _state;
|
||||
};
|
||||
|
||||
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
|
||||
* encoding headers. When the function returns without an error, bufsz is
|
||||
* updated to the length of the decoded data available. Applications should
|
||||
* repeatedly call the function while it returns -2 (incomplete) every time
|
||||
* supplying newly arrived data. If the end of the chunked-encoded data is
|
||||
* found, the function returns a non-negative number indicating the number of
|
||||
* octets left undecoded, that starts from the offset returned by `*bufsz`.
|
||||
* Returns -1 on error.
|
||||
*/
|
||||
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
|
||||
|
||||
/* returns if the chunked decoder is in middle of chunked data */
|
||||
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,69 @@
|
|||
#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_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.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Initialize a new global context object.
|
||||
*
|
||||
* @param out where to store pointer to new `lnm_http_loop_gctx`
|
||||
*/
|
||||
lnm_err lnm_http_loop_gctx_init(lnm_http_loop_gctx **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);
|
||||
|
||||
/**
|
||||
* Initialize a new context.
|
||||
*
|
||||
* @param out where to store pointer to new object
|
||||
* @param gctx global context for the loop
|
||||
*/
|
||||
lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out,
|
||||
lnm_http_loop_gctx *gctx);
|
||||
|
||||
void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx);
|
||||
|
||||
void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx);
|
||||
|
||||
void lnm_http_loop_process(lnm_http_conn *conn);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef LNM_LOG_INTERNAL
|
||||
#define LNM_LOG_INTERNAL
|
||||
|
||||
#include "lnm/log.h"
|
||||
|
||||
typedef enum lnm_logger_stream_type {
|
||||
lnm_logger_stream_type_file = 0
|
||||
} lnm_logger_stream_type;
|
||||
|
||||
typedef struct lnm_logger_stream {
|
||||
void *ptr;
|
||||
lnm_logger_stream_type type;
|
||||
lnm_log_level level;
|
||||
} lnm_logger_stream;
|
||||
|
||||
struct lnm_logger {
|
||||
struct {
|
||||
lnm_logger_stream **arr;
|
||||
size_t len;
|
||||
} streams;
|
||||
};
|
||||
|
||||
lnm_err lnm_logger_stream_register(lnm_logger *logger,
|
||||
lnm_logger_stream *stream);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef LNM_LOOP_INTERNAL
|
||||
#define LNM_LOOP_INTERNAL
|
||||
|
||||
#include "lnm/loop.h"
|
||||
|
||||
lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l);
|
||||
|
||||
void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn);
|
||||
|
||||
lnm_err lnm_loop_accept(lnm_loop *l);
|
||||
|
||||
void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,101 @@
|
|||
#include "lnm/http/consts.h"
|
||||
|
||||
const char *lnm_http_method_names[] = {"GET", "POST", "PUT",
|
||||
"PATCH", "DELETE", "HEAD"};
|
||||
const size_t lnm_http_method_names_len =
|
||||
sizeof(lnm_http_method_names) / sizeof(lnm_http_method_names[0]);
|
||||
|
||||
// clang-format off
|
||||
|
||||
const char *lnm_http_status_names[][32] = {
|
||||
// 1xx
|
||||
{
|
||||
"Continue", // 100
|
||||
"Switching Protocols", // 101,
|
||||
"Processing", // 102
|
||||
"Early Hints", // 103
|
||||
},
|
||||
// 2xx
|
||||
{
|
||||
"OK", // 200
|
||||
"Created", // 201
|
||||
"Accepted", // 202
|
||||
"Non-Authoritative Information", // 203
|
||||
"No Content", // 204
|
||||
"Reset Content", // 205
|
||||
"Partial Content", // 206
|
||||
"Multi-Status", // 207
|
||||
"Already Reported", // 208
|
||||
},
|
||||
// 3xx
|
||||
{
|
||||
"Multiple Choices", // 300
|
||||
"Moved Permanently", // 301
|
||||
"Found", // 302
|
||||
"See Other", // 303
|
||||
"Not Modified", // 304
|
||||
NULL, // 305
|
||||
NULL, // 306
|
||||
"Temporary Redirect", // 307
|
||||
"Permanent Redirect", // 308
|
||||
},
|
||||
// 4xx
|
||||
{
|
||||
"Bad Request", // 400
|
||||
"Unauthorized", // 401
|
||||
"Payment Required", // 402
|
||||
"Forbidden", // 403
|
||||
"Not Found", // 404
|
||||
"Method Not Allowed", // 405
|
||||
"Not Acceptable", // 406
|
||||
"Proxy Authentication Required", // 407
|
||||
"Request Timeout", // 408
|
||||
"Conflict", // 409
|
||||
"Gone", // 410
|
||||
"Length Required", // 411
|
||||
"Precondition Failed", // 412
|
||||
"Content Too Large", // 413
|
||||
"URI Too Long", // 414
|
||||
"Unsupported Media Type", // 415
|
||||
"Range Not Satisfiable", // 416
|
||||
"Expectation Failed", // 417
|
||||
"I'm a teapot", // 418
|
||||
NULL, // 419
|
||||
NULL, // 420
|
||||
"Misdirected Request", // 421
|
||||
"Unprocessable Content", // 422
|
||||
"Locked", // 423
|
||||
"Failed Dependency", // 424
|
||||
"Too Early", // 425
|
||||
"Upgrade Required", // 426
|
||||
NULL, // 427
|
||||
"Precondition Required", // 428
|
||||
"Too Many Requests", // 429
|
||||
NULL, // 430
|
||||
"Request Header Fields Too Large", // 431
|
||||
},
|
||||
// 5xx
|
||||
{
|
||||
"Internal Server Error", // 500
|
||||
"Not Implemented", // 501
|
||||
"Bad Gateway", // 502
|
||||
"Service Unavailable", // 503
|
||||
"Gateway Timeout", // 504
|
||||
"HTTP Version Not Supported", // 505
|
||||
"Variant Also Negotiates", // 506
|
||||
"Insufficient Storage", // 507
|
||||
"Loop Detected", // 508
|
||||
NULL, // 509
|
||||
"Not Extended", // 510
|
||||
"Network Authentication Required" // 511
|
||||
},
|
||||
};
|
||||
|
||||
const char *lnm_http_header_names[] = {
|
||||
"Connection",
|
||||
"Location",
|
||||
"Content-Type",
|
||||
"Content-Disposition",
|
||||
"Server",
|
||||
"Content-Length"
|
||||
};
|
|
@ -0,0 +1,138 @@
|
|||
#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;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
#include "lnm/http/loop_internal.h"
|
||||
|
||||
lnm_err lnm_http_loop_gctx_init(lnm_http_loop_gctx **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_gctx *gctx = calloc(1, sizeof(lnm_http_loop_gctx));
|
||||
|
||||
if (gctx == NULL) {
|
||||
return lnm_err_failed_alloc;
|
||||
}
|
||||
|
||||
gctx->c = c_gctx;
|
||||
gctx->ctx_init = ctx_init;
|
||||
gctx->ctx_reset = ctx_reset;
|
||||
gctx->ctx_free = ctx_free;
|
||||
|
||||
*out = gctx;
|
||||
|
||||
return lnm_err_ok;
|
||||
}
|
||||
|
||||
lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out,
|
||||
lnm_http_loop_gctx *gctx) {
|
||||
lnm_http_loop_ctx *ctx = calloc(1, sizeof(lnm_http_loop_ctx));
|
||||
|
||||
if (ctx == NULL) {
|
||||
return lnm_err_failed_alloc;
|
||||
}
|
||||
|
||||
LNM_RES2(gctx->ctx_init(&ctx->c, gctx), free(ctx));
|
||||
|
||||
ctx->g = gctx;
|
||||
*out = ctx;
|
||||
|
||||
return lnm_err_ok;
|
||||
}
|
||||
|
||||
void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx) {
|
||||
ctx->g->ctx_reset(ctx->c);
|
||||
|
||||
lnm_http_req_reset(&ctx->req);
|
||||
lnm_http_res_reset(&ctx->res);
|
||||
|
||||
ctx->route = NULL;
|
||||
ctx->cur_step = NULL;
|
||||
}
|
||||
|
||||
void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx) {
|
||||
lnm_http_loop_ctx_reset(ctx);
|
||||
|
||||
ctx->g->ctx_free(ctx->c);
|
||||
|
||||
free(ctx);
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lnm/http/consts.h"
|
||||
#include "lnm/http/loop.h"
|
||||
#include "lnm/http/loop_internal.h"
|
||||
#include "lnm/http/req.h"
|
||||
#include "lnm/log.h"
|
||||
#include "lnm/loop.h"
|
||||
#include "lnm/loop_internal.h"
|
||||
|
||||
static const char *section = "http";
|
||||
|
||||
/* static const lnm_http_loop_state lnm_http_loop_state_first_req =
|
||||
* lnm_http_loop_state_parse_req; */
|
||||
static const lnm_http_loop_state lnm_http_loop_state_first_res =
|
||||
lnm_http_loop_state_add_headers;
|
||||
|
||||
void lnm_http_loop_process_parse_req(lnm_http_conn *conn) {
|
||||
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||
|
||||
lnm_http_parse_err res = lnm_http_req_parse(
|
||||
&ctx->req, &conn->r.buf[conn->r.read], conn->r.size - conn->r.read);
|
||||
|
||||
switch (res) {
|
||||
case lnm_http_parse_err_ok:
|
||||
conn->r.read += ctx->req.buf.len;
|
||||
ctx->state = lnm_http_loop_state_route;
|
||||
|
||||
lnm_linfo(section, "%s %.*s HTTP/1.%i",
|
||||
lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len,
|
||||
ctx->req.buf.s + ctx->req.path.o, ctx->req.minor_version);
|
||||
break;
|
||||
case lnm_http_parse_err_incomplete:
|
||||
// If the request is already the size of the read buffer, we close the
|
||||
// request. Otherwise, we wait for anything read
|
||||
if (conn->r.size - conn->r.read == LNM_LOOP_BUF_SIZE) {
|
||||
lnm_linfo(section, "Received request larger than buffer (%i bytes)",
|
||||
LNM_LOOP_BUF_SIZE);
|
||||
|
||||
conn->state = lnm_loop_state_end;
|
||||
}
|
||||
break;
|
||||
case lnm_http_parse_err_invalid:
|
||||
lnm_linfo(section, "%s", "Received invalid request");
|
||||
|
||||
conn->state = lnm_loop_state_end;
|
||||
break;
|
||||
case lnm_http_parse_err_unknown_method:
|
||||
ctx->res.status = lnm_http_status_method_not_implemented;
|
||||
ctx->state = lnm_http_loop_state_first_res;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void lnm_http_loop_process_route(lnm_http_conn *conn) {
|
||||
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||
lnm_http_loop_gctx *gctx = ctx->g;
|
||||
|
||||
// 0: no match
|
||||
// 1: matched route, but not method
|
||||
// 2: fully matched route
|
||||
int match_level = 0;
|
||||
lnm_http_route *route;
|
||||
|
||||
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;
|
||||
case 2:
|
||||
ctx->res.status = lnm_http_status_method_not_allowed;
|
||||
ctx->state = lnm_http_loop_state_first_res;
|
||||
break;
|
||||
case 3:
|
||||
ctx->route = route;
|
||||
ctx->cur_step = route->step;
|
||||
ctx->state = lnm_http_loop_state_parse_headers;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) {
|
||||
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||
lnm_http_req *req = &ctx->req;
|
||||
|
||||
const char *value;
|
||||
size_t value_len;
|
||||
if (lnm_http_req_header_get(&value, &value_len, req,
|
||||
lnm_http_header_content_length) == lnm_err_ok) {
|
||||
req->body.expected_len = lnm_atoi(value, value_len);
|
||||
}
|
||||
|
||||
ctx->state = lnm_http_loop_state_steps;
|
||||
}
|
||||
|
||||
void lnm_http_loop_process_steps(lnm_http_conn *conn) {
|
||||
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||
lnm_http_step *step = NULL;
|
||||
|
||||
// Loop until we either:
|
||||
// - reach the end of the chain of steps, indicated by NULL
|
||||
// - have a step that's waiting for I/O
|
||||
while ((ctx->cur_step != NULL) && (step != ctx->cur_step)) {
|
||||
step = ctx->cur_step;
|
||||
|
||||
switch (step->fn(conn)) {
|
||||
case lnm_http_step_err_done:
|
||||
ctx->cur_step = ctx->cur_step->next;
|
||||
break;
|
||||
case lnm_http_step_err_io_needed:
|
||||
break;
|
||||
case lnm_http_step_err_close:
|
||||
conn->state = lnm_loop_state_end;
|
||||
break;
|
||||
case lnm_http_step_err_res:
|
||||
ctx->state = lnm_http_loop_state_first_res;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx->cur_step == NULL) {
|
||||
ctx->state = lnm_http_loop_state_add_headers;
|
||||
}
|
||||
}
|
||||
|
||||
void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) {
|
||||
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||
lnm_http_res *res = &ctx->res;
|
||||
|
||||
uint64_t digits = lnm_digits(res->body.len);
|
||||
char *buf = malloc(digits + 1);
|
||||
|
||||
if (buf == NULL) {
|
||||
conn->state = lnm_loop_state_end;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sprintf(buf, "%lu", res->body.len);
|
||||
lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, digits,
|
||||
true);
|
||||
|
||||
if (ctx->g->server != NULL) {
|
||||
lnm_http_res_add_header(res, lnm_http_header_server, (char *)ctx->g->server,
|
||||
false);
|
||||
}
|
||||
|
||||
if (res->status == 0) {
|
||||
res->status = lnm_http_status_ok;
|
||||
}
|
||||
|
||||
lnm_linfo(section, "%i %s", res->status,
|
||||
lnm_http_status_names[res->status / 100 - 1][res->status % 100]);
|
||||
|
||||
ctx->state = lnm_http_loop_state_write_status_line;
|
||||
}
|
||||
|
||||
// This function is intentionally written inefficiently for now, as it will most
|
||||
// likely only have to run once for each response
|
||||
void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) {
|
||||
lnm_http_loop_ctx *ctx = conn->ctx;
|
||||
lnm_http_res *res = &ctx->res;
|
||||
|
||||
const char *response_type_name =
|
||||
lnm_http_status_names[res->status / 100 - 1][res->status % 100];
|
||||
|
||||
// First we calculate the size of the start of the header
|
||||
size_t buf_size =
|
||||
snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name);
|
||||
char buf[buf_size + 1];
|
||||
sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name);
|
||||
|
||||
size_t to_write =
|
||||
LNM_MIN(buf_size - res->written, LNM_LOOP_BUF_SIZE - conn->w.size);
|
||||
memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write);
|
||||
|
||||
conn->w.size += to_write;
|
||||
res->written += to_write;
|
||||
|
||||
if (res->written == buf_size) {
|
||||