mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-28 08:44:06 +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 "http_server_internal.h"
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -49,12 +48,16 @@ HttpServer* http_server_new(HttpServerOpts opts)
|
|||||||
// .ctx = {},
|
// .ctx = {},
|
||||||
.workers = malloc(sizeof(Worker) * opts.workers_amount),
|
.workers = malloc(sizeof(Worker) * opts.workers_amount),
|
||||||
.workers_size = 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) {
|
for (size_t i = 0; i < opts.workers_amount; ++i) {
|
||||||
worker_construct(&server->workers[i], &server->ctx);
|
worker_construct(&server->workers[i], &server->ctx);
|
||||||
}
|
}
|
||||||
|
handler_vec_construct(&server->handlers);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
@ -65,13 +68,14 @@ void http_server_free(HttpServer* server)
|
|||||||
for (size_t i = 0; i < server->workers_size; ++i) {
|
for (size_t i = 0; i < server->workers_size; ++i) {
|
||||||
worker_destroy(&server->workers[i]);
|
worker_destroy(&server->workers[i]);
|
||||||
}
|
}
|
||||||
server_ctx_destroy(&server->ctx);
|
ctx_destroy(&server->ctx);
|
||||||
|
handler_vec_destroy(&server->handlers);
|
||||||
free(server);
|
free(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_server_listen(HttpServer* server)
|
int http_server_listen(HttpServer* server)
|
||||||
{
|
{
|
||||||
ServerCtx* ctx = &server->ctx;
|
Cx* ctx = &server->ctx;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
SockAddrIn client_addr;
|
SockAddrIn client_addr;
|
||||||
@ -83,7 +87,7 @@ int http_server_listen(HttpServer* server)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Request req = { .client_file = res, client_addr };
|
Client req = { .file = res, client_addr };
|
||||||
pthread_mutex_lock(&ctx->mutex);
|
pthread_mutex_lock(&ctx->mutex);
|
||||||
|
|
||||||
res = request_queue_push(&ctx->req_queue, req);
|
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_mutex_init(&ctx->mutex, NULL);
|
||||||
pthread_cond_init(&ctx->cond, NULL);
|
pthread_cond_init(&ctx->cond, NULL);
|
||||||
request_queue_construct(&ctx->req_queue, 8192);
|
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_mutex_destroy(&ctx->mutex);
|
||||||
pthread_cond_destroy(&ctx->cond);
|
pthread_cond_destroy(&ctx->cond);
|
||||||
request_queue_destroy(&ctx->req_queue);
|
request_queue_destroy(&ctx->req_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
int request_queue_construct(RequestQueue* queue, size_t capacity)
|
static inline int worker_construct(Worker* worker, Cx* ctx)
|
||||||
{
|
|
||||||
*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)
|
|
||||||
{
|
{
|
||||||
*worker = (Worker) {
|
*worker = (Worker) {
|
||||||
// .thread = {},
|
// .thread = {},
|
||||||
@ -172,7 +209,7 @@ int worker_construct(Worker* worker, ServerCtx* ctx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void worker_destroy(Worker* worker)
|
static inline void worker_destroy(Worker* worker)
|
||||||
{
|
{
|
||||||
if (worker->thread != 0) {
|
if (worker->thread != 0) {
|
||||||
pthread_cancel(worker->thread);
|
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* worker = data;
|
||||||
worker_listen(worker);
|
worker_listen(worker);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void worker_listen(Worker* worker)
|
static inline void worker_listen(Worker* worker)
|
||||||
{
|
{
|
||||||
ServerCtx* ctx = worker->ctx;
|
Cx* ctx = worker->ctx;
|
||||||
while (true) {
|
while (true) {
|
||||||
pthread_testcancel();
|
pthread_testcancel();
|
||||||
|
|
||||||
@ -205,7 +242,7 @@ void worker_listen(Worker* worker)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Request req;
|
Client req;
|
||||||
request_queue_pop(&ctx->req_queue, &req);
|
request_queue_pop(&ctx->req_queue, &req);
|
||||||
pthread_mutex_unlock(&ctx->mutex);
|
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;
|
(void)worker;
|
||||||
|
|
||||||
uint8_t buffer[MAX_HEADER_BUFFER_SIZE] = { 0 };
|
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) {
|
if (bytes_received == -1) {
|
||||||
fprintf(stderr, "error: could not receive request\n");
|
fprintf(stderr, "error: could not receive request\n");
|
||||||
goto f_return;
|
goto l0_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t header_end = 0;
|
size_t header_end = 0;
|
||||||
@ -234,135 +271,195 @@ void worker_handle_request(Worker* worker, Request* req)
|
|||||||
if (header_end == 0) {
|
if (header_end == 0) {
|
||||||
fprintf(stderr, "error: header too big, exceeded %d bytes\n",
|
fprintf(stderr, "error: header too big, exceeded %d bytes\n",
|
||||||
MAX_HEADER_BUFFER_SIZE);
|
MAX_HEADER_BUFFER_SIZE);
|
||||||
goto f_return;
|
goto l0_return;
|
||||||
}
|
}
|
||||||
puts((char*)buffer);
|
// puts((char*)buffer);
|
||||||
|
|
||||||
HttpReq http_req;
|
Req req;
|
||||||
int res = http_parse_header(&http_req, (char*)buffer, header_end);
|
size_t body_idx;
|
||||||
|
int res = parse_header(&req, &body_idx, (char*)buffer, header_end);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
fprintf(stderr, "error: failed to parse header\n");
|
fprintf(stderr, "error: failed to parse header\n");
|
||||||
goto f_return;
|
goto l0_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (http_req_has_header(&http_req, "Content-Length")) { }
|
char* body = NULL;
|
||||||
|
if (req.method == Method_POST) {
|
||||||
printf("headers:\n");
|
if (!req_has_header(&req, "Content-Length")) {
|
||||||
for (size_t i = 0; i < http_req.headers_size; ++i) {
|
fprintf(stderr,
|
||||||
HttpHeader* header = &http_req.headers[i];
|
"error: POST request has no body and/or Content-Length "
|
||||||
printf("'%s': '%s'\n", header->key, header->value);
|
"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:
|
HttpCtx handler_ctx = {
|
||||||
close(req->client_file);
|
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_header(HttpReq* req, const char* buf, size_t buf_size)
|
l1_return:
|
||||||
|
header_vec_destroy(&handler_ctx.res_headers);
|
||||||
|
req_destroy(&req);
|
||||||
|
free(body);
|
||||||
|
l0_return:
|
||||||
|
close(client->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int parse_header(
|
||||||
|
Req* req, size_t* body_idx, const char* const buf, size_t buf_size)
|
||||||
{
|
{
|
||||||
#define CHECK_OVERRUN \
|
Strlitter splitter = string_split(buf, buf_size, "\r\n");
|
||||||
if (i >= buf_size) { \
|
|
||||||
return -1; \
|
|
||||||
}
|
|
||||||
|
|
||||||
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 };
|
if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) {
|
||||||
size_t method_buf_i = 0;
|
fprintf(stderr, "error: unrecognized http version '%.*s'\n",
|
||||||
for (; i < buf_size && method_buf_i < 8 && buf[i] != ' '; ++i) {
|
(int)version_str.len, version_str.ptr);
|
||||||
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);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* path = calloc(MAX_PATH_SIZE, sizeof(char));
|
Method method;
|
||||||
size_t path_i = 0;
|
if (strncmp(method_str.ptr, "GET", method_str.len) == 0) {
|
||||||
for (; i < buf_size && path_i < MAX_PATH_SIZE - 1 && buf[i] != ' '; ++i) {
|
method = Method_GET;
|
||||||
char ch = buf[i];
|
} else if (strncmp(method_str.ptr, "POST", method_str.len) == 0) {
|
||||||
path[path_i] = ch;
|
method = Method_POST;
|
||||||
path_i += 1;
|
} else {
|
||||||
}
|
fprintf(stderr, "error: unrecognized http method '%.*s'\n",
|
||||||
CHECK_OVERRUN;
|
(int)method_str.len, method_str.ptr);
|
||||||
|
return -1;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*req = (HttpReq) {
|
if (path_str.len >= MAX_PATH_LEN + 1)
|
||||||
method,
|
return -1;
|
||||||
path,
|
|
||||||
headers,
|
char* path = calloc(MAX_PATH_LEN + 1, sizeof(char));
|
||||||
headers_size,
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_req_destroy(HttpReq* req)
|
static inline void req_destroy(Req* req)
|
||||||
{
|
{
|
||||||
free(req->path);
|
free(req->path);
|
||||||
for (size_t i = 0; i < req->headers_size; ++i) {
|
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||||
free(req->headers[i].key);
|
free(req->headers.data[i].key);
|
||||||
free(req->headers[i].value);
|
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) {
|
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||||
if (strcmp(key, req->headers[i].key) == 0) {
|
if (strcmp(key, req->headers.data[i].key) == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
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) {
|
for (size_t i = 0; i < req->headers.size; ++i) {
|
||||||
if (strcmp(key, req->headers[i].key) == 0) {
|
if (strcmp(key, req->headers.data[i].key) == 0) {
|
||||||
return req->headers[i].value;
|
return req->headers.data[i].value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -10,6 +11,9 @@ typedef struct {
|
|||||||
size_t workers_amount;
|
size_t workers_amount;
|
||||||
} HttpServerOpts;
|
} HttpServerOpts;
|
||||||
|
|
||||||
|
typedef struct HttpCtx HttpCtx;
|
||||||
|
typedef void (*HttpHandlerFn)(HttpCtx* ctx);
|
||||||
|
|
||||||
/// On ok, HttpServer
|
/// On ok, HttpServer
|
||||||
/// On error, returns NULL and prints.
|
/// On error, returns NULL and prints.
|
||||||
HttpServer* http_server_new(HttpServerOpts opts);
|
HttpServer* http_server_new(HttpServerOpts opts);
|
||||||
@ -17,3 +21,16 @@ void http_server_free(HttpServer* server);
|
|||||||
/// On ok, returns 0.
|
/// On ok, returns 0.
|
||||||
/// On error, returns -1 and prints;
|
/// On error, returns -1 and prints;
|
||||||
int http_server_listen(HttpServer* server);
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include "collection.h"
|
||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
#include <bits/pthreadtypes.h>
|
#include <bits/pthreadtypes.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
@ -14,89 +15,113 @@ typedef struct sockaddr SockAddr;
|
|||||||
typedef struct sockaddr_in SockAddrIn;
|
typedef struct sockaddr_in SockAddrIn;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int client_file;
|
int file;
|
||||||
SockAddrIn client_addr;
|
SockAddrIn addr;
|
||||||
} Request;
|
} Client;
|
||||||
|
|
||||||
/// This shit is implemented as a static size fifo buffer.
|
DEFINE_STATIC_QUEUE(Client, ReqQueue, request_queue)
|
||||||
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);
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
const HttpServer* server;
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
pthread_cond_t cond;
|
pthread_cond_t cond;
|
||||||
RequestQueue req_queue;
|
ReqQueue req_queue;
|
||||||
} ServerCtx;
|
} Cx;
|
||||||
|
|
||||||
void server_ctx_construct(ServerCtx* ctx);
|
static inline void ctx_construct(Cx* ctx, const HttpServer* server);
|
||||||
void server_ctx_destroy(ServerCtx* ctx);
|
static inline void ctx_destroy(Cx* ctx);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
ServerCtx* ctx;
|
Cx* ctx;
|
||||||
} Worker;
|
} Worker;
|
||||||
|
|
||||||
/// On ok, returns 0.
|
/// On ok, returns 0.
|
||||||
/// On error, returns -1;
|
/// On error, returns -1;
|
||||||
int worker_construct(Worker* worker, ServerCtx* ctx);
|
static inline int worker_construct(Worker* worker, Cx* ctx);
|
||||||
void worker_destroy(Worker* worker);
|
static inline void worker_destroy(Worker* worker);
|
||||||
void* worker_thread_fn(void* data);
|
static inline void* worker_thread_fn(void* data);
|
||||||
void worker_thread_cleanup(void* data);
|
static inline void worker_listen(Worker* worker);
|
||||||
void worker_listen(Worker* worker);
|
static inline void worker_handle_request(Worker* worker, Client* req);
|
||||||
void worker_handle_request(Worker* worker, Request* req);
|
|
||||||
|
|
||||||
struct HttpServer {
|
|
||||||
int file;
|
|
||||||
SockAddrIn addr;
|
|
||||||
ServerCtx ctx;
|
|
||||||
Worker* workers;
|
|
||||||
size_t workers_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define MAX_HEADER_BUFFER_SIZE 8192
|
#define MAX_HEADER_BUFFER_SIZE 8192
|
||||||
|
|
||||||
#define MAX_PATH_SIZE 128
|
#define MAX_PATH_LEN 128 - 1
|
||||||
#define MAX_HEADERS_SIZE 32
|
#define MAX_HEADERS_LEN 32
|
||||||
#define MAX_HEADER_KEY_SIZE 32
|
#define MAX_HEADER_KEY_LEN 32 - 1
|
||||||
#define MAX_HEADER_VALUE_SIZE 64
|
#define MAX_HEADER_VALUE_LEN 128 - 1
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
HttpMethod_GET,
|
Method_GET,
|
||||||
HttpMethod_POST,
|
Method_POST,
|
||||||
} HttpMethod;
|
} Method;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* key;
|
char* key;
|
||||||
char* value;
|
char* value;
|
||||||
} HttpHeader;
|
} Header;
|
||||||
|
|
||||||
|
DEFINE_VEC(Header, HeaderVec, header_vec, 8)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
HttpMethod method;
|
Method method;
|
||||||
char* path;
|
char* path;
|
||||||
HttpHeader* headers;
|
HeaderVec headers;
|
||||||
size_t headers_size;
|
} Req;
|
||||||
} HttpReq;
|
|
||||||
|
|
||||||
/// On error, returns -1.
|
/// On error, returns -1.
|
||||||
int http_parse_header(HttpReq* req, const char* buf, size_t buf_size);
|
static inline int parse_header(
|
||||||
void http_req_destroy(HttpReq* req);
|
Req* req, size_t* body_idx, const char* const buf, size_t buf_size);
|
||||||
bool http_req_has_header(HttpReq* req, const char* key);
|
static inline void req_destroy(Req* req);
|
||||||
const char* http_req_get_header(HttpReq* req, const char* key);
|
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 "http_server.h"
|
||||||
#include <signal.h>
|
|
||||||
#include <stdio.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;
|
HttpServer* server;
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
printf("hello world\n");
|
Cx cx = { .number = 1 };
|
||||||
|
|
||||||
server = http_server_new((HttpServerOpts) {
|
server = http_server_new((HttpServerOpts) {
|
||||||
.port = 8080,
|
.port = 8080,
|
||||||
@ -17,6 +44,10 @@ int main(void)
|
|||||||
return -1;
|
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");
|
printf("listening at http://127.0.0.1:8080/\n");
|
||||||
http_server_listen(server);
|
http_server_listen(server);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user