update build, silent folders, use threads

This commit is contained in:
SimonFJ20 2025-03-06 15:59:41 +01:00
parent f8438c591b
commit 2b1b536172
13 changed files with 230 additions and 56 deletions

View File

@ -9,10 +9,13 @@
# NOTICE that `RELEASE=1` is __after__ `make` # NOTICE that `RELEASE=1` is __after__ `make`
# #
MAKEFLAGS += -j $(shell nproc)
C_FLAGS = \ C_FLAGS = \
-std=c17 \ -std=c17 \
-Wall -Wextra -Wpedantic -Wconversion \ -Wall -Wextra -Wpedantic -Wconversion \
-pedantic -pedantic-errors \ -pedantic -pedantic-errors \
-Wno-unused-parameter \
L_FLAGS = -lm -pthread $(shell pkg-config sqlite3 openssl --libs) L_FLAGS = -lm -pthread $(shell pkg-config sqlite3 openssl --libs)
C_FLAGS = $(shell pkg-config sqlite3 openssl --cflags) C_FLAGS = $(shell pkg-config sqlite3 openssl --cflags)
@ -38,13 +41,15 @@ O_FILES = $(patsubst src/%.c,build/%.o,$(C_FILES))
CC = gcc CC = gcc
all: server TARGET=server
server: $(O_FILES) all: build/$(TARGET)
$(CC) -o build/$@ $^ $(F_FLAGS) $(OPTIMIZATION) $(L_FLAGS)
build/$(TARGET): $(O_FILES)
$(CC) -o $@ $^ $(F_FLAGS) $(OPTIMIZATION) $(L_FLAGS)
build/%.o: src/%.c $(HEADERS) build/%.o: src/%.c $(HEADERS)
mkdir -p $(@D) @mkdir -p $(dir $@)
$(CC) $< -c -o $@ $(C_FLAGS) $(OPTIMIZATION) $(F_FLAGS) $(CC) $< -c -o $@ $(C_FLAGS) $(OPTIMIZATION) $(F_FLAGS)
clean: clean:

View File

@ -6,4 +6,5 @@ xc
-Wconversion -Wconversion
-pedantic -pedantic
-pedantic-errors -pedantic-errors
-Wno-unused-parameter

View File

