aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorspl3g <spleefer6@yandex.ru>2025-03-11 21:47:03 +0300
committerspl3g <spleefer6@yandex.ru>2025-03-11 21:47:03 +0300
commit74ab2f6b89ceb2af34524386d6003746570d2b03 (patch)
treee634c55982970264306ca99d3ea1134f089c0096
parent729bbab9d554567366b30cbc24e91070e131fc04 (diff)
Add the prototype
-rw-r--r--Makefile2
-rw-r--r--arena.h108
-rw-r--r--arena_strings.c53
-rw-r--r--arena_strings.h17
-rw-r--r--const_strings.h108
-rw-r--r--main.c412
6 files changed, 700 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ea00ee9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+main: main.c const_strings.h arena.h arena_strings.h arena_strings.c
+ cc -Wall -Wextra -ggdb -o main main.c arena_strings.c
diff --git a/arena.h b/arena.h
new file mode 100644
index 0000000..c99fc97
--- /dev/null
+++ b/arena.h
@@ -0,0 +1,108 @@
+#ifndef ARENA_H_
+#define ARENA_H_
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <assert.h>
+
+typedef struct region region;
+
+struct region {
+ region *next;
+ size_t cap;
+ size_t len;
+ uintptr_t data[];
+};
+
+typedef struct {
+ region *beg;
+ region *end;
+} arena;
+
+void *arena_alloc(arena *a, size_t size);
+void arena_free(arena *a);
+void grow_da(void *slice, size_t size, arena *arena);
+
+
+#define new(a, t, c) (t *)arena_alloc(a, sizeof(t)*c)
+
+#define ARENA_REGION_DEFAULT_CAPACITY (8*1024);
+
+// from nullprogram
+#define push(slice, arena) \
+ ((slice)->len >= (slice)->cap \
+ ? grow(slice, sizeof(*(slice)->data), arena), \
+ (slice)->data + (slice)->len++ \
+ : (slice)->data + (slice)->len++)
+
+#endif //ARENA_H_
+
+#ifdef ARENA_IMPLEMENTATION
+
+region *new_region(size_t cap) {
+ size_t size_bytes = sizeof(region) + sizeof(uintptr_t)*cap;
+ region *r = (region *)malloc(size_bytes);
+ assert(r);
+ r->next = NULL;
+ r->len = 0;
+ r->cap = cap;
+ return r;
+}
+
+void *arena_alloc(arena *a, size_t size_bytes) {
+ size_t size = (size_bytes + sizeof(uintptr_t)-1)/sizeof(uintptr_t);
+
+ if (a->end == NULL) {
+ assert(a->beg == NULL);
+ size_t capacity = ARENA_REGION_DEFAULT_CAPACITY;
+ if (capacity < size) capacity = size;
+ a->beg = new_region(capacity);
+ a->end = a->beg;
+ }
+
+ while (a->end->len + size < a->end->cap && a->end->next != NULL) {
+ a->end = a->end->next;
+ }
+
+ if (a->end->len + size > a->end->cap) {
+ size_t capacity = ARENA_REGION_DEFAULT_CAPACITY;
+ if (capacity < size) capacity = size;
+ a->beg = new_region(capacity);
+ a->end->next = new_region(capacity);
+ a->end = a->end->next;
+ }
+
+ void *p = &a->end->data[a->end->len];
+ a->end->len += size;
+ return memset(p, 0, size);
+}
+
+void arena_free(arena *a) {
+ region *r = a->beg;
+ while (r != NULL) {
+ region *r0 = r;
+ r = r0->next;
+ free(r0);
+ }
+}
+
+void grow(void *slice, size_t size, arena *arena) {
+ struct {
+ void *data;
+ size_t len;
+ size_t cap;
+ } replica;
+ memcpy(&replica, slice, sizeof(replica));
+
+ replica.cap = replica.cap ? replica.cap : 1;
+ void *data = arena_alloc(arena, size*2*replica.cap);
+ replica.cap *= 2;
+ if (replica.len) {
+ memcpy(data, replica.data, size*replica.len);
+ }
+ replica.data = data;
+
+ memcpy(slice, &replica, sizeof(replica));
+}
+#endif // ARENA_IMPLEMENTATION
diff --git a/arena_strings.c b/arena_strings.c
new file mode 100644
index 0000000..28bd81a
--- /dev/null
+++ b/arena_strings.c
@@ -0,0 +1,53 @@
+#include "arena_strings.h"
+
+const_string arena_cs_init(arena *a, int len) {
+ char *str = new(a, char, len+1);
+ const_string cs = {0};
+ cs.data = str;
+ cs.len = len;
+ return cs;
+}
+
+const_string arena_cs_append(arena *a, const_string dst, const_string src) {
+ char *str = new(a, char, dst.len+src.len + 1);
+ memcpy(str, dst.data, dst.len);
+ memcpy(str + dst.len, src.data, src.len + 1);
+ const_string cs = {0};
+ cs.data = str;
+ cs.len = dst.len + src.len;
+ return cs;
+}
+
+const_string arena_cs_concat(arena *a, const_string_da strings, const_string sep) {
+ if (strings.len == 0) {
+ return CS("");
+ } else if (strings.len == 0) {
+ return *strings.data;
+ }
+
+ volatile int len = sep.len * (strings.len - 1);
+ for (size_t i = 0; i < strings.len; i++) {
+ len += (strings.data + i)->len;
+ };
+
+ volatile const_string str = arena_cs_init(a, len);
+ volatile int offset = 0;
+
+ for (size_t i = 0; i < strings.len; i++) {
+ volatile int curr_strlen = (strings.data + i)->len;
+ memcpy(str.data + offset,
+ (strings.data + i)->data,
+ curr_strlen);
+
+ offset += curr_strlen;
+
+ if (i != strings.len - 1) {
+ memcpy(str.data + offset,
+ sep.data, sep.len);
+ offset += sep.len;
+ }
+ };
+ *(str.data + len + 1) = '\0';
+
+ return str;
+};
diff --git a/arena_strings.h b/arena_strings.h
new file mode 100644
index 0000000..8a8add0
--- /dev/null
+++ b/arena_strings.h
@@ -0,0 +1,17 @@
+#ifndef ARENA_STRINGS_H
+#define ARENA_STRINGS_H
+
+#include "arena.h"
+#include "const_strings.h"
+
+typedef struct {
+ const_string *data;
+ size_t len;
+ size_t cap;
+} const_string_da;
+
+const_string arena_cs_append(arena *a, const_string dst, const_string src);
+const_string arena_cs_init(arena *a, int len);
+const_string arena_cs_concat(arena *a, const_string_da strings, const_string sep);
+
+#endif // ARENA_STRINGS_H
diff --git a/const_strings.h b/const_strings.h
new file mode 100644
index 0000000..3f13b95
--- /dev/null
+++ b/const_strings.h
@@ -0,0 +1,108 @@
+#ifndef CONST_STRINGS_H_
+#define CONST_STRINGS_H_
+
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef struct {
+ char* data;
+ int len;
+} const_string;
+
+
+const_string cs_from_parts(char* data, int len);
+const_string cs_from_cstr(char* cstr);
+const_string cs_slice(const_string src, int from, int to);
+const_string cs_chop_delim(const_string *src, char delim);
+int cs_find_delim(const_string str, char delim);
+void cs_print(char *format, const_string str);
+
+#define CS(cstr) cs_from_parts(cstr, sizeof(cstr) - 1)
+#define CS_STATIC(cstr) {.data = cstr, .len = sizeof(cstr) - 1}
+
+#endif // STRINGS_H_
+
+
+#ifdef CONST_STRINGS_IMPLEMENTATION
+
+const_string cs_from_parts(char* data, int len) {
+ const_string str;
+ str.data = data;
+ str.len = len;
+
+ return str;
+}
+
+const_string cs_from_cstr(char* cstr) {
+ return cs_from_parts(cstr, strlen(cstr));
+}
+
+char* cs_to_cstr(const_string str) {
+ str.data[str.len] = '\0';
+ return str.data;
+}
+
+const_string cs_slice(const_string src, int from, int to) {
+ if (from > src.len) {
+ return src;
+ }
+
+ int new_from;
+ if (from <= 0) {
+ new_from = 1;
+ }
+ new_from -= 1;
+
+ int new_len;
+ if (to > src.len) {
+ new_len = src.len;
+ } else {
+ new_len = to;
+ }
+
+ return cs_from_parts(src.data + new_from, new_len);
+}
+
+const_string cs_chop_delim(const_string *str, char delim) {
+ int n;
+ for (n = 0; n < str->len && str->data[n] != delim; n++) {}
+
+ const_string result = cs_from_parts(str->data, n);
+
+ if (n < str->len) {
+ str->len -= n + 1;
+ str->data += n + 1;
+ } else {
+ str->len -= n;
+ str->data += n;
+ }
+
+ return result;
+}
+
+int cs_find_delim(const_string str, char delim) {
+ int n;
+ for (n = 0; n < str.len && str.data[n] != delim; n++) {}
+
+ if (n < str.len) {
+ return n;
+ }
+ return -1;
+}
+
+bool cs_eq(const_string str1, const_string str2) {
+ if (str1.len != str2.len) {
+ return false;
+ }
+ return memcmp(str1.data, str2.data, str1.len) == 0;
+}
+
+void cs_print(char *format, const_string str) {
+ char buf[str.len + 1];
+ memcpy(buf, str.data, str.len);
+ buf[str.len] = '\0';
+ printf(format, buf);
+}
+
+#endif // STRINGS_IMPLEMENTATION
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..85ac157
--- /dev/null
+++ b/main.c
@@ -0,0 +1,412 @@
+#include <stdio.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdlib.h>
+#include "arena_strings.h"
+
+#define CONST_STRINGS_IMPLEMENTATION
+#include "const_strings.h"
+
+#define ARENA_IMPLEMENTATION
+#include "arena.h"
+
+#define MAX_DATA_SIZE 256
+#define BUFFER_SIZE 4096
+#define REQUEST_ARENA_SIZE 16*1024
+
+int volatile keepRunning = 1;
+
+struct header {
+ const_string key;
+ const_string value;
+};
+
+
+typedef struct {
+ struct header *data;
+ size_t len;
+ size_t cap;
+} header_da;
+
+struct request {
+ const_string method;
+ const_string path;
+ header_da headers;
+ const_string body;
+};
+
+struct response {
+ size_t code;
+ const_string body;
+ const_string string;
+};
+
+typedef void (*handler_func)(struct request, struct response *resp);
+
+struct handler {
+ const_string method;
+ const_string path;
+ handler_func func;
+};
+
+typedef struct {
+ struct handler *data;
+ size_t len;
+ size_t cap;
+} handler_da;
+
+struct server {
+ arena *arena;
+ int sockfd;
+ struct sockaddr *addr;
+ handler_da handlers;
+};
+
+typedef enum {
+ CONTINUE = 100,
+ SWITCHING_PROTOCOLS = 101,
+ PROCESSING = 102,
+ EARLY_HINTS = 103,
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NO_CONTENT = 204,
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY = 301,
+ FOUND = 302,
+ SEE_OTHER = 303,
+ NOT_MODIFIED = 304,
+ TEMPORARY_REDIRECT = 307,
+ PERMANENT_REDIRECT = 308,
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ PAYMENT_REQUIRED = 402,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ METHOD_NOT_ALLOWED = 405,
+ NOT_ACCEPTABLE = 406,
+ IM_A_TEEPOT = 418,
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+ BAD_GATEWAY = 502,
+ SERVICE_UNAVAILABLE = 503,
+ HTTP_VERSION_NOT_SUPPPORTED = 505,
+} response_codes;
+
+struct {
+ int code;
+ const_string string;
+} response_strings [] = {
+ {100, CS_STATIC("Continue")},
+ {101, CS_STATIC("Switching Protocols")},
+ {102, CS_STATIC("Processing")},
+ {103, CS_STATIC("Early Hints")},
+ {200, CS_STATIC("OK")},
+ {201, CS_STATIC("Created")},
+ {202, CS_STATIC("Accepted")},
+ {204, CS_STATIC("No Content")},
+ {300, CS_STATIC("Multiple Choices")},
+ {301, CS_STATIC("Moved Permanently")},
+ {302, CS_STATIC("Found")},
+ {303, CS_STATIC("See Other")},
+ {304, CS_STATIC("Not Modified")},
+ {307, CS_STATIC("Temporary Redirect")},
+ {308, CS_STATIC("Permanent Redirect")},
+ {400, CS_STATIC("Bad Request")},
+ {401, CS_STATIC("Unauthorized")},
+ {402, CS_STATIC("Payment Required")},
+ {403, CS_STATIC("Forbidden")},
+ {404, CS_STATIC("Not Found")},
+ {405, CS_STATIC("Method Not Allowed")},
+ {406, CS_STATIC("Not Acceptable")},
+ {418, CS_STATIC("Im A Teepot")},
+ {500, CS_STATIC("Internal Server Error")},
+ {501, CS_STATIC("Not Implemented")},
+ {502, CS_STATIC("Bad Gateway")},
+ {503, CS_STATIC("Service Unavailable")},
+ {505, CS_STATIC("Http Version Not Suppported")},
+};
+
+const_string get_response_string(int code) {
+ for (int i = 0; sizeof(response_strings)/sizeof(response_strings[0]) ;i++) {
+ if (response_strings[i].code == code) {
+ return response_strings[i].string;
+ }
+ }
+ return CS("");
+}
+
+
+void sigchld_handler()
+{
+ // waitpid() might overwrite errno, so we save and restore it:
+ int saved_errno = errno;
+
+ while(waitpid(-1, NULL, WNOHANG) > 0);
+
+ errno = saved_errno;
+}
+
+
+void *get_in_addr(struct sockaddr *sa) {
+ if (sa->sa_family == AF_INET) {
+ return &(((struct sockaddr_in*)sa)->sin_addr);
+ }
+
+ return &(((struct sockaddr_in6*)sa)->sin6_addr);
+}
+
+int get_in_port(struct sockaddr *sa) {
+ if (sa->sa_family == AF_INET) {
+ return ntohs((((struct sockaddr_in*)sa)->sin_port));
+ }
+
+ return (((struct sockaddr_in6*)sa)->sin6_port);
+}
+
+int init_server(arena *arena, struct server *serv, char *addr, char *port) {
+ // serv initialization
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ int status = getaddrinfo(addr, port, &hints, &res);
+ if (status != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
+ return 1;
+ }
+
+ int sockfd = socket(res->ai_family, res->ai_socktype, 0);
+ if (sockfd < 0) {
+ perror("bind");
+ return 1;
+ }
+
+ int yes = 1;
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) {
+ perror("setsockopt");
+ return 1;
+ }
+
+ if (bind(sockfd, res->ai_addr, res->ai_addrlen)) {
+ perror("bind");
+ return 1;
+ }
+
+ serv->arena = arena;
+ serv->addr = (struct sockaddr *)res->ai_addr;
+ serv->sockfd = sockfd;
+
+ freeaddrinfo(res);
+
+ struct sigaction sa;
+ sa.sa_handler = sigchld_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+ perror("sigaction");
+ return 1;
+ }
+
+ char my_ipstr[INET6_ADDRSTRLEN];
+ inet_ntop(serv->addr->sa_family, get_in_addr(serv->addr), my_ipstr, sizeof my_ipstr);
+ printf("Listening on %s:%d\n", my_ipstr, get_in_port(serv->addr));
+
+ return 0;
+}
+
+void handle_path(struct server *serv, const_string method, const_string path, handler_func handler) {
+ struct handler hand = {
+ .method = method,
+ .path = path,
+ .func = handler,
+ };
+
+ *push(&serv->handlers, serv->arena) = hand;
+}
+
+void compose_response(struct response *resp, arena *perm, arena scratch) {
+ const_string_da header_da = {0};
+ *push(&header_da, &scratch) = CS("HTTP/1.1");
+
+ if (resp->code != 0) {
+ char buf[4];
+ sprintf(buf, "%ld", resp->code);
+ *push(&header_da, &scratch) = cs_from_cstr(buf);
+ *push(&header_da, &scratch) = get_response_string(resp->code);
+ } else {
+ printf("Response is handeled, but no code is given");
+ *push(&header_da, &scratch) = CS("500");
+ *push(&header_da, &scratch) = get_response_string(INTERNAL_SERVER_ERROR);
+ }
+
+ volatile const_string header = arena_cs_concat(&scratch, header_da, CS(" "));
+
+ volatile const_string str;
+ if (resp->body.len != 0) {
+ const_string_da resp_da= {0};
+
+ *push(&resp_da, perm) = header;
+ *push(&resp_da, perm) = CS("");
+ *push(&resp_da, perm) = resp->body;
+
+ str = arena_cs_concat(&scratch, resp_da, CS("\r\n"));
+ } else {
+ str = arena_cs_append(perm, header, CS("\r\n\r\n"));
+ }
+
+ resp->string = str;
+}
+
+void process_request(struct server *serv, ssize_t inc_fd) {
+ arena req_arena = {0};
+ arena req_scratch = {0};
+ char buf[MAX_DATA_SIZE];
+ const_string req_str = {0};
+
+ int got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0);
+ if (got < 0) {
+ perror("recv");
+ return;
+ }
+
+ buf[got] = '\0';
+
+ req_str = arena_cs_append(&req_arena, req_str, cs_from_cstr(buf));
+
+ while (got == MAX_DATA_SIZE - 1) {
+ got = recv(inc_fd, &buf, MAX_DATA_SIZE - 1, 0);
+ if (got < 0) {
+ perror("recv");
+ return;
+ }
+
+ buf[got] = '\0';
+
+ const_string tmp = cs_from_parts(buf, got);
+ req_str = arena_cs_append(&req_arena, req_str, tmp);
+ }
+
+ const_string method = cs_chop_delim(&req_str, ' ');
+ const_string path = cs_chop_delim(&req_str, ' ');
+ cs_chop_delim(&req_str, '\r');
+ req_str.data += 1;
+
+ header_da headers = {0};
+ while (req_str.data[0] != '\r') {
+ const_string header_str = cs_chop_delim(&req_str, '\r');
+ req_str.data += 1;
+
+ struct header header;
+ header.key = cs_chop_delim(&header_str, ':');
+ header_str.data++;
+ header.value = header_str;
+
+ *push(&headers, &req_arena) = header;
+ }
+ cs_chop_delim(&req_str, '\n');
+
+ struct request req = {
+ .method = method,
+ .path = path,
+ .headers = headers,
+ .body = req_str,
+ };
+ struct response resp = {0};
+
+ bool handled = false;
+ for (size_t i = 0; i < serv->handlers.len; i++) {
+ struct handler handler = serv->handlers.data[i];
+ if ((cs_eq(handler.path, CS("HEAD"))
+ || cs_eq(handler.path, path))
+ && cs_eq(serv->handlers.data[i].path, path)) {
+ serv->handlers.data[i].func(req, &resp);
+ handled = true;
+ break;
+ }
+ }
+
+ if (!handled) {
+ resp.code = 404;
+ }
+
+ if (resp.string.len == 0) {
+ compose_response(&resp, &req_arena, req_scratch);
+ }
+
+ char *resp_cstr = resp.string.data;
+ int resp_len = resp.string.len;
+
+ int sent = send(inc_fd, resp_cstr, resp_len, 0);
+ while (sent > 0 && resp_len - sent > 0) {
+ resp_len -= sent;
+ sent = send(inc_fd, resp_cstr, resp_len, 0);
+ }
+
+ if (sent < 0) {
+ fprintf(stderr, "send: %s\n", strerror(errno));
+ }
+
+ close(inc_fd);
+ arena_free(&req_arena);
+}
+
+int listen_and_serve(struct server *serv) {
+ if (listen(serv->sockfd, 10) != 0) {
+ fprintf(stderr, "listen: %s\n", strerror(errno));
+ return 1;
+ }
+
+ while (keepRunning) {
+ struct sockaddr inc_addr;
+ socklen_t addr_size = sizeof inc_addr;
+ int inc_fd = accept(serv->sockfd, &inc_addr, &addr_size);
+ if (inc_fd < 0) {
+ perror("accept");
+ return 1;
+ }
+
+ process_request(serv, inc_fd);
+ close(inc_fd);
+ }
+ return 0;
+}
+
+void success(struct request req, struct response *resp) {
+ printf("Success handler:\n");
+ cs_print("Method: %s\n", req.method);
+ cs_print("Path: %s\n", req.path);
+ cs_print("Body: %s\n", req.body);
+
+ resp->code = OK;
+ resp->body = CS("Hello world!\n");
+}
+
+int main() {
+ arena arena = {0};
+ struct server serv = {0};
+ if (init_server(&arena, &serv, "127.0.0.1", "6969") != 0) {
+ return 1;
+ }
+
+ handle_path(&serv, CS("GET"), CS("/"), success);
+ listen_and_serve(&serv);
+
+ // closing and freeing
+ printf("closing sockets");
+ close(serv.sockfd);
+
+ return 0;
+}