runtime: http server works

This commit is contained in:
sfja 2025-03-05 01:28:47 +01:00
parent 3c43ca074f
commit aeeed02c35
6 changed files with 674 additions and 223 deletions

View File

@ -0,0 +1,151 @@
#pragma once
#include <stddef.h>
#include <stdlib.h>
#define DEFINE_VEC(TYPE, VEC_TYPE, FN_PREFIX, INITIAL_CAPACITY) \
typedef struct { \
TYPE* data; \
size_t capacity; \
size_t size; \
} VEC_TYPE; \
\
static inline int FN_PREFIX##_construct(VEC_TYPE* vec) \
{ \
const size_t capacity = INITIAL_CAPACITY; \
TYPE* data = malloc(sizeof(TYPE) * capacity); \
if (!data) \
return -1; \
*vec = (VEC_TYPE) { data, capacity, .size = 0 }; \
return 0; \
} \
\
static inline void FN_PREFIX##_destroy(VEC_TYPE* vec) \
{ \
free(vec->data); \
} \
\
static inline VEC_TYPE* FN_PREFIX##_new(void) \
{ \
VEC_TYPE* vec = malloc(sizeof(VEC_TYPE)); \
if (!vec) \
return NULL; \
int res = FN_PREFIX##_construct(vec); \
if (res != 0) \
return NULL; \
return vec; \
} \
\
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) \
{ \
if (vec->size + 1 > vec->capacity) { \
size_t new_capacity = vec->capacity * 2; \
TYPE* new_data = realloc(vec->data, new_capacity * sizeof(TYPE)); \
if (!new_data) \
return -1; \
vec->data = new_data; \
vec->capacity = new_capacity; \
} \
vec->data[vec->size] = value; \
vec->size += 1; \
return 0; \
} \
\
static inline TYPE* FN_PREFIX##_at(VEC_TYPE* vec, size_t idx) \
{ \
return &vec->data[idx]; \
} \
\
static inline const TYPE* 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) \
{ \
return vec->data[idx]; \
}
#define DEFINE_STATIC_QUEUE(TYPE, QUEUE_TYPE, FN_PREFIX) \
typedef struct { \
TYPE* data; \
size_t capacity; \
size_t back; \
size_t front; \
} QUEUE_TYPE; \
\
static inline int FN_PREFIX##_construct( \
QUEUE_TYPE* queue, size_t capacity) \
{ \
*queue = (QUEUE_TYPE) { \
.data = malloc(sizeof(TYPE) * capacity), \
.capacity = capacity, \
.back = 0, \
.front = 0, \
}; \
return 0; \
} \
\
static inline void FN_PREFIX##_destroy(QUEUE_TYPE* queue) \
{ \
free(queue->data); \
} \
\
static inline QUEUE_TYPE* FN_PREFIX##_new(size_t capacity) \
{ \
QUEUE_TYPE* queue = malloc(sizeof(QUEUE_TYPE)); \
if (!queue) \
return NULL; \
int res = FN_PREFIX##_construct(queue, capacity); \
if (res != 0) \
return NULL; \
return queue; \
} \
\
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) \
{ \
size_t front = queue->front + 1; \
if (front >= queue->capacity) { \
front = 0; \
} \
if (front == queue->back) { \
return -1; \
} \
queue->data[queue->front] = req; \
queue->front = front; \
return 0; \
} \
\
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) \
{ \
if (queue->back == queue->front) { \
return -1; \
} \
*req = queue->data[queue->back]; \
size_t back = queue->back + 1; \
if (back >= queue->capacity) { \
back = 0; \
} \
queue->back = back; \
return 0; \
}

View File

