commit 172934e8a07eb7e73ac6cb389a6af667ea116248 Author: Katajisto Date: Thu Nov 20 23:05:04 2025 +0200 initial http server diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92f2071 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +first +.build diff --git a/first.jai b/first.jai new file mode 100644 index 0000000..802f34d --- /dev/null +++ b/first.jai @@ -0,0 +1 @@ +#load "main.jai"; diff --git a/main.jai b/main.jai new file mode 100644 index 0000000..0adb952 --- /dev/null +++ b/main.jai @@ -0,0 +1,133 @@ +#import "Basic"; +#import "Hash_Table"; +#import "Socket"; +String :: #import "String"; + +#load "server.jai"; + +consoom :: (input: *string, delimiter: string) -> string { + if input.count < 1 then return "INVALID READ"; + 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); + 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, " "); + req.route = consoom(data, " "); + 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_add(*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("%: %\n", k,v); + 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; +} + +handle_signal :: (sig: s32, info: *siginfo_t, secret: *void) #c_call { + c : #Context; + push_context(c) { + print("Graceful exit!!!\n"); + close_and_reset(*main_socket); + } +} +main :: () { + 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); + + + while true { + rbuf : [2048 * 1000]u8; + 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); + } +} diff --git a/page.jai b/page.jai new file mode 100644 index 0000000..2aab128 --- /dev/null +++ b/page.jai @@ -0,0 +1,18 @@ +serve_page :: (res: *Response, content: string) { + res.body = sprint("%\n%\n%", header, content, footer); +} + +#scope_file +header : string = #string DONE + + + + Omakase + + +DONE + +footer : string = #string DONE + + +DONE diff --git a/server.jai b/server.jai new file mode 100644 index 0000000..14f93f4 --- /dev/null +++ b/server.jai @@ -0,0 +1,12 @@ +#load "style.jai"; +#load "page.jai"; + +handler :: (req: Request, res: *Response) { + if req.route == { + case "/"; + serve_page(res, "

Hello from Jai!

"); + return; + case "/style.css"; + serve_stylesheet(res); + } +} diff --git a/style.jai b/style.jai new file mode 100644 index 0000000..f996c0f --- /dev/null +++ b/style.jai @@ -0,0 +1,24 @@ +serve_stylesheet :: (res: *Response) { + set_header(res, "Content-Type", "text/stylesheet"); + res.body = stylesheet; +} + +stylesheet : string = #string DONE +:root { + --ink: #050505; + --paper: #FAFAFA; + --gold: #C5A059; + --gold-dim: #E0D0B0; +} + +@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:wght@452&family=Prata&display=swap'); + +body { + font-family: "Prata", serif; + font-weight: 400; + font-style: normal; + background-color: var(--paper); + color: var(--gold); +} + +DONE