164 lines
4.6 KiB
Plaintext
164 lines
4.6 KiB
Plaintext
#import "Basic";
|
|
#import "Hash_Table";
|
|
#import "Socket";
|
|
#import "Pool";
|
|
String :: #import "String";
|
|
|
|
#load "server.jai";
|
|
|
|
consoom :: (input: *string, delimiter: string) -> string {
|
|
if input.count < 1 then return "";
|
|
found, left, right := String.split_from_left(input.*, delimiter);
|
|
input.* = right;
|
|
return left;
|
|
}
|
|
|
|
Request :: struct {
|
|
route : string;
|
|
method : string;
|
|
protocol : string;
|
|
headers : Table(string, string);
|
|
query : Table(string, string);
|
|
body : string;
|
|
}
|
|
|
|
Response :: struct {
|
|
protocol : string = "HTTP/1.1";
|
|
status : int = 200;
|
|
headers : Table(string, string);
|
|
body : string;
|
|
}
|
|
|
|
parse_request :: (data: *string) -> Request {
|
|
req := New(Request);
|
|
req.method = consoom(data, " ");
|
|
full_route := consoom(data, " ");
|
|
|
|
ok, route, query := String.split_from_left(full_route, "?");
|
|
req.route = route;
|
|
|
|
curQuery := consoom(*query, "&");
|
|
while curQuery.count > 0 {
|
|
key := consoom(*curQuery, "=");
|
|
value := curQuery;
|
|
table_set(*req.query, key, value);
|
|
curQuery = consoom(*query, "&");
|
|
}
|
|
|
|
req.protocol = consoom(data, "\n");
|
|
headers_string := consoom(data, "\r\n\r\n");
|
|
|
|
while headers_string.count > 0 {
|
|
key := consoom(*headers_string, ": ");
|
|
value := consoom(*headers_string, "\n");
|
|
table_set(*req.headers, key, value);
|
|
}
|
|
|
|
req.body = data.*;
|
|
return req;
|
|
}
|
|
|
|
set_header :: (res: *Response, key: string, value: string) {
|
|
table_add(*res.headers, key, value);
|
|
}
|
|
|
|
serialize_response :: (res: *Response) -> string {
|
|
builder : String_Builder;
|
|
print_to_builder(*builder, "% % OK", res.protocol, res.status);
|
|
for v, k : res.headers {
|
|
print_to_builder(*builder, "\n%: %", k, v);
|
|
}
|
|
print_to_builder(*builder, "\r\n\r\n");
|
|
print_to_builder(*builder, "%", res.body);
|
|
return builder_to_string(*builder);
|
|
}
|
|
|
|
sigaction_t :: struct {
|
|
sa_sigaction: (sig: s32, info: *siginfo_t, p: *void) #c_call;
|
|
sa_mask: sigset_t; // Signal mask to apply.
|
|
sa_flags: s32; // See signal options below.
|
|
|
|
#if OS == .LINUX then sa_restorer: () #c_call;
|
|
}
|
|
|
|
libc :: #library,system "libc";
|
|
|
|
|
|
sigaction :: (signum: s32, act: *sigaction_t, oldact: *void) -> s32 #foreign libc;
|
|
|
|
main_socket : Socket;
|
|
|
|
sigset_t :: struct { __val: [16] u64; }
|
|
|
|
siginfo_t :: struct { // This is not the correct size, but we don't instantiate it, so it's fine.
|
|
si_signo: s32;
|
|
si_errno: s32;
|
|
si_code: s32;
|
|
__pad0: s32;
|
|
si_addr: *void;
|
|
}
|
|
|
|
is_killed : bool = false;
|
|
|
|
handle_signal :: (sig: s32, info: *siginfo_t, secret: *void) #c_call {
|
|
c : #Context;
|
|
push_context c {
|
|
print("Exiting gracefully.....\n");
|
|
}
|
|
is_killed = true;
|
|
}
|
|
main :: () {
|
|
init_server();
|
|
socket_init();
|
|
main_socket = socket(AF_INET, .SOCK_STREAM, .TCP);
|
|
reuse : s32 = 1;
|
|
setsockopt(main_socket, SOL_SOCKET, SO_REUSEADDR, *reuse, size_of(s32));
|
|
err := bind(main_socket, "127.0.0.1", 8080);
|
|
if err != 0 {
|
|
print("Error in binding socket: %\n", err);
|
|
return;
|
|
}
|
|
err = listen(main_socket, 1);
|
|
if err != 0 {
|
|
print("Error in listening: %\n", err);
|
|
return;
|
|
}
|
|
|
|
sa: sigaction_t;
|
|
sa.sa_sigaction = handle_signal;
|
|
sigaction(2, *sa, null);
|
|
|
|
// Creating a pool and setting it as the allocator in the context.
|
|
// This means that we can do whatever the fuck after this, and we
|
|
// can release all the memory by releasing memory in the pool.
|
|
//
|
|
// We can also just reset the pool, meaning that we just reset pointer
|
|
// to the pool's start, but keep the pool's size. So allocating inside
|
|
// a request is basically free and we never actually free any memory or
|
|
// allocate any new memory (unless we go over the previous amount of max used memory).
|
|
reqpool : Pool;
|
|
set_allocators(*reqpool);
|
|
context.allocator = .{pool_allocator_proc, *reqpool};
|
|
|
|
while !is_killed {
|
|
rbuf : [2048 * 1000]u8; // @ToDo: This sucks, we should have some better thing here.
|
|
addr : sockaddr;
|
|
addr_len : socklen_t;
|
|
reqs := accept(main_socket, *addr, *addr_len);
|
|
bytesRead := recv(reqs, rbuf.data, rbuf.count, 0);
|
|
datastring : string;
|
|
datastring.count = bytesRead;
|
|
datastring.data = rbuf.data;
|
|
|
|
req := parse_request(*datastring);
|
|
res : Response;
|
|
handler(req, *res);
|
|
res_string := serialize_response(*res);
|
|
send(reqs, res_string.data, xx res_string.count, 0);
|
|
close_and_reset(*reqs);
|
|
// Here we reset the pool, obviously you should not be keeping pointers between requests.
|
|
reset(*reqpool);
|
|
}
|
|
close_and_reset(*main_socket);
|
|
}
|