@ -0,0 +1,130 @@
const char* http_response_code_string(int code)
{
switch (code) {
case 100:
return "Continue";
case 101:
return "Switching Protocols";
case 102:
return "Processing";
case 103:
return "Early Hints";
case 200:
return "OK";
case 201:
return "Created";
case 202:
return "Accepted";
case 203:
return "Non-Authoritative Information";
case 204:
return "No Content";
case 205:
return "Reset Content";
case 206:
return "Partial Content";
case 207:
return "Multi-Status";
case 208:
return "Already Reported";
case 226:
return "IM Used";
case 300:
return "Multiple Choices";
case 301:
return "Moved Permanently";
case 302:
return "Found";
case 303:
return "See Other";
case 304:
return "Not Modified";
case 307:
return "Temporary Redirect";
case 308:
return "Permanent Redirect";
case 400:
return "Bad Request";
case 401:
return "Unauthorized";
case 402:
return "Payment Required";
case 403:
return "Forbidden";
case 404:
return "Not Found";
case 405:
return "Method Not Allowed";
case 406:
return "Not Acceptable";
case 407:
return "Proxy Authentication Required";
case 408:
return "Request Timeout";
case 409:
return "Conflict";
case 410:
return "Gone";
case 411:
return "Length Required";
case 412:
return "Precondition Failed";
case 413:
return "Content Too Large";
case 414:
return "URI Too Long";
case 415:
return "Unsupported Media Type";
case 416:
return "Range Not Satisfiable";
case 417:
return "Expectation Failed";
case 418:
return "I'm a teapot";
case 421:
return "Misdirected Request";
case 422:
return "Unprocessable Content";
case 423:
return "Locked";
case 424:
return "Failed Dependency";
case 425:
return "Too Early";
case 426:
return "Upgrade Required";
case 428:
return "Precondition Required";
case 429:
return "Too Many Requests";
case 431:
return "Request Header Fields Too Large";
case 451:
return "Unavailable For Legal Reasons";
case 500:
return "Internal Server Error";
case 501:
return "Not Implemented";
case 502:
return "Bad Gateway";
case 503:
return "Service Unavailable";
case 504:
return "Gateway Timeout";
case 505:
return "HTTP Version Not Supported";
case 506:
return "Variant Also Negotiates";
case 507:
return "Insufficient Storage";
case 508:
return "Loop Detected";
case 510:
return "Not Extended";
case 511:
return "Network Authentication Required";
default:
return "<unknown>";
}
}

View File

