runtime: parse req, send res

This commit is contained in:
sfja 2025-03-05 03:33:55 +01:00
parent aeeed02c35
commit 355c907135
8 changed files with 575 additions and 95 deletions

View File

@ -3,29 +3,33 @@
#include <stddef.h>
#include <stdlib.h>
#define DEFINE_VEC(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY) \
#define MAYBE_UNUSED __attribute__((unused))
#define DEFINE_VEC_TYPE(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY) \
typedef TYPE VEC_TYPE##T; \
typedef struct { \
TYPE* data; \
VEC_TYPE##T* data; \
size_t capacity; \
size_t size; \
} VEC_TYPE; \
\
static inline int FN_PREFIX##_construct(VEC_TYPE* vec) \
} VEC_TYPE;
#define DEFINE_VEC_IMPL(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY) \
MAYBE_UNUSED static inline int FN_PREFIX##_construct(VEC_TYPE* vec) \
{ \
const size_t capacity = INITIAL_CAPACITY; \
TYPE* data = malloc(sizeof(TYPE) * capacity); \
VEC_TYPE##T* data = malloc(sizeof(VEC_TYPE##T) * capacity); \
if (!data) \
return -1; \
*vec = (VEC_TYPE) { data, capacity, .size = 0 }; \
return 0; \
} \
\
static inline void FN_PREFIX##_destroy(VEC_TYPE* vec) \
MAYBE_UNUSED static inline void FN_PREFIX##_destroy(VEC_TYPE* vec) \
{ \
free(vec->data); \
} \
\
static inline VEC_TYPE* FN_PREFIX##_new(void) \
MAYBE_UNUSED static inline VEC_TYPE* FN_PREFIX##_new(void) \
{ \
VEC_TYPE* vec = malloc(sizeof(VEC_TYPE)); \
if (!vec) \
@ -36,13 +40,14 @@
return vec; \
} \
\
static inline void FN_PREFIX##_free(VEC_TYPE* vec) \
MAYBE_UNUSED static inline void FN_PREFIX##_free(VEC_TYPE* vec) \
{ \
FN_PREFIX##_destroy(vec); \
free(vec); \
} \
\
static inline int FN_PREFIX##_push(VEC_TYPE* vec, TYPE value) \
MAYBE_UNUSED static inline int FN_PREFIX##_push( \
VEC_TYPE* vec, VEC_TYPE##T value) \
{ \
if (vec->size + 1 > vec->capacity) { \
size_t new_capacity = vec->capacity * 2; \
@ -57,22 +62,30 @@
return 0; \
} \
\
static inline TYPE* FN_PREFIX##_at(VEC_TYPE* vec, size_t idx) \
MAYBE_UNUSED static inline VEC_TYPE##T* FN_PREFIX##_at( \
VEC_TYPE* vec, size_t idx) \
{ \
return &vec->data[idx]; \
} \
\
static inline const TYPE* FN_PREFIX##_at_const( \
MAYBE_UNUSED static inline const VEC_TYPE##T* FN_PREFIX##_at_const( \
const VEC_TYPE* vec, size_t idx) \
{ \
return &vec->data[idx]; \
} \
\
static inline TYPE FN_PREFIX##_get(const VEC_TYPE* vec, size_t idx) \
MAYBE_UNUSED static inline VEC_TYPE##T FN_PREFIX##_get( \
const VEC_TYPE* vec, size_t idx) \
{ \
return vec->data[idx]; \
}
#define DEFINE_VEC(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY) \
DEFINE_VEC_TYPE(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY) \
DEFINE_VEC_IMPL(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY)
#define DEFINE_MAYBE_UNUSED
#define DEFINE_STATIC_QUEUE(TYPE, QUEUE_TYPE, FN_PREFIX) \
typedef struct { \
TYPE* data; \
@ -81,7 +94,7 @@
size_t front; \
} QUEUE_TYPE; \
\
static inline int FN_PREFIX##_construct( \
MAYBE_UNUSED static inline int FN_PREFIX##_construct( \
QUEUE_TYPE* queue, size_t capacity) \
{ \
*queue = (QUEUE_TYPE) { \
@ -93,12 +106,12 @@
return 0; \
} \
\
static inline void FN_PREFIX##_destroy(QUEUE_TYPE* queue) \
MAYBE_UNUSED static inline void FN_PREFIX##_destroy(QUEUE_TYPE* queue) \
{ \
free(queue->data); \
} \
\
static inline QUEUE_TYPE* FN_PREFIX##_new(size_t capacity) \
MAYBE_UNUSED static inline QUEUE_TYPE* FN_PREFIX##_new(size_t capacity) \
{ \
QUEUE_TYPE* queue = malloc(sizeof(QUEUE_TYPE)); \
if (!queue) \
@ -109,13 +122,14 @@
return queue; \
} \
\
static inline void FN_PREFIX##_free(QUEUE_TYPE* queue) \
MAYBE_UNUSED static inline void FN_PREFIX##_free(QUEUE_TYPE* queue) \
{ \
FN_PREFIX##_destroy(queue); \
free(queue); \
} \
\
static inline int FN_PREFIX##_push(QUEUE_TYPE* queue, TYPE req) \
MAYBE_UNUSED static inline int FN_PREFIX##_push( \
QUEUE_TYPE* queue, TYPE req) \
{ \
size_t front = queue->front + 1; \
if (front >= queue->capacity) { \
@ -129,14 +143,16 @@
return 0; \
} \
\
static inline size_t FN_PREFIX##_size(const QUEUE_TYPE* queue) \
MAYBE_UNUSED static inline size_t FN_PREFIX##_size( \
const QUEUE_TYPE* queue) \
{ \
return queue->front >= queue->back \
? queue->front - queue->back \
: (queue->capacity - queue->back) + queue->front; \
} \
\
static inline int FN_PREFIX##_pop(QUEUE_TYPE* queue, TYPE* req) \
MAYBE_UNUSED static inline int FN_PREFIX##_pop( \
QUEUE_TYPE* queue, TYPE* req) \
{ \
if (queue->back == queue->front) { \
return -1; \

View File

@ -1,5 +1,6 @@
#include "http_server.h"
#include "http_server_internal.h"
#include "str_util.h"
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
@ -294,6 +295,8 @@ static inline void worker_handle_request(Worker* worker, Client* client)
const char* length_val = req_get_header(&req, "Content-Length");
size_t length = strtoul(length_val, NULL, 10);
body = calloc(length + 1, sizeof(char));
strncpy(body, (char*)&buffer[body_idx], length);
body[length] = '\0';
}
HttpCtx handler_ctx = {
@ -327,13 +330,13 @@ l0_return:
static inline int parse_header(
Req* req, size_t* body_idx, const char* const buf, size_t buf_size)
{
Strlitter splitter = string_split(buf, buf_size, "\r\n");
StrSplitter splitter = str_split(buf, buf_size, "\r\n");
StrSlice first = split_next(&splitter);
Strlitter first_splitter = string_split(first.ptr, first.len, " ");
StrSlice method_str = split_next(&first_splitter);
StrSlice path_str = split_next(&first_splitter);
StrSlice version_str = split_next(&first_splitter);
StrSlice first = strsplit_next(&splitter);
StrSplitter first_splitter = str_split(first.ptr, first.len, " ");
StrSlice method_str = strsplit_next(&first_splitter);
StrSlice path_str = strsplit_next(&first_splitter);
StrSlice version_str = strsplit_next(&first_splitter);
if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) {
fprintf(stderr, "error: unrecognized http version '%.*s'\n",
@ -363,7 +366,7 @@ static inline int parse_header(
header_vec_construct(&headers);
while (headers.size < MAX_HEADERS_LEN) {
StrSlice line = split_next(&splitter);
StrSlice line = strsplit_next(&splitter);
if (line.len == 0) {
*body_idx = (size_t)&line.ptr[2] - (size_t)buf;
break;
@ -427,39 +430,3 @@ static inline const char* req_get_header(const Req* req, const char* key)
}
return NULL;
}
static inline Strlitter string_split(
const char* text, size_t text_len, const char* split)
{
return (Strlitter) {
.text = text,
.text_len = text_len,
.i = 0,
.split = split,
.split_len = strlen(split),
};
}
static inline StrSlice split_next(Strlitter* splitter)
{
for (size_t i = splitter->i; i < splitter->text_len; ++i) {
if (strncmp(&splitter->text[i], splitter->split, splitter->split_len)
== 0) {
const char* ptr = &splitter->text[splitter->i];
size_t len = i - splitter->i;
splitter->i = i + splitter->split_len;
return (StrSlice) { ptr, len };
}
}
return (StrSlice) {
.ptr = &splitter->text[splitter->i],
.len = splitter->text_len - splitter->i,
};
}
static inline void string_push_str(String* string, const char* str)
{
for (size_t i = 0; i < strlen(str); ++i) {
string_push(string, str[i]);
}
}

View File

@ -103,25 +103,4 @@ struct HttpCtx {
void* user_ctx;
};
typedef struct {
const char* ptr;
size_t len;
} StrSlice;
typedef struct {
const char* text;
size_t text_len;
size_t i;
const char* split;
size_t split_len;
} Strlitter;
static inline Strlitter string_split(
const char* text, size_t text_len, const char* split);
static inline StrSlice split_next(Strlitter* splitter);
DEFINE_VEC(char, String, string, 8)
static inline void string_push_str(String* string, const char* str);
const char* http_response_code_string(int code);

376
slige/runtime/src/json.c Normal file
View File

@ -0,0 +1,376 @@
#include "json.h"
#include "collection.h"
#include "str_util.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char* key;
JsonValue* val;
} KV;
DEFINE_VEC(JsonValue*, Arr, arr, 4)
DEFINE_VEC(KV, Obj, obj, 4)
struct JsonValue {
JsonType type;
union {
bool bool_val;
char* str_val;
Arr arr_val;
Obj obj_val;
};
};
bool json_is(const JsonValue* value, JsonType type)
{
return value != NULL && value->type == type;
}
bool json_bool(const JsonValue* value)
{
return value->bool_val;
}
int64_t json_int(const JsonValue* value)
{
return strtoll(value->str_val, NULL, 10);
}
double json_float(const JsonValue* value)
{
return strtof(value->str_val, NULL);
}
const char* json_string(const JsonValue* value)
{
return value->str_val;
}
size_t json_array_size(const JsonValue* value)
{
return value->arr_val.size;
}
const JsonValue* json_array_get(const JsonValue* value, size_t idx)
{
return value->arr_val.data[idx];
}
bool json_object_has(const JsonValue* value, const char* key)
{
for (size_t i = 0; i < value->obj_val.size; ++i) {
KV* kv = &value->obj_val.data[i];
if (strcmp(key, kv->key) == 0) {
return true;
}
}
return false;
}
const JsonValue* json_object_get(const JsonValue* value, const char* key)
{
for (size_t i = 0; i < value->obj_val.size; ++i) {
KV* kv = &value->obj_val.data[i];
if (strcmp(key, kv->key) == 0) {
return kv->val;
}
}
return NULL;
}
void json_value_free(JsonValue* value)
{
switch (value->type) {
case JsonType_Error:
case JsonType_Null:
case JsonType_Bool:
break;
case JsonType_Number:
case JsonType_String:
free(value->str_val);
break;
case JsonType_Array:
for (size_t i = 0; i < value->arr_val.size; ++i) {
json_value_free(value->arr_val.data[i]);
}
arr_destroy(&value->arr_val);
break;
case JsonType_Object:
for (size_t i = 0; i < value->obj_val.size; ++i) {
free(value->obj_val.data[i].key);
json_value_free(value->obj_val.data[i].val);
}
obj_destroy(&value->obj_val);
break;
}
}
#define TOK_EOF '\0'
#define TOK_ERROR 'e'
#define TOK_NULL 'n'
#define TOK_FALSE 'f'
#define TOK_TRUE 't'
#define TOK_NUMBER '0'
#define TOK_STRING '"'
static inline JsonValue* alloc(JsonValue init);
static inline void lex(JsonParser* p);
static inline void lstep(JsonParser* p);
void json_parser_construct(JsonParser* p, const char* text, size_t text_len)
{
*p = (JsonParser) {
text,
text_len,
.i = 0,
.ch = text[0],
.curr_tok = TOK_EOF,
.curr_val = NULL,
};
lex(p);
}
void json_parser_destroy(JsonParser* p)
{
(void)p;
}
JsonValue* json_parser_parse(JsonParser* p)
{
switch (p->curr_tok) {
case TOK_EOF:
fprintf(stderr, "error: json: unexpected eof\n");
return NULL;
case TOK_ERROR:
lex(p);
return NULL;
case TOK_NULL:
lex(p);
return alloc((JsonValue) { .type = JsonType_Null });
case TOK_FALSE:
lex(p);
return alloc(
(JsonValue) { .type = JsonType_Bool, .bool_val = false });
case TOK_TRUE:
lex(p);
return alloc(
(JsonValue) { .type = JsonType_Null, .bool_val = true });
case TOK_NUMBER: {
char* val = p->curr_val;
lex(p);
return alloc(
(JsonValue) { .type = JsonType_Number, .str_val = val });
}
case TOK_STRING: {
char* val = p->curr_val;
lex(p);
return alloc(
(JsonValue) { .type = JsonType_String, .str_val = val });
}
}
if (p->curr_tok == '[') {
lex(p);
Arr arr;
arr_construct(&arr);
{
JsonValue* value = json_parser_parse(p);
if (!value)
return NULL;
arr_push(&arr, value);
}
while (p->curr_tok != TOK_EOF && p->curr_tok != ']') {
if (p->curr_tok != ',') {
fprintf(stderr, "error: json: expected ',' in array\n");
return NULL;
}
lex(p);
JsonValue* value = json_parser_parse(p);
if (!value)
return NULL;
arr_push(&arr, value);
}
if (p->curr_tok != ']') {
fprintf(stderr, "error: json: expected ']' after array\n");
return NULL;
}
lex(p);
return alloc((JsonValue) { .type = JsonType_Array, .arr_val = arr });
}
if (p->curr_tok == '{') {
lex(p);
Obj obj;
obj_construct(&obj);
{
if (p->curr_tok != '"') {
fprintf(stderr, "error: json: expected '\"' in kv\n");
return NULL;
}
char* key = p->curr_val;
lex(p);
if (p->curr_tok != ':') {
fprintf(stderr, "error: json: expected ':' in kv\n");
return NULL;
}
lex(p);
JsonValue* value = json_parser_parse(p);
if (!value)
return NULL;
obj_push(&obj, (KV) { key, value });
}
while (p->curr_tok != TOK_EOF && p->curr_tok != '}') {
if (p->curr_tok != ',') {
fprintf(stderr, "error: json: expected ',' in object\n");
return NULL;
}
lex(p);
if (p->curr_tok != '"') {
fprintf(stderr, "error: json: expected '\"' in kv\n");
return NULL;
}
char* key = p->curr_val;
lex(p);
if (p->curr_tok != ':') {
fprintf(stderr, "error: json: expected ':' in kv\n");
return NULL;
}
lex(p);
JsonValue* value = json_parser_parse(p);
if (!value)
return NULL;
obj_push(&obj, (KV) { key, value });
}
if (p->curr_tok != '}') {
fprintf(stderr, "error: json: expected '}' after object\n");
return NULL;
}
lex(p);
return alloc((JsonValue) { .type = JsonType_Object, .obj_val = obj });
}
fprintf(stderr, "error: json: unexpeted tok\n");
return NULL;
}
static inline JsonValue* alloc(JsonValue init)
{
JsonValue* value = malloc(sizeof(JsonValue));
*value = init;
return value;
}
static inline void lex(JsonParser* p)
{
if (p->i >= p->text_len) {
p->curr_tok = TOK_EOF;
return;
}
switch (p->ch) {
case ' ':
case '\t':
case '\r':
case '\n':
lstep(p);
lex(p);
return;
case '[':
case ']':
case '{':
case '}':
case ',':
case ':':
p->curr_tok = p->ch;
lstep(p);
return;
case '0':
lstep(p);
p->curr_tok = TOK_NUMBER;
return;
}
if ((p->ch >= '1' && p->ch <= '9') || p->ch == '.') {
String value;
string_construct(&value);
int dec_seps = 0;
while (p->i < p->text_len
&& ((p->ch >= '0' && p->ch <= '9')
|| (p->ch == '.' && dec_seps <= 1))) {
if (p->ch == '.') {
dec_seps += 1;
}
string_push(&value, p->ch);
lstep(p);
}
// should be interned
char* copy = string_copy(&value);
string_destroy(&value);
p->curr_tok = TOK_NUMBER;
p->curr_val = copy;
return;
}
if (p->ch == '"') {
lstep(p);
String value;
string_construct(&value);
while (p->i < p->text_len && p->text[p->i] != '"') {
if (p->text[p->i] == '\\') {
lstep(p);
if (p->i >= p->text_len)
break;
switch (p->ch) {
case '0':
string_push(&value, '\0');
break;
case 'n':
string_push(&value, '\n');
break;
case 'r':
string_push(&value, '\r');
break;
case 't':
string_push(&value, '\t');
break;
default:
string_push(&value, p->ch);
break;
}
} else {
string_push(&value, p->ch);
}
lstep(p);
}
if (p->i >= p->text_len || p->text[p->i] != '"') {
string_destroy(&value);
fprintf(stderr, "error: json: bad string\n");
p->curr_tok = TOK_ERROR;
return;
}
lstep(p);
// should be interned
char* copy = string_copy(&value);
string_destroy(&value);
p->curr_tok = '"';
p->curr_val = copy;
return;
}
fprintf(stderr, "error: json: illegal char '%c'\n", p->ch);
lstep(p);
p->curr_tok = TOK_ERROR;
return;
}
static inline void lstep(JsonParser* p)
{
p->i += 1;
p->ch = p->text[p->i];
}

44
slige/runtime/src/json.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum {
JsonType_Error,
JsonType_Null,
JsonType_Bool,
JsonType_Number,
JsonType_String,
JsonType_Array,
JsonType_Object,
} JsonType;
typedef struct JsonValue JsonValue;
bool json_is(const JsonValue* value, JsonType type);
bool json_bool(const JsonValue* value);
int64_t json_int(const JsonValue* value);
double json_float(const JsonValue* value);
const char* json_string(const JsonValue* value);
size_t json_array_size(const JsonValue* value);
const JsonValue* json_array_get(const JsonValue* value, size_t idx);
bool json_object_has(const JsonValue* value, const char* key);
const JsonValue* json_object_get(const JsonValue* value, const char* key);
void json_value_free(JsonValue* value);
typedef struct {
const char* text;
size_t text_len;
size_t i;
char ch;
char curr_tok;
char* curr_val;
} JsonParser;
void json_parser_construct(
JsonParser* parser, const char* text, size_t text_len);
void json_parser_destroy(JsonParser* parser);
JsonValue* json_parser_parse(JsonParser* parser);

View File

@ -1,4 +1,5 @@
#include "http_server.h"
#include "json.h"
#include <stdio.h>
#include <string.h>
@ -6,28 +7,52 @@ typedef struct {
int number;
} Cx;
#define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \
{ \
HttpCtx* _ctx = (HTTP_CTX); \
char _body[512]; \
snprintf(_body, 512 - 1, __VA_ARGS__); \
\
char content_length[24] = { 0 }; \
snprintf(content_length, 24 - 1, "%ld", strlen(_body)); \
\
http_ctx_res_headers_set(_ctx, "Content-Type", MIME_TYPE); \
http_ctx_res_headers_set(_ctx, "Content-Length", content_length); \
\
http_ctx_respond(_ctx, (STATUS), _body); \
}
#define RESPOND_HTML(HTTP_CTX, STATUS, ...) \
RESPOND(HTTP_CTX, STATUS, "text/html", __VA_ARGS__)
#define RESPOND_JSON(HTTP_CTX, STATUS, ...) \
RESPOND(HTTP_CTX, STATUS, "application/json", __VA_ARGS__)
void route_get_index(HttpCtx* ctx)
{
Cx* cx = http_ctx_user_ctx(ctx);
char body[512];
snprintf(body, 512 - 1,
RESPOND_HTML(ctx, 200,
"<!DOCTYPE html><html><head><meta "
"charset=\"utf-8\"></head><body><h1>Number = %d</h1></body></html>",
cx->number);
char content_length[24] = { 0 };
snprintf(content_length, 24 - 1, "%ld", strlen(body));
http_ctx_res_headers_set(ctx, "Content-Type", "text/html");
http_ctx_res_headers_set(ctx, "Content-Length", content_length);
http_ctx_respond(ctx, 200, body);
}
void route_post_set_number(HttpCtx* ctx)
{
printf("set number\n");
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);
int64_t value = json_int(json_object_get(body, "value"));
cx->number = (int)value;
json_value_free(body);
RESPOND_JSON(ctx, 200, "{\"ok\": true}\r\n");
}
HttpServer* server;

View File

@ -0,0 +1,46 @@
#include "str_util.h"
#include <stddef.h>
#include <string.h>
StrSplitter str_split(const char* text, size_t text_len, const char* split)
{
return (StrSplitter) {
.text = text,
.text_len = text_len,
.i = 0,
.split = split,
.split_len = strlen(split),
};
}
StrSlice strsplit_next(StrSplitter* splitter)
{
for (size_t i = splitter->i; i < splitter->text_len; ++i) {
if (strncmp(&splitter->text[i], splitter->split, splitter->split_len)
== 0) {
const char* ptr = &splitter->text[splitter->i];
size_t len = i - splitter->i;
splitter->i = i + splitter->split_len;
return (StrSlice) { ptr, len };
}
}
return (StrSlice) {
.ptr = &splitter->text[splitter->i],
.len = splitter->text_len - splitter->i,
};
}
void string_push_str(String* string, const char* str)
{
for (size_t i = 0; i < strlen(str); ++i) {
string_push(string, str[i]);
}
}
char* string_copy(const String* string)
{
char* copy = malloc(string->size + 1);
strncpy(copy, string->data, string->size);
copy[string->size] = '\0';
return copy;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "collection.h"
#include <stddef.h>
typedef struct {
const char* ptr;
size_t len;
} StrSlice;
typedef struct {
const char* text;
size_t text_len;
size_t i;
const char* split;
size_t split_len;
} StrSplitter;
StrSplitter str_split(const char* text, size_t text_len, const char* split);
StrSlice strsplit_next(StrSplitter* splitter);
DEFINE_VEC(char, String, string, 8)
void string_push_str(String* string, const char* str);
char* string_copy(const String* string);
DEFINE_VEC(char*, RawStrVec, rawstr_vec, 8)