@ -10,6 +10,14 @@ typedef struct {
Db* db; Db* db;
} Cx; } Cx;
void route_get_index(HttpCtx* ctx);
void route_post_set_number(HttpCtx* ctx);
void route_get_not_found(HttpCtx* ctx);
void route_get_products_all(HttpCtx* ctx);
void route_post_user_register(HttpCtx* ctx);
#define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \ #define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \
{ \ { \
HttpCtx* _ctx = (HTTP_CTX); \ HttpCtx* _ctx = (HTTP_CTX); \
@ -30,4 +38,5 @@ typedef struct {
#define RESPOND_JSON(HTTP_CTX, STATUS, ...) \ #define RESPOND_JSON(HTTP_CTX, STATUS, ...) \
RESPOND(HTTP_CTX, STATUS, "application/json", __VA_ARGS__) RESPOND(HTTP_CTX, STATUS, "application/json", __VA_ARGS__)
void route_get_products_all(HttpCtx* ctx); #define RESPOND_BAD_REQUEST(HTTP_CTX) \
RESPOND_JSON(HTTP_CTX, 400, "{\"ok\":false}")

View File

@ -10,7 +10,7 @@ void route_get_products_all(HttpCtx* ctx)
ProductVec products; ProductVec products;
product_vec_construct(&products); product_vec_construct(&products);
DbRes db_res = db_product_all_fill(cx->db, &products); DbRes db_res = db_product_all(cx->db, &products);
if (db_res != DbRes_Ok) { if (db_res != DbRes_Ok) {
RESPOND_JSON(ctx, 500, "{\"ok\":false,\"msg\":\"db error\"}"); RESPOND_JSON(ctx, 500, "{\"ok\":false,\"msg\":\"db error\"}");
return; return;

View File

@ -3,6 +3,7 @@
#include "collection.h" #include "collection.h"
#include "models.h" #include "models.h"
DEFINE_VEC(int64_t, Ids, ids, 8)
DEFINE_VEC(Product, ProductVec, product_vec, 32) DEFINE_VEC(Product, ProductVec, product_vec, 32)
typedef enum { typedef enum {
@ -16,5 +17,8 @@ typedef struct Db Db;
/// `user.id` field is ignored. /// `user.id` field is ignored.
DbRes db_user_insert(Db* db, User* user); DbRes db_user_insert(Db* db, User* user);
DbRes db_user_from_id(Db* db, User* user, int64_t id); DbRes db_user_from_id(Db* db, User* user, int64_t id);
/// Expects `ids` to be constructed.
DbRes db_users_with_email(Db* db, Ids* ids, const char* email);
DbRes db_product_all_fill(Db* db, ProductVec* vec); /// Expects `vec` to be constructed.
DbRes db_product_all(Db* db, ProductVec* vec);

View File

@ -139,7 +139,39 @@ l0_return:
return res; return res;
} }
DbRes db_product_all_fill(Db* db, ProductVec* vec) DbRes db_users_with_email(Db* db, Ids* ids, const char* email)
{
static_assert(sizeof(User) == 40, "model has changed");
sqlite3* connection;
CONNECT;
DbRes res;
int sqlite_res;
sqlite3_stmt* stmt;
sqlite_res = sqlite3_prepare_v2(
connection, "SELECT id FROM users WHERE email = ?", -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, email, -1, NULL);
while ((sqlite_res = sqlite3_step(stmt)) == SQLITE_ROW) {
int64_t id = GET_INT(0);
ids_push(ids, id);
}
if (sqlite_res != SQLITE_DONE) {
fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection));
res = DbRes_Error;
goto l0_return;
}
res = DbRes_Ok;
l0_return:
if (stmt)
sqlite3_finalize(stmt);
DISCONNECT;
return res;
}
DbRes db_product_all(Db* db, ProductVec* vec)
{ {
sqlite3* connection; sqlite3* connection;
CONNECT; CONNECT;

View File

@ -130,6 +130,11 @@ void* http_ctx_user_ctx(HttpCtx* ctx)
return ctx->user_ctx; return ctx->user_ctx;
} }
const char* http_ctx_req_path(HttpCtx* ctx)
{
return ctx->req->path;
}
bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key) bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key)
{ {
return req_has_header(ctx->req, key); return req_has_header(ctx->req, key);
@ -274,7 +279,7 @@ static inline void worker_handle_request(Worker* worker, Client* client)
MAX_HEADER_BUFFER_SIZE); MAX_HEADER_BUFFER_SIZE);
goto l0_return; goto l0_return;
} }
puts((char*)buffer); // puts((char*)buffer);
Req req; Req req;
size_t body_idx; size_t body_idx;
@ -309,6 +314,8 @@ static inline void worker_handle_request(Worker* worker, Client* client)
header_vec_construct(&handler_ctx.res_headers); header_vec_construct(&handler_ctx.res_headers);
bool been_handled = false;
for (size_t i = 0; i < worker->ctx->server->handlers.size; ++i) { for (size_t i = 0; i < worker->ctx->server->handlers.size; ++i) {
Handler* handler = &worker->ctx->server->handlers.data[i]; Handler* handler = &worker->ctx->server->handlers.data[i];
if (handler->method != req.method) if (handler->method != req.method)
@ -316,9 +323,14 @@ static inline void worker_handle_request(Worker* worker, Client* client)
if (strcmp(handler->path, req.path) != 0) if (strcmp(handler->path, req.path) != 0)
continue; continue;
handler->handler(&handler_ctx); handler->handler(&handler_ctx);
been_handled = true;
break; break;
} }
if (!been_handled && worker->ctx->server->not_found_handler != NULL) {
worker->ctx->server->not_found_handler(&handler_ctx);
}
l1_return: l1_return:
header_vec_destroy(&handler_ctx.res_headers); header_vec_destroy(&handler_ctx.res_headers);
req_destroy(&req); req_destroy(&req);
@ -333,11 +345,11 @@ static inline int parse_header(
{ {
StrSplitter splitter = str_split(buf, buf_size, "\r\n"); StrSplitter splitter = str_split(buf, buf_size, "\r\n");
StrSlice first = strsplit_next(&splitter); StrSlice req_line = strsplit_next(&splitter);
StrSplitter first_splitter = str_split(first.ptr, first.len, " "); StrSplitter req_line_splitter = str_split(req_line.ptr, req_line.len, " ");
StrSlice method_str = strsplit_next(&first_splitter); StrSlice method_str = strsplit_next(&req_line_splitter);
StrSlice path_str = strsplit_next(&first_splitter); StrSlice uri_str = strsplit_next(&req_line_splitter);
StrSlice version_str = strsplit_next(&first_splitter); StrSlice version_str = strsplit_next(&req_line_splitter);
if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) { if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) {
fprintf(stderr, "error: unrecognized http version '%.*s'\n", fprintf(stderr, "error: unrecognized http version '%.*s'\n",
@ -356,14 +368,32 @@ static inline int parse_header(
return -1; return -1;
} }
if (path_str.len >= MAX_PATH_LEN + 1) { if (uri_str.len >= MAX_PATH_LEN + 1) {
fprintf(stderr, "error: path too long\n"); fprintf(stderr, "error: path too long\n");
return -1; return -1;
} }
char* path = calloc(MAX_PATH_LEN + 1, sizeof(char)); size_t path_len = 0;
strncpy(path, path_str.ptr, path_str.len); while (path_len < uri_str.len && uri_str.ptr[path_len] != '?'
path[path_str.len] = '\0'; && uri_str.ptr[path_len] != '#') {
path_len += 1;
}
char* path = calloc(path_len + 1, sizeof(char));
strncpy(path, uri_str.ptr, path_len);
path[path_len] = '\0';
char* query = NULL;
if (path_len < uri_str.len) {
size_t query_len = 0;
while (path_len + query_len < uri_str.len
&& uri_str.ptr[path_len + query_len] != '#') {
query_len += 1;
}
query = calloc(query_len + 1, sizeof(char));
strncpy(query, &uri_str.ptr[path_len], query_len);
query[query_len] = '\0';
}
HeaderVec headers; HeaderVec headers;
header_vec_construct(&headers); header_vec_construct(&headers);
@ -402,7 +432,7 @@ static inline int parse_header(
header_vec_push(&headers, (Header) { key, value }); header_vec_push(&headers, (Header) { key, value });
} }
*req = (Req) { method, path, headers }; *req = (Req) { method, path, query, headers };
return 0; return 0;
} }

View File

@ -29,6 +29,7 @@ void http_server_post(
void http_server_set_not_found(HttpServer* server, HttpHandlerFn handler); void http_server_set_not_found(HttpServer* server, HttpHandlerFn handler);
void* http_ctx_user_ctx(HttpCtx* ctx); void* http_ctx_user_ctx(HttpCtx* ctx);
const char* http_ctx_req_path(HttpCtx* ctx);
bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key); bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key);
const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key); const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key);
const char* http_ctx_req_body(HttpCtx* ctx); const char* http_ctx_req_body(HttpCtx* ctx);