@ -2,7 +2,6 @@
#include "http_server_internal.h"
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
@ -49,12 +48,16 @@ HttpServer* http_server_new(HttpServerOpts opts)
// .ctx = {},
.workers = malloc(sizeof(Worker) * opts.workers_amount),
.workers_size = opts.workers_amount,
.handlers = { 0 },
.not_found_handler = NULL,
.user_ctx = NULL,
};
server_ctx_construct(&server->ctx);
ctx_construct(&server->ctx, server);
for (size_t i = 0; i < opts.workers_amount; ++i) {
worker_construct(&server->workers[i], &server->ctx);
}
handler_vec_construct(&server->handlers);
return server;
}
@ -65,13 +68,14 @@ void http_server_free(HttpServer* server)
for (size_t i = 0; i < server->workers_size; ++i) {
worker_destroy(&server->workers[i]);
}
server_ctx_destroy(&server->ctx);
ctx_destroy(&server->ctx);
handler_vec_destroy(&server->handlers);
free(server);
}
int http_server_listen(HttpServer* server)
{
ServerCtx* ctx = &server->ctx;
Cx* ctx = &server->ctx;
while (true) {
SockAddrIn client_addr;
@ -83,7 +87,7 @@ int http_server_listen(HttpServer* server)
return -1;
}
Request req = { .client_file = res, client_addr };
Client req = { .file = res, client_addr };
pthread_mutex_lock(&ctx->mutex);
res = request_queue_push(&ctx->req_queue, req);
@ -96,72 +100,105 @@ int http_server_listen(HttpServer* server)
}
}
void server_ctx_construct(ServerCtx* ctx)
void http_server_set_user_ctx(HttpServer* server, void* user_ctx)
{
server->user_ctx = user_ctx;
}
void http_server_get(
HttpServer* server, const char* path, HttpHandlerFn handler)
{
handler_vec_push(
&server->handlers, (Handler) { path, .method = Method_GET, handler });
}
void http_server_post(
HttpServer* server, const char* path, HttpHandlerFn handler)
{
handler_vec_push(
&server->handlers, (Handler) { path, .method = Method_POST, handler });
}
void http_server_set_not_found(HttpServer* server, HttpHandlerFn handler)
{
server->not_found_handler = handler;
}
void* http_ctx_user_ctx(HttpCtx* ctx)
{
return ctx->user_ctx;
}
bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key)
{
return req_has_header(ctx->req, key);
}
const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key)
{
return req_get_header(ctx->req, key);
}
const char* http_ctx_req_body(HttpCtx* ctx)
{
return ctx->req_body;
}
void http_ctx_res_headers_set(HttpCtx* ctx, const char* key, const char* value)
{
char* key_copy = malloc(strlen(key) + 1);
strcpy(key_copy, key);
char* value_copy = malloc(strlen(value) + 1);
strcpy(value_copy, value);
header_vec_push(&ctx->res_headers, (Header) { key_copy, value_copy });
}
void http_ctx_respond(HttpCtx* ctx, int status, const char* body)
{
String res;
string_construct(&res);
char first_line[32];
snprintf(first_line, 32 - 1, "HTTP/1.1 %d %s\r\n", status,
http_response_code_string(status));
string_push_str(&res, first_line);
for (size_t i = 0; i < ctx->res_headers.size; ++i) {
Header* header = &ctx->res_headers.data[i];
char line[96];
snprintf(line, 96 - 1, "%s: %s\r\n", header->key, header->value);
string_push_str(&res, line);
}
string_push_str(&res, "\r\n");
string_push_str(&res, body);
send(ctx->client->file, res.data, res.size, 0);
string_destroy(&res);
}
//
//
//
static inline void ctx_construct(Cx* ctx, const HttpServer* server)
{
ctx->server = server;
pthread_mutex_init(&ctx->mutex, NULL);
pthread_cond_init(&ctx->cond, NULL);
request_queue_construct(&ctx->req_queue, 8192);
}
void server_ctx_destroy(ServerCtx* ctx)
static inline void ctx_destroy(Cx* ctx)
{
pthread_mutex_destroy(&ctx->mutex);
pthread_cond_destroy(&ctx->cond);
request_queue_destroy(&ctx->req_queue);
}
int request_queue_construct(RequestQueue* queue, size_t capacity)
{
*queue = (RequestQueue) {
.data = malloc(sizeof(Request) * capacity),
.capacity = capacity,
.back = 0,
.front = 0,
};
return 0;
}
void request_queue_destroy(RequestQueue* queue)
{
free(queue->data);
}
int request_queue_push(RequestQueue* queue, Request req)
{
size_t front = queue->front + 1;
if (front >= queue->capacity) {
front = 0;
}
if (front == queue->back) {
return -1;
}
queue->data[queue->front] = req;
queue->front = front;
return 0;
}
size_t request_queue_size(const RequestQueue* queue)
{
return queue->front >= queue->back
? queue->front - queue->back
: (queue->capacity - queue->back) + queue->front;
}
int request_queue_pop(RequestQueue* queue, Request* req)
{
if (queue->back == queue->front) {
return -1;
}
*req = queue->data[queue->back];
size_t back = queue->back + 1;
if (back >= queue->capacity) {
back = 0;
}
queue->back = back;
return 0;
}
int worker_construct(Worker* worker, ServerCtx* ctx)
static inline int worker_construct(Worker* worker, Cx* ctx)
{
*worker = (Worker) {
// .thread = {},
@ -172,7 +209,7 @@ int worker_construct(Worker* worker, ServerCtx* ctx)
return 0;
}
void worker_destroy(Worker* worker)
static inline void worker_destroy(Worker* worker)
{
if (worker->thread != 0) {
pthread_cancel(worker->thread);
@ -184,16 +221,16 @@ void worker_destroy(Worker* worker)
}
}
void* worker_thread_fn(void* data)
static inline void* worker_thread_fn(void* data)
{
Worker* worker = data;
worker_listen(worker);
return NULL;
}
void worker_listen(Worker* worker)
static inline void worker_listen(Worker* worker)
{
ServerCtx* ctx = worker->ctx;
Cx* ctx = worker->ctx;
while (true) {
pthread_testcancel();
@ -205,7 +242,7 @@ void worker_listen(Worker* worker)
continue;
}
Request req;
Client req;
request_queue_pop(&ctx->req_queue, &req);
pthread_mutex_unlock(&ctx->mutex);
@ -213,16 +250,16 @@ void worker_listen(Worker* worker)
}
}
void worker_handle_request(Worker* worker, Request* req)
static inline void worker_handle_request(Worker* worker, Client* client)
{
(void)worker;
uint8_t buffer[MAX_HEADER_BUFFER_SIZE] = { 0 };
ssize_t bytes_received = recv(req->client_file, &buffer, sizeof(buffer), 0);
ssize_t bytes_received = recv(client->file, &buffer, sizeof(buffer), 0);
if (bytes_received == -1) {
fprintf(stderr, "error: could not receive request\n");
goto f_return;
goto l0_return;
}
size_t header_end = 0;
@ -234,135 +271,195 @@ void worker_handle_request(Worker* worker, Request* req)
if (header_end == 0) {
fprintf(stderr, "error: header too big, exceeded %d bytes\n",
MAX_HEADER_BUFFER_SIZE);
goto f_return;
goto l0_return;
}
puts((char*)buffer);
// puts((char*)buffer);
HttpReq http_req;
int res = http_parse_header(&http_req, (char*)buffer, header_end);
Req req;
size_t body_idx;
int res = parse_header(&req, &body_idx, (char*)buffer, header_end);
if (res != 0) {
fprintf(stderr, "error: failed to parse header\n");
goto f_return;
goto l0_return;
}
if (http_req_has_header(&http_req, "Content-Length")) { }
printf("headers:\n");
for (size_t i = 0; i < http_req.headers_size; ++i) {
HttpHeader* header = &http_req.headers[i];
printf("'%s': '%s'\n", header->key, header->value);
char* body = NULL;
if (req.method == Method_POST) {
if (!req_has_header(&req, "Content-Length")) {
fprintf(stderr,
"error: POST request has no body and/or Content-Length "
"header\n");
goto l1_return;
}
const char* length_val = req_get_header(&req, "Content-Length");
size_t length = strtoul(length_val, NULL, 10);
body = calloc(length + 1, sizeof(char));
}
f_return:
close(req->client_file);
HttpCtx handler_ctx = {
.client = client,
.req = &req,
.req_body = body,
.res_headers = { 0 },
.user_ctx = worker->ctx->server->user_ctx,
};
header_vec_construct(&handler_ctx.res_headers);
for (size_t i = 0; i < worker->ctx->server->handlers.size; ++i) {
Handler* handler = &worker->ctx->server->handlers.data[i];
if (handler->method != req.method)
continue;
if (strcmp(handler->path, req.path) != 0)
continue;
handler->handler(&handler_ctx);
break;
}
l1_return:
header_vec_destroy(&handler_ctx.res_headers);
req_destroy(&req);
free(body);
l0_return:
close(client->file);
}
int http_parse_header(HttpReq* req, const char* buf, size_t buf_size)
static inline int parse_header(
Req* req, size_t* body_idx, const char* const buf, size_t buf_size)
{
#define CHECK_OVERRUN \
if (i >= buf_size) { \
return -1; \
}
Strlitter splitter = string_split(buf, buf_size, "\r\n");
size_t i = 0;
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);
char method_buf[8] = { 0 };
size_t method_buf_i = 0;
for (; i < buf_size && method_buf_i < 8 && buf[i] != ' '; ++i) {
method_buf[method_buf_i] = buf[i];
method_buf_i += 1;
}
CHECK_OVERRUN;
i += 1;
HttpMethod method;
if (strncmp(method_buf, "GET", 3) == 0) {
method = HttpMethod_GET;
} else if (strncmp(method_buf, "POST", 4) == 0) {
method = HttpMethod_POST;
} else {
fprintf(stderr, "error: unrecognized http method '%.8s'\n", method_buf);
if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) {
fprintf(stderr, "error: unrecognized http version '%.*s'\n",
(int)version_str.len, version_str.ptr);
return -1;
}
char* path = calloc(MAX_PATH_SIZE, sizeof(char));
size_t path_i = 0;
for (; i < buf_size && path_i < MAX_PATH_SIZE - 1 && buf[i] != ' '; ++i) {
char ch = buf[i];
path[path_i] = ch;
path_i += 1;
}
CHECK_OVERRUN;
for (; i < buf_size && buf[i] != '\r'; ++i) { }
i += 2;
CHECK_OVERRUN;
HttpHeader* headers = malloc(sizeof(HttpHeader) * MAX_HEADERS_SIZE);
size_t headers_size = 0;
for (; i < buf_size && headers_size < MAX_HEADERS_SIZE && buf[i] != '\r';
++i) {
i += 1;
CHECK_OVERRUN;
char* key = calloc(MAX_HEADER_KEY_SIZE, sizeof(char));
size_t key_i = 0;
for (; i < buf_size && key_i < MAX_HEADER_KEY_SIZE - 1 && buf[i] != ':';
++i) {
key[key_i] = buf[i];
key_i += 1;
}
i += 1;
CHECK_OVERRUN;
char* value = calloc(MAX_HEADER_VALUE_SIZE, sizeof(char));
size_t value_i = 0;
for (; i < buf_size && value_i < MAX_HEADER_VALUE_SIZE - 1
&& buf[i] != '\r';
++i) {
value[value_i] = buf[i];
value_i += 1;
}
i += 2;
CHECK_OVERRUN;
headers[headers_size] = (HttpHeader) { key, value };
headers_size += 1;
Method method;
if (strncmp(method_str.ptr, "GET", method_str.len) == 0) {
method = Method_GET;
} else if (strncmp(method_str.ptr, "POST", method_str.len) == 0) {
method = Method_POST;
} else {
fprintf(stderr, "error: unrecognized http method '%.*s'\n",
(int)method_str.len, method_str.ptr);
return -1;
}
*req = (HttpReq) {
method,
path,
headers,
headers_size,
};
if (path_str.len >= MAX_PATH_LEN + 1)
return -1;
char* path = calloc(MAX_PATH_LEN + 1, sizeof(char));
strncpy(path, path_str.ptr, path_str.len);
path[path_str.len] = '\0';
HeaderVec headers;
header_vec_construct(&headers);
while (headers.size < MAX_HEADERS_LEN) {
StrSlice line = split_next(&splitter);
if (line.len == 0) {
*body_idx = (size_t)&line.ptr[2] - (size_t)buf;
break;
}
size_t key_len = 0;
while (key_len < line.len && line.ptr[key_len] != ':') {
key_len += 1;
}
if (key_len == 0 || key_len > MAX_HEADER_KEY_LEN) {
return -1;
}
size_t value_begin = key_len + 1;
while (value_begin < line.len && line.ptr[value_begin] == ' ') {
value_begin += 1;
}
size_t value_len = line.len - value_begin;
if (value_len == 0 || value_len > MAX_HEADER_VALUE_LEN) {
return -1;
}
char* key = calloc(key_len + 1, sizeof(char));
strncpy(key, line.ptr, key_len);
char* value = calloc(value_len + 1, sizeof(char));
strncpy(value, &line.ptr[value_begin], value_len);
header_vec_push(&headers, (Header) { key, value });
}
*req = (Req) { method, path, headers };
return 0;
}
void http_req_destroy(HttpReq* req)
static inline void req_destroy(Req* req)
{
free(req->path);
for (size_t i = 0; i < req->headers_size; ++i) {
free(req->headers[i].key);
free(req->headers[i].value);
for (size_t i = 0; i < req->headers.size; ++i) {
free(req->headers.data[i].key);
free(req->headers.data[i].value);
}
free(req->headers);
header_vec_destroy(&req->headers);
}
bool http_req_has_header(HttpReq* req, const char* key)
static inline bool req_has_header(const Req* req, const char* key)
{
for (size_t i = 0; i < req->headers_size; ++i) {
if (strcmp(key, req->headers[i].key) == 0) {
for (size_t i = 0; i < req->headers.size; ++i) {
if (strcmp(key, req->headers.data[i].key) == 0) {
return true;
}
}
return false;
}
const char* http_req_get_header(HttpReq* req, const char* key)
static inline const char* req_get_header(const Req* req, const char* key)
{
for (size_t i = 0; i < req->headers_size; ++i) {
if (strcmp(key, req->headers[i].key) == 0) {
return req->headers[i].value;
for (size_t i = 0; i < req->headers.size; ++i) {
if (strcmp(key, req->headers.data[i].key) == 0) {
return req->headers.data[i].value;
}
}
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

@ -1,5 +1,6 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@ -10,6 +11,9 @@ typedef struct {
size_t workers_amount;
} HttpServerOpts;
typedef struct HttpCtx HttpCtx;
typedef void (*HttpHandlerFn)(HttpCtx* ctx);
/// On ok, HttpServer
/// On error, returns NULL and prints.
HttpServer* http_server_new(HttpServerOpts opts);
@ -17,3 +21,16 @@ void http_server_free(HttpServer* server);
/// On ok, returns 0.
/// On error, returns -1 and prints;
int http_server_listen(HttpServer* server);
void http_server_set_user_ctx(HttpServer* server, void* user_ctx);
void http_server_get(
HttpServer* server, const char* path, HttpHandlerFn handler);
void http_server_post(
HttpServer* server, const char* path, HttpHandlerFn handler);
void http_server_set_not_found(HttpServer* server, HttpHandlerFn handler);
void* http_ctx_user_ctx(HttpCtx* ctx);
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_body(HttpCtx* ctx);
void http_ctx_res_headers_set(HttpCtx* ctx, const char* key, const char* value);
void http_ctx_respond(HttpCtx* ctx, int status, const char* body);

View File

@ -1,5 +1,6 @@
#pragma once
#include "collection.h"
#include "http_server.h"
#include <bits/pthreadtypes.h>
#include <netinet/in.h>
@ -14,89 +15,113 @@ typedef struct sockaddr SockAddr;
typedef struct sockaddr_in SockAddrIn;
typedef struct {
int client_file;
SockAddrIn client_addr;
} Request;
int file;
SockAddrIn addr;
} Client;
/// This shit is implemented as a static size fifo buffer.
typedef struct {
Request* data;
size_t capacity;
size_t back;
size_t front;
} RequestQueue;
/// On ok, returns 0.
/// On error, returns -1.
int request_queue_construct(RequestQueue* queue, size_t capacity);
void request_queue_destroy(RequestQueue* queue);
/// On ok, returns 0.
/// On error, returns -1 if queue is full.
int request_queue_push(RequestQueue* queue, Request req);
size_t request_queue_size(const RequestQueue* queue);
/// On ok, returns 0.
/// On error, returns -1 if queue is empty.
int request_queue_pop(RequestQueue* queue, Request* req);
DEFINE_STATIC_QUEUE(Client, ReqQueue, request_queue)
typedef struct {
const HttpServer* server;
pthread_mutex_t mutex;
pthread_cond_t cond;
RequestQueue req_queue;
} ServerCtx;
ReqQueue req_queue;
} Cx;
void server_ctx_construct(ServerCtx* ctx);
void server_ctx_destroy(ServerCtx* ctx);
static inline void ctx_construct(Cx* ctx, const HttpServer* server);
static inline void ctx_destroy(Cx* ctx);
typedef struct {
pthread_t thread;
ServerCtx* ctx;
Cx* ctx;
} Worker;
/// On ok, returns 0.
/// On error, returns -1;
int worker_construct(Worker* worker, ServerCtx* ctx);
void worker_destroy(Worker* worker);
void* worker_thread_fn(void* data);
void worker_thread_cleanup(void* data);
void worker_listen(Worker* worker);
void worker_handle_request(Worker* worker, Request* req);
struct HttpServer {
int file;
SockAddrIn addr;
ServerCtx ctx;
Worker* workers;
size_t workers_size;
};
static inline int worker_construct(Worker* worker, Cx* ctx);
static inline void worker_destroy(Worker* worker);
static inline void* worker_thread_fn(void* data);
static inline void worker_listen(Worker* worker);
static inline void worker_handle_request(Worker* worker, Client* req);
#define MAX_HEADER_BUFFER_SIZE 8192
#define MAX_PATH_SIZE 128
#define MAX_HEADERS_SIZE 32
#define MAX_HEADER_KEY_SIZE 32
#define MAX_HEADER_VALUE_SIZE 64
#define MAX_PATH_LEN 128 - 1
#define MAX_HEADERS_LEN 32
#define MAX_HEADER_KEY_LEN 32 - 1
#define MAX_HEADER_VALUE_LEN 128 - 1
typedef enum {
HttpMethod_GET,
HttpMethod_POST,
} HttpMethod;
Method_GET,
Method_POST,
} Method;
typedef struct {
char* key;
char* value;
} HttpHeader;
} Header;
DEFINE_VEC(Header, HeaderVec, header_vec, 8)
typedef struct {
HttpMethod method;
Method method;
char* path;
HttpHeader* headers;
size_t headers_size;
} HttpReq;
HeaderVec headers;
} Req;
/// On error, returns -1.
int http_parse_header(HttpReq* req, const char* buf, size_t buf_size);
void http_req_destroy(HttpReq* req);
bool http_req_has_header(HttpReq* req, const char* key);
const char* http_req_get_header(HttpReq* req, const char* key);
static inline int parse_header(
Req* req, size_t* body_idx, const char* const buf, size_t buf_size);
static inline void req_destroy(Req* req);
static inline bool req_has_header(const Req* req, const char* key);
static inline const char* req_get_header(const Req* req, const char* key);
typedef struct {
const char* path;
Method method;
HttpHandlerFn handler;
} Handler;
DEFINE_VEC(Handler, HandlerVec, handler_vec, 8)
struct HttpServer {
int file;
SockAddrIn addr;
Cx ctx;
Worker* workers;
size_t workers_size;
HandlerVec handlers;
HttpHandlerFn not_found_handler;
void* user_ctx;
};
struct HttpCtx {
Client* client;
const Req* req;
const char* req_body;
HeaderVec res_headers;
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);

View File

@ -1,13 +1,40 @@
#include "http_server.h"
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int number;
} Cx;
void route_get_index(HttpCtx* ctx)
{
Cx* cx = http_ctx_user_ctx(ctx);
char body[512];
snprintf(body, 512 - 1,
"<!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");
}
HttpServer* server;
int main(void)
{
printf("hello world\n");
Cx cx = { .number = 1 };
server = http_server_new((HttpServerOpts) {
.port = 8080,
@ -17,6 +44,10 @@ int main(void)
return -1;
}
http_server_set_user_ctx(server, &cx);
http_server_get(server, "/", route_get_index);
http_server_post(server, "/set_number", route_post_set_number);
printf("listening at http://127.0.0.1:8080/\n");
http_server_listen(server);