mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
runtime: http server works
This commit is contained in:
parent
3c43ca074f
commit
aeeed02c35
151
slige/runtime/src/collection.h
Normal file
151
slige/runtime/src/collection.h
Normal 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; \
|
||||
}
|
130
slige/runtime/src/http_response_code_string.c
Normal file
130
slige/runtime/src/http_response_code_string.c
Normal 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>";
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user