View File

@ -47,6 +47,7 @@ static inline void worker_handle_request(Worker* worker, Client* req);
#define MAX_HEADER_BUFFER_SIZE 8192 #define MAX_HEADER_BUFFER_SIZE 8192
#define MAX_PATH_LEN 128 - 1 #define MAX_PATH_LEN 128 - 1
#define MAX_QUERY_LEN 128 - 1
#define MAX_HEADERS_LEN 32 #define MAX_HEADERS_LEN 32
#define MAX_HEADER_KEY_LEN 32 - 1 #define MAX_HEADER_KEY_LEN 32 - 1
#define MAX_HEADER_VALUE_LEN 512 - 1 #define MAX_HEADER_VALUE_LEN 512 - 1
@ -66,6 +67,7 @@ DEFINE_VEC(Header, HeaderVec, header_vec, 8)
typedef struct { typedef struct {
Method method; Method method;
char* path; char* path;
char* query;
HeaderVec headers; HeaderVec headers;
} Req; } Req;

View File

@ -8,42 +8,6 @@
#include <sqlite3.h> #include <sqlite3.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
void route_get_index(HttpCtx* ctx)
{
Cx* cx = http_ctx_user_ctx(ctx);
RESPOND_HTML(ctx, 200,
"<!DOCTYPE html><html><head><meta "
"charset=\"utf-8\"></head><body><h1>Number = %d</h1></body></html>",
cx->number);
}
void route_post_set_number(HttpCtx* ctx)
{
Cx* cx = http_ctx_user_ctx(ctx);
const char* body_text = http_ctx_req_body(ctx);
JsonParser parser;
json_parser_construct(&parser, body_text, strlen(body_text));
JsonValue* body = json_parser_parse(&parser);
json_parser_destroy(&parser);
if (!json_object_has(body, "value")) {
RESPOND_JSON(
ctx, 200, "{\"ok\": false, \"msg\": \"no 'value' key\"}\r\n");
goto l0_return;
}
int64_t value = json_int(json_object_get(body, "value"));
cx->number = (int)value;
RESPOND_JSON(ctx, 200, "{\"ok\": true}\r\n");
l0_return:
json_free(body);
}
HttpServer* server; HttpServer* server;
int main(void) int main(void)
@ -65,11 +29,15 @@ int main(void)
http_server_set_user_ctx(server, &cx); http_server_set_user_ctx(server, &cx);
http_server_get(server, "/products/all", route_get_products_all); http_server_get(server, "/api/products/all", route_get_products_all);
http_server_post(server, "/api/users/register", route_post_user_register);
http_server_get(server, "/", route_get_index); http_server_get(server, "/", route_get_index);
http_server_post(server, "/set_number", route_post_set_number); http_server_post(server, "/set_number", route_post_set_number);
http_server_set_not_found(server, route_get_not_found);
printf("listening at http://127.0.0.1:8080/\n"); printf("listening at http://127.0.0.1:8080/\n");
http_server_listen(server); http_server_listen(server);

View File

@ -1,5 +1,6 @@
#include "models.h" #include "models.h"
#include "json.h" #include "json.h"
#include "models_json.h"
#include "str_util.h" #include "str_util.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
@ -7,6 +8,8 @@
void user_free(User* m) void user_free(User* m)
{ {
static_assert(sizeof(User) == 40, "model has changed");
free(m->name); free(m->name);
free(m->email); free(m->email);
free(m->password_hash); free(m->password_hash);
@ -14,30 +17,57 @@ void user_free(User* m)
void coord_free(Coord* m) void coord_free(Coord* m)
{ {
static_assert(sizeof(Coord) == 24, "model has changed");
(void)m; (void)m;
} }
void product_free(Product* m) void product_free(Product* m)
{ {
static_assert(sizeof(Product) == 40, "model has changed");
free(m->name); free(m->name);
free(m->barcode); free(m->barcode);
} }
void product_price_free(ProductPrice* m) void product_price_free(ProductPrice* m)
{ {
static_assert(sizeof(ProductPrice) == 24, "model has changed");
(void)m; (void)m;
} }
void cart_free(Cart* m) void cart_free(Cart* m)
{ {
static_assert(sizeof(Cart) == 16, "model has changed");
(void)m; (void)m;
} }
void cart_item_free(CartItem* m) void cart_item_free(CartItem* m)
{ {
static_assert(sizeof(CartItem) == 24, "model has changed");
(void)m; (void)m;
} }
void users_register_req_free(UsersRegisterReq* model)
{
static_assert(sizeof(UsersRegisterReq) == 24, "model has changed");
free(model->name);
free(model->email);
free(model->password);
}
void auth_login_req_free(AuthLoginReq* model)
{
static_assert(sizeof(AuthLoginReq) == 16, "model has changed");
free(model->email);
free(model->password);
}
char* user_to_json_string(const User* m) char* user_to_json_string(const User* m)
{ {
static_assert(sizeof(User) == 40, "model has changed"); static_assert(sizeof(User) == 40, "model has changed");
@ -155,6 +185,43 @@ char* cart_item_to_json_string(const CartItem* m)
return result; return result;
} }
char* users_register_req_to_json(const UsersRegisterReq* m)
{
static_assert(sizeof(UsersRegisterReq) == 24, "model has changed");
String string;
string_construct(&string);
string_pushf(&string,
"{"
"\"name\":\"%s\","
"\"email\":\"%s\","
"\"password\":\"%s\""
"}",
m->name, m->email, m->password);
char* result = string_copy(&string);
string_destroy(&string);
return result;
}
char* auth_login_req_to_json(const AuthLoginReq* m)
{
static_assert(sizeof(AuthLoginReq) == 16, "model has changed");
String string;
string_construct(&string);
string_pushf(&string,
"{"
"\"email\":\"%s\","
"\"password\":\"%s\""
"}",
m->email, m->password);
char* result = string_copy(&string);
string_destroy(&string);
return result;
}
typedef struct { typedef struct {
const char* key; const char* key;
JsonType type; JsonType type;
@ -299,3 +366,39 @@ int cart_item_from_json(CartItem* m, const JsonValue* json)
}; };
return 0; return 0;
} }
int users_register_req_from_json(UsersRegisterReq* m, const JsonValue* json)
{
static_assert(sizeof(UsersRegisterReq) == 24, "model has changed");
ObjField fields[] = {
{ "name", JsonType_String },
{ "email", JsonType_String },
{ "password", JsonType_String },
};
if (!obj_conforms(json, fields, sizeof(fields) / sizeof(fields[0])))
return -1;
*m = (UsersRegisterReq) {
.name = GET_STR("name"),
.email = GET_STR("email"),
.password = GET_STR("password"),
};
return 0;
}
int auth_login_req_from_json(AuthLoginReq* m, const JsonValue* json)
{
static_assert(sizeof(AuthLoginReq) == 16, "model has changed");
ObjField fields[] = {
{ "email", JsonType_String },
{ "password", JsonType_String },
};
if (!obj_conforms(json, fields, sizeof(fields) / sizeof(fields[0])))
return -1;
*m = (AuthLoginReq) {
.email = GET_STR("email"),
.password = GET_STR("password"),
};
return 0;
}

View File

@ -47,3 +47,19 @@ void product_free(Product* model);
void product_price_free(ProductPrice* model); void product_price_free(ProductPrice* model);
void cart_free(Cart* model); void cart_free(Cart* model);
void cart_item_free(CartItem* model); void cart_item_free(CartItem* model);
//
typedef struct {
char* name;
char* email;
char* password;
} UsersRegisterReq;
typedef struct {
char* email;
char* password;
} AuthLoginReq;
void users_register_req_free(UsersRegisterReq* model);
void auth_login_req_free(AuthLoginReq* model);

View File

@ -11,3 +11,6 @@ DEFINE_MODEL_JSON(Product, product)
DEFINE_MODEL_JSON(ProductPrice, product_price) DEFINE_MODEL_JSON(ProductPrice, product_price)
DEFINE_MODEL_JSON(Cart, cart) DEFINE_MODEL_JSON(Cart, cart)
DEFINE_MODEL_JSON(CartItem, cart_item) DEFINE_MODEL_JSON(CartItem, cart_item)
DEFINE_MODEL_JSON(UsersRegisterReq, users_register_req)
DEFINE_MODEL_JSON(AuthLoginReq, auth_login_req)